]> source.dussan.org Git - vaadin-framework.git/commitdiff
CommunicationManager is no longer dependent on the Portlet API. The refactored API...
authorPetter Holmström <petter.holmstrom@itmill.com>
Tue, 3 Nov 2009 11:59:16 +0000 (11:59 +0000)
committerPetter Holmström <petter.holmstrom@itmill.com>
Tue, 3 Nov 2009 11:59:16 +0000 (11:59 +0000)
svn changeset:9601/svn branch:portlet_2.0

src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/CommunicationManager.java
src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java
src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/WebBrowser.java

index fade97005fa141f03f8b2ad7558ad054236d98e9..325cda34341899396845592075ebdb4818f10659 100644 (file)
@@ -37,9 +37,11 @@ import com.vaadin.ui.Window;
 
 public abstract class AbstractApplicationPortlet extends GenericPortlet {
 
+    // TODO Move some (all?) of the constants to a separate interface (shared with servlet)
+    
     private static final String ERROR_NO_WINDOW_FOUND = "No window found. Did you remember to setMainWindow()?";
 
-    private static final String THEME_DIRECTORY_PATH = "VAADIN/themes/";
+    static final String THEME_DIRECTORY_PATH = "VAADIN/themes/";
 
     private static final String WIDGETSET_DIRECTORY_PATH = "VAADIN/widgetsets/";
 
@@ -162,7 +164,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet {
              */
             PortletApplicationContext2 applicationContext = PortletApplicationContext2
                     .getApplicationContext(request.getPortletSession());
-            CommunicationManager applicationManager = applicationContext
+            PortletCommunicationManager applicationManager = applicationContext
                     .getApplicationManager(application);
 
             /* Update browser information from request */
@@ -185,7 +187,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet {
             } else if (requestType == RequestType.UIDL) {
                 // Handles AJAX UIDL requests
                 applicationManager.handleUidlRequest((ResourceRequest) request,
-                        (ResourceResponse) response);
+                        (ResourceResponse) response, this);
                 return;
             } else if (requestType == RequestType.RENDER) {
                 /*
index f27bf51899c6de1a11dc1d1958d18a3c3609dcdf..9430d1d27678370cc5db785bde42417eeab15ea0 100644 (file)
@@ -58,6 +58,9 @@ import com.vaadin.ui.Window;
 
 @SuppressWarnings("serial")
 public abstract class AbstractApplicationServlet extends HttpServlet {
+    
+    // TODO Move some (all?) of the constants to a separate interface (shared with portlet)
+
     /**
      * Version number of this release. For example "5.0.0".
      */
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
new file mode 100644 (file)
index 0000000..cfd5beb
--- /dev/null
@@ -0,0 +1,1708 @@
+/* 
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.server;
+
+import java.io.BufferedWriter;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.Application;
+import com.vaadin.Application.SystemMessages;
+import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
+import com.vaadin.external.org.apache.commons.fileupload.FileItemStream;
+import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
+import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
+import com.vaadin.external.org.apache.commons.fileupload.ProgressListener;
+import com.vaadin.terminal.DownloadStream;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.URIHandler;
+import com.vaadin.terminal.UploadStream;
+import com.vaadin.terminal.VariableOwner;
+import com.vaadin.terminal.Paintable.RepaintRequestEvent;
+import com.vaadin.terminal.Terminal.ErrorEvent;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
+import com.vaadin.ui.AbstractField;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Upload;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.Upload.UploadException;
+
+/**
+ * TODO Document me!
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractCommunicationManager implements
+        Paintable.RepaintRequestListener, Serializable {
+
+    /**
+     * TODO Document me!
+     * 
+     * @author peholmst
+     */
+    protected interface Request {
+
+        public Session getSession();
+
+        public boolean isRunningInPortlet();
+
+        public String getParameter(String name);
+
+        public int getContentLength();
+
+        public InputStream getInputStream() throws IOException;
+
+        public String getRequestID();
+
+        public Object getAttribute(String name);
+
+        public void setAttribute(String name, Object o);
+
+        public Object getWrappedRequest();
+
+    }
+
+    /**
+     * TODO Document me!
+     * 
+     * @author peholmst
+     */
+    protected interface Response {
+
+        public OutputStream getOutputStream() throws IOException;
+
+        public void setContentType(String type);
+
+        public Object getWrappedResponse();
+
+    }
+
+    /**
+     * TODO Document me!
+     * 
+     * @author peholmst
+     */
+    protected interface Session {
+
+        public boolean isNew();
+
+        public Object getAttribute(String name);
+
+        public void setAttribute(String name, Object o);
+
+        public int getMaxInactiveInterval();
+
+        public Object getWrappedSession();
+
+    }
+
+    /**
+     * TODO Document me!
+     * 
+     * @author peholmst
+     */
+    protected interface Callback {
+
+        public void criticalNotification(Request request, Response response,
+                String cap, String msg, String details, String outOfSyncURL)
+                throws IOException;
+
+        public String getRequestPathInfo(Request request);
+
+        public InputStream getThemeResourceAsStream(String themeName,
+                String resource) throws IOException;
+
+    }
+
+    // FIXME Create an abstract class with custom Request/Response/Session
+    // interfaces, then create
+    // subclasses for servlets and portlets.
+
+    private static String GET_PARAM_REPAINT_ALL = "repaintAll";
+
+    // flag used in the request to indicate that the security token should be
+    // written to the response
+    private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
+
+    /* Variable records indexes */
+    private static final int VAR_PID = 1;
+    private static final int VAR_NAME = 2;
+    private static final int VAR_TYPE = 3;
+    private static final int VAR_VALUE = 0;
+
+    private static final String VAR_RECORD_SEPARATOR = "\u001e";
+
+    private static final String VAR_FIELD_SEPARATOR = "\u001f";
+
+    public static final String VAR_BURST_SEPARATOR = "\u001d";
+
+    public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
+
+    private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>();
+
+    private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+    private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts";
+
+    private final ArrayList<Paintable> dirtyPaintabletSet = new ArrayList<Paintable>();
+
+    private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>();
+
+    private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>();
+
+    private int idSequence = 0;
+
+    private final Application application;
+
+    // Note that this is only accessed from synchronized block and
+    // thus should be thread-safe.
+    private String closingWindowName = null;
+
+    private List<String> locales;
+
+    private int pendingLocalesIndex;
+
+    private int timeoutInterval = -1;
+
+    /**
+     * TODO New constructor - document me!
+     * 
+     * @param application
+     */
+    public AbstractCommunicationManager(Application application) {
+        this.application = application;
+        requireLocale(application.getLocale().toString());
+    }
+
+    /**
+     * TODO New method - document me!
+     * 
+     * @return
+     */
+    protected abstract FileUpload createFileUpload();
+
+    /**
+     * TODO New method - document me!
+     * 
+     * @param upload
+     * @param request
+     * @return
+     * @throws IOException
+     * @throws FileUploadException
+     */
+    protected abstract FileItemIterator getItemIterator(FileUpload upload,
+            Request request) throws IOException, FileUploadException;
+
+    /**
+     * TODO New method - document me!
+     * 
+     * @param request
+     * @param response
+     * @throws IOException
+     * @throws FileUploadException
+     */
+    protected void doHandleFileUpload(Request request, Response response)
+            throws IOException, FileUploadException {
+        // Create a new file upload handler
+        final FileUpload upload = createFileUpload();
+
+        final UploadProgressListener pl = new UploadProgressListener();
+
+        upload.setProgressListener(pl);
+
+        // Parse the request
+        FileItemIterator iter;
+
+        try {
+            iter = getItemIterator(upload, request);
+            /*
+             * ATM this loop is run only once as we are uploading one file per
+             * request.
+             */
+            while (iter.hasNext()) {
+                final FileItemStream item = iter.next();
+                final String name = item.getFieldName();
+                final String filename = item.getName();
+                final String mimeType = item.getContentType();
+                final InputStream stream = item.openStream();
+                if (item.isFormField()) {
+                    // ignored, upload requests contains only files
+                } else {
+                    final String pid = name.split("_")[0];
+                    final Upload uploadComponent = (Upload) idPaintableMap
+                            .get(pid);
+                    if (uploadComponent == null) {
+                        throw new FileUploadException(
+                                "Upload component not found");
+                    }
+                    if (uploadComponent.isReadOnly()) {
+                        throw new FileUploadException(
+                                "Warning: ignored file upload because upload component is set as read-only");
+                    }
+                    synchronized (application) {
+                        // put upload component into receiving state
+                        uploadComponent.startUpload();
+                    }
+                    final UploadStream upstream = new UploadStream() {
+
+                        public String getContentName() {
+                            return filename;
+                        }
+
+                        public String getContentType() {
+                            return mimeType;
+                        }
+
+                        public InputStream getStream() {
+                            return stream;
+                        }
+
+                        public String getStreamName() {
+                            return "stream";
+                        }
+
+                    };
+
+                    // tell UploadProgressListener which component is receiving
+                    // file
+                    pl.setUpload(uploadComponent);
+
+                    try {
+                        uploadComponent.receiveUpload(upstream);
+                    } catch (UploadException e) {
+                        // error happened while receiving file. Handle the
+                        // error in the same manner as it would have happened in
+                        // variable change.
+                        synchronized (application) {
+                            handleChangeVariablesError(application,
+                                    uploadComponent, e,
+                                    new HashMap<String, Object>());
+                        }
+                    }
+                }
+            }
+        } catch (final FileUploadException e) {
+            throw e;
+        }
+
+        // Send short response to acknowledge client that request was done
+        response.setContentType("text/html");
+        final OutputStream out = response.getOutputStream();
+        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+                new OutputStreamWriter(out, "UTF-8")));
+        outWriter.print("<html><body>download handled</body></html>");
+        outWriter.flush();
+        out.close();
+    }
+
+    /**
+     * TODO New method - document me!
+     * 
+     * @param request
+     * @param response
+     * @param callback
+     * @throws IOException
+     * @throws InvalidUIDLSecurityKeyException
+     */
+    protected void doHandleUidlRequest(Request request, Response response,
+            Callback callback) throws IOException,
+            InvalidUIDLSecurityKeyException {
+
+        // repaint requested or session has timed out and new one is created
+        boolean repaintAll;
+        final OutputStream out;
+
+        repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null)
+                || (request.getSession().isNew());
+        out = response.getOutputStream();
+
+        boolean analyzeLayouts = false;
+        if (repaintAll) {
+            // analyzing can be done only with repaintAll
+            analyzeLayouts = (request.getParameter(GET_PARAM_ANALYZE_LAYOUTS) != null);
+        }
+
+        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+                new OutputStreamWriter(out, "UTF-8")));
+
+        // The rest of the process is synchronized with the application
+        // in order to guarantee that no parallel variable handling is
+        // made
+        synchronized (application) {
+
+            // Finds the window within the application
+            Window window = null;
+            if (application.isRunning()) {
+                window = doGetApplicationWindow(request, callback, application,
+                        null);
+                // Returns if no window found
+                if (window == null) {
+                    // This should not happen, no windows exists but
+                    // application is still open.
+                    System.err
+                            .println("Warning, could not get window for application with request ID "
+                                    + request.getRequestID());
+                    return;
+                }
+            } else {
+                // application has been closed
+                endApplication(request, response, application);
+                return;
+            }
+
+            // Change all variables based on request parameters
+            if (!handleVariables(request, response, callback, application,
+                    window)) {
+
+                // var inconsistency; the client is probably out-of-sync
+                SystemMessages ci = null;
+                try {
+                    Method m = application.getClass().getMethod(
+                            "getSystemMessages", (Class[]) null);
+                    ci = (Application.SystemMessages) m.invoke(null,
+                            (Object[]) null);
+                } catch (Exception e2) {
+                    // FIXME: Handle exception
+                    // Not critical, but something is still wrong; print
+                    // stacktrace
+                    e2.printStackTrace();
+                }
+                if (ci != null) {
+                    String msg = ci.getOutOfSyncMessage();
+                    String cap = ci.getOutOfSyncCaption();
+                    if (msg != null || cap != null) {
+                        callback.criticalNotification(request, response, cap,
+                                msg, null, ci.getOutOfSyncURL());
+                        // will reload page after this
+                        return;
+                    }
+                }
+                // No message to show, let's just repaint all.
+                repaintAll = true;
+
+            }
+
+            paintAfterVariablechanges(request, response, callback, repaintAll,
+                    outWriter, window, analyzeLayouts);
+
+            if (closingWindowName != null) {
+                currentlyOpenWindowsInClient.remove(closingWindowName);
+                closingWindowName = null;
+            }
+        }
+
+        out.flush();
+        out.close();
+    }
+
+    private void paintAfterVariablechanges(Request request, Response response,
+            Callback callback, boolean repaintAll, final PrintWriter outWriter,
+            Window window, boolean analyzeLayouts) throws PaintException,
+            IOException {
+
+        if (repaintAll) {
+            // If repaint is requested, clean all ids in this root window
+            for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it
+                    .hasNext();) {
+                final Component c = (Component) idPaintableMap.get(it.next());
+                if (isChildOf(window, c)) {
+                    it.remove();
+                    paintableIdMap.remove(c);
+                }
+            }
+            // clean WindowCache
+            OpenWindowCache openWindowCache = currentlyOpenWindowsInClient
+                    .get(window.getName());
+            if (openWindowCache != null) {
+                openWindowCache.clear();
+            }
+        }
+
+        // Removes application if it has stopped during variable changes
+        if (!application.isRunning()) {
+            endApplication(request, response, application);
+            return;
+        }
+
+        // Sets the response type
+        response.setContentType("application/json; charset=UTF-8");
+        // some dirt to prevent cross site scripting
+        outWriter.print("for(;;);[{");
+
+        // security key
+        Object writeSecurityTokenFlag = request
+                .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
+
+        if (writeSecurityTokenFlag != null) {
+            String seckey = (String) request.getSession().getAttribute(
+                    ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
+            if (seckey == null) {
+                seckey = "" + (int) (Math.random() * 1000000);
+                request.getSession().setAttribute(
+                        ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey);
+            }
+            outWriter.print("\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID
+                    + "\":\"");
+            outWriter.print(seckey);
+            outWriter.print("\",");
+        }
+
+        outWriter.print("\"changes\":[");
+
+        ArrayList<Paintable> paintables = null;
+
+        // If the browser-window has been closed - we do not need to paint it at
+        // all
+        if (!window.getName().equals(closingWindowName)) {
+
+            List<InvalidLayout> invalidComponentRelativeSizes = null;
+
+            // re-get window - may have been changed
+            Window newWindow = doGetApplicationWindow(request, callback,
+                    application, window);
+            if (newWindow != window) {
+                window = newWindow;
+                repaintAll = true;
+            }
+
+            JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
+                    !repaintAll);
+            OpenWindowCache windowCache = currentlyOpenWindowsInClient
+                    .get(window.getName());
+            if (windowCache == null) {
+                windowCache = new OpenWindowCache();
+                currentlyOpenWindowsInClient.put(window.getName(), windowCache);
+            }
+
+            // Paints components
+            if (repaintAll) {
+                paintables = new ArrayList<Paintable>();
+                paintables.add(window);
+
+                // Reset sent locales
+                locales = null;
+                requireLocale(application.getLocale().toString());
+
+            } else {
+                // remove detached components from paintableIdMap so they
+                // can be GC'ed
+                for (Iterator<Paintable> it = paintableIdMap.keySet()
+                        .iterator(); it.hasNext();) {
+                    Component p = (Component) it.next();
+                    if (p.getApplication() == null) {
+                        idPaintableMap.remove(paintableIdMap.get(p));
+                        it.remove();
+                        dirtyPaintabletSet.remove(p);
+                        p.removeListener(this);
+                    }
+                }
+                paintables = getDirtyVisibleComponents(window);
+            }
+            if (paintables != null) {
+
+                // We need to avoid painting children before parent.
+                // This is ensured by ordering list by depth in component
+                // tree
+                Collections.sort(paintables, new Comparator<Paintable>() {
+                    public int compare(Paintable o1, Paintable o2) {
+                        Component c1 = (Component) o1;
+                        Component c2 = (Component) o2;
+                        int d1 = 0;
+                        while (c1.getParent() != null) {
+                            d1++;
+                            c1 = c1.getParent();
+                        }
+                        int d2 = 0;
+                        while (c2.getParent() != null) {
+                            d2++;
+                            c2 = c2.getParent();
+                        }
+                        if (d1 < d2) {
+                            return -1;
+                        }
+                        if (d1 > d2) {
+                            return 1;
+                        }
+                        return 0;
+                    }
+                });
+
+                for (final Iterator i = paintables.iterator(); i.hasNext();) {
+                    final Paintable p = (Paintable) i.next();
+
+                    // TODO CLEAN
+                    if (p instanceof Window) {
+                        final Window w = (Window) p;
+                        if (w.getTerminal() == null) {
+                            w.setTerminal(application.getMainWindow()
+                                    .getTerminal());
+                        }
+                    }
+                    /*
+                     * This does not seem to happen in tk5, but remember this
+                     * case: else if (p instanceof Component) { if (((Component)
+                     * p).getParent() == null || ((Component)
+                     * p).getApplication() == null) { // Component requested
+                     * repaint, but is no // longer attached: skip
+                     * paintablePainted(p); continue; } }
+                     */
+
+                    // TODO we may still get changes that have been
+                    // rendered already (changes with only cached flag)
+                    if (paintTarget.needsToBePainted(p)) {
+                        paintTarget.startTag("change");
+                        paintTarget.addAttribute("format", "uidl");
+                        final String pid = getPaintableId(p);
+                        paintTarget.addAttribute("pid", pid);
+
+                        p.paint(paintTarget);
+
+                        paintTarget.endTag("change");
+                    }
+                    paintablePainted(p);
+
+                    if (analyzeLayouts) {
+                        Window w = (Window) p;
+                        invalidComponentRelativeSizes = ComponentSizeValidator
+                                .validateComponentRelativeSizes(w.getContent(),
+                                        null, null);
+
+                        // Also check any existing subwindows
+                        if (w.getChildWindows() != null) {
+                            for (Window subWindow : w.getChildWindows()) {
+                                invalidComponentRelativeSizes = ComponentSizeValidator
+                                        .validateComponentRelativeSizes(
+                                                subWindow.getContent(),
+                                                invalidComponentRelativeSizes,
+                                                null);
+                            }
+                        }
+                    }
+                }
+            }
+
+            paintTarget.close();
+            outWriter.print("]"); // close changes
+
+            outWriter.print(", \"meta\" : {");
+            boolean metaOpen = false;
+
+            if (repaintAll) {
+                metaOpen = true;
+                outWriter.write("\"repaintAll\":true");
+                if (analyzeLayouts) {
+                    outWriter.write(", \"invalidLayouts\":");
+                    outWriter.write("[");
+                    if (invalidComponentRelativeSizes != null) {
+                        boolean first = true;
+                        for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
+                            if (!first) {
+                                outWriter.write(",");
+                            } else {
+                                first = false;
+                            }
+                            invalidLayout.reportErrors(outWriter, this,
+                                    System.err);
+                        }
+                    }
+                    outWriter.write("]");
+                }
+            }
+
+            SystemMessages ci = null;
+            try {
+                Method m = application.getClass().getMethod(
+                        "getSystemMessages", (Class[]) null);
+                ci = (Application.SystemMessages) m.invoke(null,
+                        (Object[]) null);
+            } catch (NoSuchMethodException e1) {
+                e1.printStackTrace();
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+
+            // meta instruction for client to enable auto-forward to
+            // sessionExpiredURL after timer expires.
+            if (ci != null && ci.getSessionExpiredMessage() == null
+                    && ci.getSessionExpiredCaption() == null
+                    && ci.isSessionExpiredNotificationEnabled()) {
+                int newTimeoutInterval = request.getSession()
+                        .getMaxInactiveInterval();
+                if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
+                    String escapedURL = ci.getSessionExpiredURL() == null ? ""
+                            : ci.getSessionExpiredURL().replace("/", "\\/");
+                    if (metaOpen) {
+                        outWriter.write(",");
+                    }
+                    outWriter.write("\"timedRedirect\":{\"interval\":"
+                            + (newTimeoutInterval + 15) + ",\"url\":\""
+                            + escapedURL + "\"}");
+                    metaOpen = true;
+                }
+                timeoutInterval = newTimeoutInterval;
+            }
+
+            outWriter.print("}, \"resources\" : {");
+
+            // Precache custom layouts
+            String themeName = window.getTheme();
+            String requestThemeName = request.getParameter("theme");
+
+            if (requestThemeName != null) {
+                themeName = requestThemeName;
+            }
+            if (themeName == null) {
+                themeName = AbstractApplicationServlet.getDefaultTheme();
+            }
+
+            // TODO We should only precache the layouts that are not
+            // cached already (plagiate from usedPaintableTypes)
+            int resourceIndex = 0;
+            for (final Iterator i = paintTarget.getUsedResources().iterator(); i
+                    .hasNext();) {
+                final String resource = (String) i.next();
+                InputStream is = null;
+                try {
+                    is = callback.getThemeResourceAsStream(themeName, resource);
+                } catch (final Exception e) {
+                    // FIXME: Handle exception
+                    e.printStackTrace();
+                }
+                if (is != null) {
+
+                    outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
+                            + resource + "\" : ");
+                    final StringBuffer layout = new StringBuffer();
+
+                    try {
+                        final InputStreamReader r = new InputStreamReader(is,
+                                "UTF-8");
+                        final char[] buffer = new char[20000];
+                        int charsRead = 0;
+                        while ((charsRead = r.read(buffer)) > 0) {
+                            layout.append(buffer, 0, charsRead);
+                        }
+                        r.close();
+                    } catch (final java.io.IOException e) {
+                        // FIXME: Handle exception
+                        System.err.println("Resource transfer failed:  "
+                                + request.getRequestID() + ". ("
+                                + e.getMessage() + ")");
+                    }
+                    outWriter.print("\""
+                            + JsonPaintTarget.escapeJSON(layout.toString())
+                            + "\"");
+                } else {
+                    // FIXME: Handle exception
+                    System.err.println("CustomLayout not found");
+                }
+            }
+            outWriter.print("}");
+
+            Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget
+                    .getUsedPaintableTypes();
+            boolean typeMappingsOpen = false;
+            for (Class<? extends Paintable> class1 : usedPaintableTypes) {
+                if (windowCache.cache(class1)) {
+                    // client does not know the mapping key for this type, send
+                    // mapping to client
+                    if (!typeMappingsOpen) {
+                        typeMappingsOpen = true;
+                        outWriter.print(", \"typeMappings\" : { ");
+                    } else {
+                        outWriter.print(" , ");
+                    }
+                    String canonicalName = class1.getCanonicalName();
+                    outWriter.print("\"");
+                    outWriter.print(canonicalName);
+                    outWriter.print("\" : ");
+                    outWriter.print(getTagForType(class1));
+                }
+            }
+            if (typeMappingsOpen) {
+                outWriter.print(" }");
+            }
+
+            printLocaleDeclarations(outWriter);
+
+            outWriter.print("}]");
+        }
+        outWriter.flush();
+        outWriter.close();
+
+    }
+
+    /**
+     * If this method returns false, something was submitted that we did not
+     * expect; this is probably due to the client being out-of-sync and sending
+     * variable changes for non-existing pids
+     * 
+     * @return true if successful, false if there was an inconsistency
+     */
+    private boolean handleVariables(Request request, Response response,
+            Callback callback, Application application2, Window window)
+            throws IOException, InvalidUIDLSecurityKeyException {
+        boolean success = true;
+        int contentLength = request.getContentLength();
+
+        if (contentLength > 0) {
+            String changes = readRequest(request);
+
+            // Manage bursts one by one
+            final String[] bursts = changes.split(VAR_BURST_SEPARATOR);
+
+            // Security: double cookie submission pattern unless disabled by
+            // property
+            if (!"true"
+                    .equals(application2
+                            .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))) {
+                if (bursts.length == 1 && "init".equals(bursts[0])) {
+                    // init request; don't handle any variables, key sent in
+                    // response.
+                    request.setAttribute(WRITE_SECURITY_TOKEN_FLAG, true);
+                    return true;
+                } else {
+                    // ApplicationServlet has stored the security token in the
+                    // session; check that it matched the one sent in the UIDL
+                    String sessId = (String) request.getSession().getAttribute(
+                            ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
+
+                    if (sessId == null || !sessId.equals(bursts[0])) {
+                        throw new InvalidUIDLSecurityKeyException(
+                                "Security key mismatch");
+                    }
+                }
+
+            }
+
+            for (int bi = 1; bi < bursts.length; bi++) {
+
+                // extract variables to two dim string array
+                final String[] tmp = bursts[bi].split(VAR_RECORD_SEPARATOR);
+                final String[][] variableRecords = new String[tmp.length][4];
+                for (int i = 0; i < tmp.length; i++) {
+                    variableRecords[i] = tmp[i].split(VAR_FIELD_SEPARATOR);
+                }
+
+                for (int i = 0; i < variableRecords.length; i++) {
+                    String[] variable = variableRecords[i];
+                    String[] nextVariable = null;
+                    if (i + 1 < variableRecords.length) {
+                        nextVariable = variableRecords[i + 1];
+                    }
+                    final VariableOwner owner = (VariableOwner) idPaintableMap
+                            .get(variable[VAR_PID]);
+                    if (owner != null && owner.isEnabled()) {
+                        Map m;
+                        if (nextVariable != null
+                                && variable[VAR_PID]
+                                        .equals(nextVariable[VAR_PID])) {
+                            // we have more than one value changes in row for
+                            // one variable owner, collect em in HashMap
+                            m = new HashMap();
+                            m.put(variable[VAR_NAME], convertVariableValue(
+                                    variable[VAR_TYPE].charAt(0),
+                                    variable[VAR_VALUE]));
+                        } else {
+                            // use optimized single value map
+                            m = new SingleValueMap(variable[VAR_NAME],
+                                    convertVariableValue(variable[VAR_TYPE]
+                                            .charAt(0), variable[VAR_VALUE]));
+                        }
+
+                        // collect following variable changes for this owner
+                        while (nextVariable != null
+                                && variable[VAR_PID]
+                                        .equals(nextVariable[VAR_PID])) {
+                            i++;
+                            variable = nextVariable;
+                            if (i + 1 < variableRecords.length) {
+                                nextVariable = variableRecords[i + 1];
+                            } else {
+                                nextVariable = null;
+                            }
+                            m.put(variable[VAR_NAME], convertVariableValue(
+                                    variable[VAR_TYPE].charAt(0),
+                                    variable[VAR_VALUE]));
+                        }
+                        try {
+                            owner.changeVariables(request, m);
+
+                            // Special-case of closing browser-level windows:
+                            // track browser-windows currently open in client
+                            if (owner instanceof Window
+                                    && ((Window) owner).getParent() == null) {
+                                final Boolean close = (Boolean) m.get("close");
+                                if (close != null && close.booleanValue()) {
+                                    closingWindowName = ((Window) owner)
+                                            .getName();
+                                }
+                            }
+                        } catch (Exception e) {
+                            handleChangeVariablesError(application2,
+                                    (Component) owner, e, m);
+                        }
+                    } else {
+
+                        // Handle special case where window-close is called
+                        // after the window has been removed from the
+                        // application or the application has closed
+                        if ("close".equals(variable[VAR_NAME])
+                                && "true".equals(variable[VAR_VALUE])) {
+                            // Silently ignore this
+                            continue;
+                        }
+
+                        // Ignore variable change
+                        String msg = "Warning: Ignoring variable change for ";
+                        if (owner != null) {
+                            msg += "disabled component " + owner.getClass();
+                            String caption = ((Component) owner).getCaption();
+                            if (caption != null) {
+                                msg += ", caption=" + caption;
+                            }
+                        } else {
+                            msg += "non-existent component, VAR_PID="
+                                    + variable[VAR_PID];
+                            success = false;
+                        }
+                        System.err.println(msg);
+                        continue;
+                    }
+                }
+
+                // In case that there were multiple bursts, we know that this is
+                // a special synchronous case for closing window. Thus we are
+                // not interested in sending any UIDL changes back to client.
+                // Still we must clear component tree between bursts to ensure
+                // that no removed components are updated. The painting after
+                // the last burst is handled normally by the calling method.
+                if (bi < bursts.length - 1) {
+
+                    // We will be discarding all changes
+                    final PrintWriter outWriter = new PrintWriter(
+                            new CharArrayWriter());
+
+                    paintAfterVariablechanges(request, response, callback,
+                            true, outWriter, window, false);
+
+                }
+
+            }
+        }
+        return success;
+    }
+
+    /**
+     * Reads the request data from the Request and returns it converted to an
+     * UTF-8 string.
+     * 
+     * @param request
+     * @return
+     * @throws IOException
+     */
+    private static String readRequest(Request request) throws IOException {
+
+        int requestLength = request.getContentLength();
+
+        byte[] buffer = new byte[requestLength];
+        InputStream inputStream = request.getInputStream();
+
+        int bytesRemaining = requestLength;
+        while (bytesRemaining > 0) {
+            int bytesToRead = Math.min(bytesRemaining, MAX_BUFFER_SIZE);
+            int bytesRead = inputStream.read(buffer, requestLength
+                    - bytesRemaining, bytesToRead);
+            if (bytesRead == -1) {
+                break;
+            }
+
+            bytesRemaining -= bytesRead;
+        }
+
+        String result = new String(buffer, "utf-8");
+
+        return result;
+    }
+
+    public class ErrorHandlerErrorEvent implements ErrorEvent, Serializable {
+        private final Throwable throwable;
+
+        public ErrorHandlerErrorEvent(Throwable throwable) {
+            this.throwable = throwable;
+        }
+
+        public Throwable getThrowable() {
+            return throwable;
+        }
+
+    }
+
+    private void handleChangeVariablesError(Application application,
+            Component owner, Exception e, Map m) {
+        boolean handled = false;
+        ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent(
+                owner, e, m);
+
+        if (owner instanceof AbstractField) {
+            try {
+                handled = ((AbstractField) owner).handleError(errorEvent);
+            } catch (Exception handlerException) {
+                /*
+                 * If there is an error in the component error handler we pass
+                 * the that error to the application error handler and continue
+                 * processing the actual error
+                 */
+                application.getErrorHandler().terminalError(
+                        new ErrorHandlerErrorEvent(handlerException));
+                handled = false;
+            }
+        }
+
+        if (!handled) {
+            application.getErrorHandler().terminalError(errorEvent);
+        }
+
+    }
+
+    private Object convertVariableValue(char variableType, String strValue) {
+        Object val = null;
+        switch (variableType) {
+        case 'a':
+            val = strValue.split(VAR_ARRAYITEM_SEPARATOR);
+            break;
+        case 's':
+            val = strValue;
+            break;
+        case 'i':
+            val = Integer.valueOf(strValue);
+            break;
+        case 'l':
+            val = Long.valueOf(strValue);
+            break;
+        case 'f':
+            val = Float.valueOf(strValue);
+            break;
+        case 'd':
+            val = Double.valueOf(strValue);
+            break;
+        case 'b':
+            val = Boolean.valueOf(strValue);
+            break;
+        case 'p':
+            val = idPaintableMap.get(strValue);
+            break;
+        }
+
+        return val;
+    }
+
+    private void printLocaleDeclarations(PrintWriter outWriter) {
+        /*
+         * ----------------------------- Sending Locale sensitive date
+         * -----------------------------
+         */
+
+        // Send locale informations to client
+        outWriter.print(", \"locales\":[");
+        for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
+
+            final Locale l = generateLocale(locales.get(pendingLocalesIndex));
+            // Locale name
+            outWriter.print("{\"name\":\"" + l.toString() + "\",");
+
+            /*
+             * Month names (both short and full)
+             */
+            final DateFormatSymbols dfs = new DateFormatSymbols(l);
+            final String[] short_months = dfs.getShortMonths();
+            final String[] months = dfs.getMonths();
+            outWriter.print("\"smn\":[\""
+                    + // ShortMonthNames
+                    short_months[0] + "\",\"" + short_months[1] + "\",\""
+                    + short_months[2] + "\",\"" + short_months[3] + "\",\""
+                    + short_months[4] + "\",\"" + short_months[5] + "\",\""
+                    + short_months[6] + "\",\"" + short_months[7] + "\",\""
+                    + short_months[8] + "\",\"" + short_months[9] + "\",\""
+                    + short_months[10] + "\",\"" + short_months[11] + "\""
+                    + "],");
+            outWriter.print("\"mn\":[\""
+                    + // MonthNames
+                    months[0] + "\",\"" + months[1] + "\",\"" + months[2]
+                    + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
+                    + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
+                    + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
+                    + months[10] + "\",\"" + months[11] + "\"" + "],");
+
+            /*
+             * Weekday names (both short and full)
+             */
+            final String[] short_days = dfs.getShortWeekdays();
+            final String[] days = dfs.getWeekdays();
+            outWriter.print("\"sdn\":[\""
+                    + // ShortDayNames
+                    short_days[1] + "\",\"" + short_days[2] + "\",\""
+                    + short_days[3] + "\",\"" + short_days[4] + "\",\""
+                    + short_days[5] + "\",\"" + short_days[6] + "\",\""
+                    + short_days[7] + "\"" + "],");
+            outWriter.print("\"dn\":[\""
+                    + // DayNames
+                    days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
+                    + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
+                    + days[7] + "\"" + "],");
+
+            /*
+             * First day of week (0 = sunday, 1 = monday)
+             */
+            final Calendar cal = new GregorianCalendar(l);
+            outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
+
+            /*
+             * Date formatting (MM/DD/YYYY etc.)
+             */
+
+            DateFormat dateFormat = DateFormat.getDateTimeInstance(
+                    DateFormat.SHORT, DateFormat.SHORT, l);
+            if (!(dateFormat instanceof SimpleDateFormat)) {
+                System.err
+                        .println("Unable to get default date pattern for locale "
+                                + l.toString());
+                dateFormat = new SimpleDateFormat();
+            }
+            final String df = ((SimpleDateFormat) dateFormat).toPattern();
+
+            int timeStart = df.indexOf("H");
+            if (timeStart < 0) {
+                timeStart = df.indexOf("h");
+            }
+            final int ampm_first = df.indexOf("a");
+            // E.g. in Korean locale AM/PM is before h:mm
+            // TODO should take that into consideration on client-side as well,
+            // now always h:mm a
+            if (ampm_first > 0 && ampm_first < timeStart) {
+                timeStart = ampm_first;
+            }
+            // Hebrew locale has time before the date
+            final boolean timeFirst = timeStart == 0;
+            String dateformat;
+            if (timeFirst) {
+                int dateStart = df.indexOf(' ');
+                if (ampm_first > dateStart) {
+                    dateStart = df.indexOf(' ', ampm_first);
+                }
+                dateformat = df.substring(dateStart + 1);
+            } else {
+                dateformat = df.substring(0, timeStart - 1);
+            }
+
+            outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
+
+            /*
+             * Time formatting (24 or 12 hour clock and AM/PM suffixes)
+             */
+            final String timeformat = df.substring(timeStart, df.length());
+            /*
+             * Doesn't return second or milliseconds.
+             * 
+             * We use timeformat to determine 12/24-hour clock
+             */
+            final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
+            // TODO there are other possibilities as well, like 'h' in french
+            // (ignore them, too complicated)
+            final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
+                    : ":";
+            // outWriter.print("\"tf\":\"" + timeformat + "\",");
+            outWriter.print("\"thc\":" + twelve_hour_clock + ",");
+            outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
+            if (twelve_hour_clock) {
+                final String[] ampm = dfs.getAmPmStrings();
+                outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
+                        + "\"]");
+            }
+            outWriter.print("}");
+            if (pendingLocalesIndex < locales.size() - 1) {
+                outWriter.print(",");
+            }
+        }
+        outWriter.print("]"); // Close locales
+    }
+
+    /**
+     * TODO New method - document me!
+     * 
+     * @param request
+     * @param callback
+     * @param application
+     * @param assumedWindow
+     * @return
+     */
+    protected Window doGetApplicationWindow(Request request, Callback callback,
+            Application application, Window assumedWindow) {
+
+        Window window = null;
+
+        // If the client knows which window to use, use it if possible
+        String windowClientRequestedName = request.getParameter("windowName");
+
+        if (assumedWindow != null
+                && application.getWindows().contains(assumedWindow)) {
+            windowClientRequestedName = assumedWindow.getName();
+        }
+        if (windowClientRequestedName != null) {
+            window = application.getWindow(windowClientRequestedName);
+            if (window != null) {
+                return window;
+            }
+        }
+
+        // If client does not know what window it wants
+        if (window == null && !request.isRunningInPortlet()) {
+            // This is only supported if the application is running inside a
+            // servlet
+
+            // Get the path from URL
+            String path = callback.getRequestPathInfo(request);
+            if (path != null && path.startsWith("/UIDL")) {
+                path = path.substring("/UIDL".length());
+            }
+
+            // If the path is specified, create name from it
+            if (path != null && path.length() > 0 && !path.equals("/")) {
+                String windowUrlName = null;
+                if (path.charAt(0) == '/') {
+                    path = path.substring(1);
+                }
+                final int index = path.indexOf('/');
+                if (index < 0) {
+                    windowUrlName = path;
+                    path = "";
+                } else {
+                    windowUrlName = path.substring(0, index);
+                    path = path.substring(index + 1);
+                }
+
+                window = application.getWindow(windowUrlName);
+            }
+        }
+
+        // By default, use mainwindow
+        if (window == null) {
+            window = application.getMainWindow();
+            // Return null if no main window was found
+            if (window == null) {
+                return null;
+            }
+        }
+
+        // If the requested window is already open, resolve conflict
+        if (currentlyOpenWindowsInClient.containsKey(window.getName())) {
+            String newWindowName = window.getName();
+            while (currentlyOpenWindowsInClient.containsKey(newWindowName)) {
+                newWindowName = window.getName() + "_"
+                        + ((int) (Math.random() * 100000000));
+            }
+
+            window = application.getWindow(newWindowName);
+
+            // If everything else fails, use main window even in case of
+            // conflicts
+            if (window == null) {
+                window = application.getMainWindow();
+            }
+        }
+
+        return window;
+    }
+
+    /**
+     * Ends the Application.
+     * 
+     * @param request
+     *            the request instance.
+     * @param response
+     *            the response to write to.
+     * @param application
+     *            the Application to end.
+     * @throws IOException
+     *             if the writing failed due to input/output error.
+     */
+    private void endApplication(Request request, Response response,
+            Application application) throws IOException {
+
+        String logoutUrl = application.getLogoutURL();
+        if (logoutUrl == null) {
+            logoutUrl = application.getURL().toString();
+        }
+        // clients JS app is still running, send a special json file to tell
+        // client that application has quit and where to point browser now
+        // Set the response type
+        final OutputStream out = response.getOutputStream();
+        response.setContentType("application/json; charset=UTF-8");
+        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+                new OutputStreamWriter(out, "UTF-8")));
+        outWriter.print("for(;;);[{");
+        outWriter.print("\"redirect\":{");
+        outWriter.write("\"url\":\"" + logoutUrl + "\"}}]");
+        outWriter.flush();
+        outWriter.close();
+        out.flush();
+    }
+
+    /**
+     * Gets the Paintable Id. If Paintable has debug id set it will be used
+     * prefixed with "PID_S". Otherwise a sequenced ID is created.
+     * 
+     * @param paintable
+     * @return the paintable Id.
+     */
+    public String getPaintableId(Paintable paintable) {
+
+        String id = paintableIdMap.get(paintable);
+        if (id == null) {
+            // use testing identifier as id if set
+            id = paintable.getDebugId();
+            if (id == null) {
+                id = "PID" + Integer.toString(idSequence++);
+            } else {
+                id = "PID_S" + id;
+            }
+            Paintable old = idPaintableMap.put(id, paintable);
+            if (old != null && old != paintable) {
+                /*
+                 * Two paintables have the same id. We still make sure the old
+                 * one is a component which is still attached to the
+                 * application. This is just a precaution and should not be
+                 * absolutely necessary.
+                 */
+
+                if (old instanceof Component
+                        && ((Component) old).getApplication() != null) {
+                    throw new IllegalStateException("Two paintables ("
+                            + paintable.getClass().getSimpleName() + ","
+                            + old.getClass().getSimpleName()
+                            + ") have been assigned the same id: "
+                            + paintable.getDebugId());
+                }
+            }
+            paintableIdMap.put(paintable, id);
+        }
+
+        return id;
+    }
+
+    public boolean hasPaintableId(Paintable paintable) {
+        return paintableIdMap.containsKey(paintable);
+    }
+
+    /**
+     * Returns dirty components which are in given window. Components in an
+     * invisible subtrees are omitted.
+     * 
+     * @param w
+     *            root window for which dirty components is to be fetched
+     * @return
+     */
+    private ArrayList<Paintable> getDirtyVisibleComponents(Window w) {
+        final ArrayList<Paintable> resultset = new ArrayList<Paintable>(
+                dirtyPaintabletSet);
+
+        // The following algorithm removes any components that would be painted
+        // as a direct descendant of other components from the dirty components
+        // list. The result is that each component should be painted exactly
+        // once and any unmodified components will be painted as "cached=true".
+
+        for (final Iterator<Paintable> i = dirtyPaintabletSet.iterator(); i
+                .hasNext();) {
+            final Paintable p = i.next();
+            if (p instanceof Component) {
+                final Component component = (Component) p;
+                if (component.getApplication() == null) {
+                    // component is detached after requestRepaint is called
+                    resultset.remove(p);
+                    i.remove();
+                } else {
+                    Window componentsRoot = component.getWindow();
+                    if (componentsRoot.getParent() != null) {
+                        // this is a subwindow
+                        componentsRoot = (Window) componentsRoot.getParent();
+                    }
+                    if (componentsRoot != w) {
+                        resultset.remove(p);
+                    } else if (component.getParent() != null
+                            && !component.getParent().isVisible()) {
+                        /*
+                         * Do not return components in an invisible subtree.
+                         * 
+                         * Components that are invisible in visible subree, must
+                         * be rendered (to let client know that they need to be
+                         * hidden).
+                         */
+                        resultset.remove(p);
+                    }
+                }
+            }
+        }
+
+        return resultset;
+    }
+
+    /**
+     * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent)
+     */
+    public void repaintRequested(RepaintRequestEvent event) {
+        final Paintable p = event.getPaintable();
+        if (!dirtyPaintabletSet.contains(p)) {
+            dirtyPaintabletSet.add(p);
+        }
+    }
+
+    /**
+     * 
+     * @param p
+     */
+    private void paintablePainted(Paintable p) {
+        dirtyPaintabletSet.remove(p);
+        p.requestRepaintRequests();
+    }
+
+    private final class SingleValueMap implements Map<Object, Object>,
+            Serializable {
+
+        private final String name;
+
+        private final Object value;
+
+        private SingleValueMap(String name, Object value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        public void clear() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean containsKey(Object key) {
+            if (name == null) {
+                return key == null;
+            }
+            return name.equals(key);
+        }
+
+        public boolean containsValue(Object v) {
+            if (value == null) {
+                return v == null;
+            }
+            return value.equals(v);
+        }
+
+        public Set entrySet() {
+            final Set s = new HashSet();
+            s.add(new Map.Entry() {
+
+                public Object getKey() {
+                    return name;
+                }
+
+                public Object getValue() {
+                    return value;
+                }
+
+                public Object setValue(Object value) {
+                    throw new UnsupportedOperationException();
+                }
+            });
+            return s;
+        }
+
+        public Object get(Object key) {
+            if (!name.equals(key)) {
+                return null;
+            }
+            return value;
+        }
+
+        public boolean isEmpty() {
+            return false;
+        }
+
+        public Set keySet() {
+            final Set s = new HashSet();
+            s.add(name);
+            return s;
+        }
+
+        public Object put(Object key, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void putAll(Map t) {
+            throw new UnsupportedOperationException();
+        }
+
+        public Object remove(Object key) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int size() {
+            return 1;
+        }
+
+        public Collection values() {
+            final LinkedList s = new LinkedList();
+            s.add(value);
+            return s;
+
+        }
+    }
+
+    /**
+     * Implementation of URIHandler.ErrorEvent interface.
+     */
+    public class URIHandlerErrorImpl implements URIHandler.ErrorEvent,
+            Serializable {
+
+        private final URIHandler owner;
+
+        private final Throwable throwable;
+
+        /**
+         * 
+         * @param owner
+         * @param throwable
+         */
+        private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
+            this.owner = owner;
+            this.throwable = throwable;
+        }
+
+        /**
+         * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable()
+         */
+        public Throwable getThrowable() {
+            return throwable;
+        }
+
+        /**
+         * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler()
+         */
+        public URIHandler getURIHandler() {
+            return owner;
+        }
+    }
+
+    public void requireLocale(String value) {
+        if (locales == null) {
+            locales = new ArrayList<String>();
+            locales.add(application.getLocale().toString());
+            pendingLocalesIndex = 0;
+        }
+        if (!locales.contains(value)) {
+            locales.add(value);
+        }
+    }
+
+    private Locale generateLocale(String value) {
+        final String[] temp = value.split("_");
+        if (temp.length == 1) {
+            return new Locale(temp[0]);
+        } else if (temp.length == 2) {
+            return new Locale(temp[0], temp[1]);
+        } else {
+            return new Locale(temp[0], temp[1], temp[2]);
+        }
+    }
+
+    /*
+     * Upload progress listener notifies upload component once when Jakarta
+     * FileUpload can determine content length. Used to detect files total size,
+     * uploads progress can be tracked inside upload.
+     */
+    private class UploadProgressListener implements ProgressListener,
+            Serializable {
+
+        Upload uploadComponent;
+
+        boolean updated = false;
+
+        public void setUpload(Upload u) {
+            uploadComponent = u;
+        }
+
+        public void update(long bytesRead, long contentLength, int items) {
+            if (!updated && uploadComponent != null) {
+                uploadComponent.setUploadSize(contentLength);
+                updated = true;
+            }
+        }
+    }
+
+    /**
+     * Helper method to test if a component contains another
+     * 
+     * @param parent
+     * @param child
+     */
+    private static boolean isChildOf(Component parent, Component child) {
+        Component p = child.getParent();
+        while (p != null) {
+            if (parent == p) {
+                return true;
+            }
+            p = p.getParent();
+        }
+        return false;
+    }
+
+    protected class InvalidUIDLSecurityKeyException extends
+            GeneralSecurityException {
+
+        InvalidUIDLSecurityKeyException(String message) {
+            super(message);
+        }
+
+    }
+
+    /**
+     * Handles the requested URI. An application can add handlers to do special
+     * processing, when a certain URI is requested. The handlers are invoked
+     * before any windows URIs are processed and if a DownloadStream is returned
+     * it is sent to the client.
+     * 
+     * @param application
+     *            the Application owning the URI.
+     * @param request
+     *            the request instance.
+     * @param response
+     *            the response to write to.
+     * @return boolean <code>true</code> if the request was handled and further
+     *         processing should be suppressed, <code>false</code> otherwise.
+     * @see com.vaadin.terminal.URIHandler
+     */
+    protected DownloadStream handleURI(Window window, Request request,
+            Response response, Callback callback) {
+
+        // FIXME Check what to do when running as a portlet that does not have
+        // URIs
+
+        String uri = callback.getRequestPathInfo(request);
+
+        // If no URI is available
+        if (uri == null) {
+            uri = "";
+        } else {
+            // Removes the leading /
+            while (uri.startsWith("/") && uri.length() > 0) {
+                uri = uri.substring(1);
+            }
+        }
+
+        // Handles the uri
+        try {
+            URL context = application.getURL();
+            if (window == application.getMainWindow()) {
+                DownloadStream stream = null;
+                /*
+                 * Application.handleURI run first. Handles possible
+                 * ApplicationResources.
+                 */
+                stream = application.handleURI(context, uri);
+                if (stream == null) {
+                    stream = window.handleURI(context, uri);
+                }
+                return stream;
+            } else {
+                // Resolve the prefix end inded
+                final int index = uri.indexOf('/');
+                if (index > 0) {
+                    String prefix = uri.substring(0, index);
+                    URL windowContext;
+                    windowContext = new URL(context, prefix + "/");
+                    final String windowUri = (uri.length() > prefix.length() + 1) ? uri
+                            .substring(prefix.length() + 1)
+                            : "";
+                    return window.handleURI(windowContext, windowUri);
+                } else {
+                    return null;
+                }
+            }
+
+        } catch (final Throwable t) {
+            application.getErrorHandler().terminalError(
+                    new URIHandlerErrorImpl(application, t));
+            return null;
+        }
+    }
+
+    private static HashMap<Class<? extends Paintable>, Integer> typeToKey = new HashMap<Class<? extends Paintable>, Integer>();
+    private static int nextTypeKey = 0;
+
+    static String getTagForType(Class<? extends Paintable> class1) {
+        synchronized (typeToKey) {
+            Integer object = typeToKey.get(class1);
+            if (object == null) {
+                object = nextTypeKey++;
+                typeToKey.put(class1, object);
+            }
+            return object.toString();
+        }
+    }
+
+    /**
+     * Helper class for terminal to keep track of data that client is expected
+     * to know.
+     * 
+     * TODO make customlayout templates (from theme) to be cached here.
+     */
+    class OpenWindowCache implements Serializable {
+
+        private Set<Object> res = new HashSet<Object>();
+
+        /**
+         * 
+         * @param paintable
+         * @return true if the given class was added to cache
+         */
+        boolean cache(Object object) {
+            return res.add(object);
+        }
+
+        public void clear() {
+            res.clear();
+        }
+
+    }
+}
index dd7de4b2b7e9187d3ba14c3b7f89e3cc466c2650..5a467c35f73c650e9017e3a1a4fb7259925db819 100644 (file)
@@ -4,68 +4,22 @@
 
 package com.vaadin.terminal.gwt.server;
 
-import java.io.BufferedWriter;
-import java.io.CharArrayWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.security.GeneralSecurityException;
-import java.text.DateFormat;
-import java.text.DateFormatSymbols;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
 
-import javax.portlet.ActionRequest;
-import javax.portlet.ActionResponse;
-import javax.portlet.PortletException;
-import javax.portlet.ResourceRequest;
-import javax.portlet.ResourceResponse;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 import com.vaadin.Application;
-import com.vaadin.Application.SystemMessages;
 import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
-import com.vaadin.external.org.apache.commons.fileupload.FileItemStream;
+import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
 import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
-import com.vaadin.external.org.apache.commons.fileupload.ProgressListener;
 import com.vaadin.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
 import com.vaadin.terminal.DownloadStream;
-import com.vaadin.terminal.PaintException;
-import com.vaadin.terminal.Paintable;
-import com.vaadin.terminal.URIHandler;
-import com.vaadin.terminal.UploadStream;
-import com.vaadin.terminal.VariableOwner;
-import com.vaadin.terminal.Paintable.RepaintRequestEvent;
-import com.vaadin.terminal.Terminal.ErrorEvent;
-import com.vaadin.terminal.gwt.client.ApplicationConnection;
-import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
-import com.vaadin.ui.AbstractField;
-import com.vaadin.ui.Component;
-import com.vaadin.ui.Upload;
 import com.vaadin.ui.Window;
-import com.vaadin.ui.Upload.UploadException;
 
 /**
  * Application manager processes changes and paints for single application
@@ -77,1774 +31,241 @@ import com.vaadin.ui.Upload.UploadException;
  * @since 5.0
  */
 @SuppressWarnings("serial")
-public class CommunicationManager implements Paintable.RepaintRequestListener,
-        Serializable {
-    
-    // FIXME Create an abstract class with custom Request/Response/Session interfaces, then create
-    // subclasses for servlets and portlets.
+public class CommunicationManager extends AbstractCommunicationManager {
 
-    private static String GET_PARAM_REPAINT_ALL = "repaintAll";
+    private static class HttpServletRequestWrapper implements Request {
 
-    // flag used in the request to indicate that the security token should be
-    // written to the response
-    private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
+        private final HttpServletRequest request;
 
-    /* Variable records indexes */
-    private static final int VAR_PID = 1;
-    private static final int VAR_NAME = 2;
-    private static final int VAR_TYPE = 3;
-    private static final int VAR_VALUE = 0;
-
-    private static final String VAR_RECORD_SEPARATOR = "\u001e";
-
-    private static final String VAR_FIELD_SEPARATOR = "\u001f";
-
-    public static final String VAR_BURST_SEPARATOR = "\u001d";
-
-    public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
-
-    private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>();
-
-    private static final int MAX_BUFFER_SIZE = 64 * 1024;
-
-    private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts";
-
-    private final ArrayList<Paintable> dirtyPaintabletSet = new ArrayList<Paintable>();
-
-    private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>();
-
-    private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>();
-
-    private int idSequence = 0;
-
-    private final Application application;
-
-    // Note that this is only accessed from synchronized block and
-    // thus should be thread-safe.
-    private String closingWindowName = null;
-
-    private List<String> locales;
-
-    private int pendingLocalesIndex;
-
-    private int timeoutInterval = -1;
-
-    /**
-     * @deprecated use {@link #CommunicationManager(Application)} instead
-     * @param application
-     * @param applicationServlet
-     */
-    @Deprecated
-    public CommunicationManager(Application application,
-            AbstractApplicationServlet applicationServlet) {
-        this.application = application;
-        requireLocale(application.getLocale().toString());
-    }
-
-    /**
-     * TODO New constructor - document me!
-     * 
-     * @param application
-     */
-    public CommunicationManager(Application application) {
-        this.application = application;
-        requireLocale(application.getLocale().toString());
-    }
-
-    /**
-     * TODO New method - document me!
-     * 
-     * @param reuqest
-     * @param response
-     * @throws IOException
-     * @throws FileUploadException
-     */
-    public void handleFileUpload(ActionRequest reuqest, ActionResponse response)
-            throws IOException, FileUploadException {
-        // FIXME Implement me!
-        throw new UnsupportedOperationException("Not implemented!");
-    }
-
-    /**
-     * Handles file upload request submitted via Upload component.
-     * 
-     * @param request
-     * @param response
-     * @throws IOException
-     * @throws FileUploadException
-     */
-    public void handleFileUpload(HttpServletRequest request,
-            HttpServletResponse response) throws IOException,
-            FileUploadException {
-        // Create a new file upload handler
-        final ServletFileUpload upload = new ServletFileUpload();
-
-        final UploadProgressListener pl = new UploadProgressListener();
-
-        upload.setProgressListener(pl);
-
-        // Parse the request
-        FileItemIterator iter;
-
-        try {
-            iter = upload.getItemIterator(request);
-            /*
-             * ATM this loop is run only once as we are uploading one file per
-             * request.
-             */
-            while (iter.hasNext()) {
-                final FileItemStream item = iter.next();
-                final String name = item.getFieldName();
-                final String filename = item.getName();
-                final String mimeType = item.getContentType();
-                final InputStream stream = item.openStream();
-                if (item.isFormField()) {
-                    // ignored, upload requests contains only files
-                } else {
-                    final String pid = name.split("_")[0];
-                    final Upload uploadComponent = (Upload) idPaintableMap
-                            .get(pid);
-                    if (uploadComponent == null) {
-                        throw new FileUploadException(
-                                "Upload component not found");
-                    }
-                    if (uploadComponent.isReadOnly()) {
-                        throw new FileUploadException(
-                                "Warning: ignored file upload because upload component is set as read-only");
-                    }
-                    synchronized (application) {
-                        // put upload component into receiving state
-                        uploadComponent.startUpload();
-                    }
-                    final UploadStream upstream = new UploadStream() {
-
-                        public String getContentName() {
-                            return filename;
-                        }
-
-                        public String getContentType() {
-                            return mimeType;
-                        }
-
-                        public InputStream getStream() {
-                            return stream;
-                        }
-
-                        public String getStreamName() {
-                            return "stream";
-                        }
-
-                    };
-
-                    // tell UploadProgressListener which component is receiving
-                    // file
-                    pl.setUpload(uploadComponent);
-
-                    try {
-                        uploadComponent.receiveUpload(upstream);
-                    } catch (UploadException e) {
-                        // error happened while receiving file. Handle the
-                        // error in the same manner as it would have happened in
-                        // variable change.
-                        synchronized (application) {
-                            handleChangeVariablesError(application,
-                                    uploadComponent, e,
-                                    new HashMap<String, Object>());
-                        }
-                    }
-                }
-            }
-        } catch (final FileUploadException e) {
-            throw e;
+        public HttpServletRequestWrapper(HttpServletRequest request) {
+            this.request = request;
         }
 
-        // Send short response to acknowledge client that request was done
-        response.setContentType("text/html");
-        final OutputStream out = response.getOutputStream();
-        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
-                new OutputStreamWriter(out, "UTF-8")));
-        outWriter.print("<html><body>download handled</body></html>");
-        outWriter.flush();
-        out.close();
-    }
-
-    /**
-     * TODO New method - document me!
-     * 
-     * @param request
-     * @param response
-     * @throws IOException
-     * @throws PortletException
-     * @throws InvalidUIDLSecurityKeyException
-     */
-    public void handleUidlRequest(ResourceRequest request,
-            ResourceResponse response) throws IOException, PortletException,
-            InvalidUIDLSecurityKeyException {
-        try {
-            doHandleUidlRequest(request, response, null);
-        } catch (ServletException e) {
-            throw new PortletException(e.getMessage(), e.getCause());
+        public Object getAttribute(String name) {
+            return request.getAttribute(name);
         }
-    }
-
-    /**
-     * Handles UIDL request
-     * 
-     * @param request
-     * @param response
-     * @throws IOException
-     * @throws ServletException
-     */
-    public void handleUidlRequest(HttpServletRequest request,
-            HttpServletResponse response,
-            AbstractApplicationServlet applicationServlet) throws IOException,
-            ServletException, InvalidUIDLSecurityKeyException {
-        doHandleUidlRequest(request, response, applicationServlet);
-    }
-
-    private void doHandleUidlRequest(Object request, Object response,
-            AbstractApplicationServlet applicationServlet) throws IOException,
-            ServletException, InvalidUIDLSecurityKeyException {
-
-        // repaint requested or session has timed out and new one is created
-        boolean repaintAll;
-        final OutputStream out;
 
-        if (request instanceof ResourceRequest) {
-            repaintAll = (((ResourceRequest) request)
-                    .getParameter(GET_PARAM_REPAINT_ALL) != null)
-                    || ((ResourceRequest) request).getPortletSession().isNew();
-            // Assume the response is a ResourceResponse
-            out = ((ResourceResponse) response).getPortletOutputStream();
-        } else {
-            repaintAll = (((HttpServletRequest) request)
-                    .getParameter(GET_PARAM_REPAINT_ALL) != null)
-                    || ((HttpServletRequest) request).getSession().isNew();
-            // Assume the response is a HttpServletResponse
-            out = ((HttpServletResponse) response).getOutputStream();
-        }
-        boolean analyzeLayouts = false;
-        if (repaintAll) {
-            // analyzing can be done only with repaintAll
-            if (request instanceof ResourceRequest) {
-                analyzeLayouts = (((ResourceRequest) request)
-                        .getParameter(GET_PARAM_ANALYZE_LAYOUTS) != null);
-            } else {
-                analyzeLayouts = (((HttpServletRequest) request)
-                        .getParameter(GET_PARAM_ANALYZE_LAYOUTS) != null);
-            }
+        public int getContentLength() {
+            return request.getContentLength();
         }
 
-        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
-                new OutputStreamWriter(out, "UTF-8")));
-
-        // The rest of the process is synchronized with the application
-        // in order to guarantee that no parallel variable handling is
-        // made
-        synchronized (application) {
-
-            // Finds the window within the application
-            Window window = null;
-            if (application.isRunning()) {
-                window = doGetApplicationWindow(request, applicationServlet,
-                        application, null);
-                // Returns if no window found
-                if (window == null) {
-                    // This should not happen, no windows exists but
-                    // application is still open.
-                    if (request instanceof ResourceRequest) {
-                        System.err
-                                .println("Warning, could not get window for application with resource ID "
-                                        + ((ResourceRequest) request)
-                                                .getResourceID());
-                    } else {
-                        System.err
-                                .println("Warning, could not get window for application with request URI "
-                                        + ((HttpServletRequest) request)
-                                                .getRequestURI());
-                    }
-                    return;
-                }
-            } else {
-                // application has been closed
-                endApplication(request, response, application);
-                return;
-            }
-
-            // Change all variables based on request parameters
-            if (!handleVariables(request, response, applicationServlet,
-                    application, window)) {
-
-                // var inconsistency; the client is probably out-of-sync
-                SystemMessages ci = null;
-                try {
-                    Method m = application.getClass().getMethod(
-                            "getSystemMessages", (Class[]) null);
-                    ci = (Application.SystemMessages) m.invoke(null,
-                            (Object[]) null);
-                } catch (Exception e2) {
-                    // FIXME: Handle exception
-                    // Not critical, but something is still wrong; print
-                    // stacktrace
-                    e2.printStackTrace();
-                }
-                if (ci != null) {
-                    String msg = ci.getOutOfSyncMessage();
-                    String cap = ci.getOutOfSyncCaption();
-                    if (msg != null || cap != null) {
-                        if (request instanceof HttpServletRequest) {
-                            applicationServlet.criticalNotification(
-                                    (HttpServletRequest) request,
-                                    (HttpServletResponse) response, cap, msg,
-                                    null, ci.getOutOfSyncURL());
-                        }
-                        // FIXME What about Portlets?
-                        // will reload page after this
-                        return;
-                    }
-                }
-                // No message to show, let's just repaint all.
-                repaintAll = true;
-
-            }
-
-            paintAfterVariablechanges(request, response, applicationServlet,
-                    repaintAll, outWriter, window, analyzeLayouts);
-
-            if (closingWindowName != null) {
-                currentlyOpenWindowsInClient.remove(closingWindowName);
-                closingWindowName = null;
-            }
+        public InputStream getInputStream() throws IOException {
+            return request.getInputStream();
         }
 
-        out.flush();
-        out.close();
-    }
-
-    private void paintAfterVariablechanges(Object request, Object response,
-            AbstractApplicationServlet applicationServlet, boolean repaintAll,
-            final PrintWriter outWriter, Window window, boolean analyzeLayouts)
-            throws IOException, ServletException, PaintException {
-
-        if (repaintAll) {
-            // If repaint is requested, clean all ids in this root window
-            for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it
-                    .hasNext();) {
-                final Component c = (Component) idPaintableMap.get(it.next());
-                if (isChildOf(window, c)) {
-                    it.remove();
-                    paintableIdMap.remove(c);
-                }
-            }
-            // clean WindowCache
-            OpenWindowCache openWindowCache = currentlyOpenWindowsInClient
-                    .get(window.getName());
-            if (openWindowCache != null) {
-                openWindowCache.clear();
-            }
+        public String getParameter(String name) {
+            return request.getParameter(name);
         }
 
-        // Removes application if it has stopped during variable changes
-        if (!application.isRunning()) {
-            endApplication(request, response, application);
-            return;
+        public String getRequestID() {
+            return "RequestURL:" + request.getRequestURI();
         }
 
-        // Sets the response type
-        if (response instanceof ResourceResponse) {
-            ((ResourceResponse) response)
-                    .setContentType("application/json; charset=UTF-8");
-        } else {
-            ((HttpServletResponse) response)
-                    .setContentType("application/json; charset=UTF-8");
+        public Session getSession() {
+            return new HttpSessionWrapper(request.getSession());
         }
-        // some dirt to prevent cross site scripting
-        outWriter.print("for(;;);[{");
 
-        // security key
-        Object writeSecurityTokenFlag;
-        if (request instanceof ResourceRequest) {
-            writeSecurityTokenFlag = ((ResourceRequest) request)
-                    .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
-        } else {
-            writeSecurityTokenFlag = ((HttpServletRequest) request)
-                    .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
+        public Object getWrappedRequest() {
+            return request;
         }
 
-        if (writeSecurityTokenFlag != null) {
-            String seckey;
-            if (request instanceof ResourceRequest) {
-                seckey = (String) ((ResourceRequest) request)
-                        .getPortletSession().getAttribute(
-                                ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
-            } else {
-                seckey = (String) ((HttpServletRequest) request).getSession()
-                        .getAttribute(
-                                ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
-            }
-            if (seckey == null) {
-                seckey = "" + (int) (Math.random() * 1000000);
-                if (request instanceof ResourceRequest) {
-                    ((ResourceRequest) request)
-                            .getPortletSession()
-                            .setAttribute(
-                                    ApplicationConnection.UIDL_SECURITY_TOKEN_ID,
-                                    seckey);
-                } else {
-                    ((HttpServletRequest) request).getSession().setAttribute(
-                            ApplicationConnection.UIDL_SECURITY_TOKEN_ID,
-                            seckey);
-                }
-            }
-            outWriter.print("\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID
-                    + "\":\"");
-            outWriter.print(seckey);
-            outWriter.print("\",");
+        public boolean isRunningInPortlet() {
+            return false;
         }
 
-        outWriter.print("\"changes\":[");
-
-        ArrayList<Paintable> paintables = null;
-
-        // If the browser-window has been closed - we do not need to paint it at
-        // all
-        if (!window.getName().equals(closingWindowName)) {
-
-            List<InvalidLayout> invalidComponentRelativeSizes = null;
-
-            // re-get window - may have been changed
-            Window newWindow = doGetApplicationWindow(request,
-                    applicationServlet, application, window);
-            if (newWindow != window) {
-                window = newWindow;
-                repaintAll = true;
-            }
-
-            JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
-                    !repaintAll);
-            OpenWindowCache windowCache = currentlyOpenWindowsInClient
-                    .get(window.getName());
-            if (windowCache == null) {
-                windowCache = new OpenWindowCache();
-                currentlyOpenWindowsInClient.put(window.getName(), windowCache);
-            }
-
-            // Paints components
-            if (repaintAll) {
-                paintables = new ArrayList<Paintable>();
-                paintables.add(window);
-
-                // Reset sent locales
-                locales = null;
-                requireLocale(application.getLocale().toString());
-
-            } else {
-                // remove detached components from paintableIdMap so they
-                // can be GC'ed
-                for (Iterator<Paintable> it = paintableIdMap.keySet()
-                        .iterator(); it.hasNext();) {
-                    Component p = (Component) it.next();
-                    if (p.getApplication() == null) {
-                        idPaintableMap.remove(paintableIdMap.get(p));
-                        it.remove();
-                        dirtyPaintabletSet.remove(p);
-                        p.removeListener(this);
-                    }
-                }
-                paintables = getDirtyVisibleComponents(window);
-            }
-            if (paintables != null) {
-
-                // We need to avoid painting children before parent.
-                // This is ensured by ordering list by depth in component
-                // tree
-                Collections.sort(paintables, new Comparator<Paintable>() {
-                    public int compare(Paintable o1, Paintable o2) {
-                        Component c1 = (Component) o1;
-                        Component c2 = (Component) o2;
-                        int d1 = 0;
-                        while (c1.getParent() != null) {
-                            d1++;
-                            c1 = c1.getParent();
-                        }
-                        int d2 = 0;
-                        while (c2.getParent() != null) {
-                            d2++;
-                            c2 = c2.getParent();
-                        }
-                        if (d1 < d2) {
-                            return -1;
-                        }
-                        if (d1 > d2) {
-                            return 1;
-                        }
-                        return 0;
-                    }
-                });
-
-                for (final Iterator i = paintables.iterator(); i.hasNext();) {
-                    final Paintable p = (Paintable) i.next();
-
-                    // TODO CLEAN
-                    if (p instanceof Window) {
-                        final Window w = (Window) p;
-                        if (w.getTerminal() == null) {
-                            w.setTerminal(application.getMainWindow()
-                                    .getTerminal());
-                        }
-                    }
-                    /*
-                     * This does not seem to happen in tk5, but remember this
-                     * case: else if (p instanceof Component) { if (((Component)
-                     * p).getParent() == null || ((Component)
-                     * p).getApplication() == null) { // Component requested
-                     * repaint, but is no // longer attached: skip
-                     * paintablePainted(p); continue; } }
-                     */
-
-                    // TODO we may still get changes that have been
-                    // rendered already (changes with only cached flag)
-                    if (paintTarget.needsToBePainted(p)) {
-                        paintTarget.startTag("change");
-                        paintTarget.addAttribute("format", "uidl");
-                        final String pid = getPaintableId(p);
-                        paintTarget.addAttribute("pid", pid);
-
-                        p.paint(paintTarget);
-
-                        paintTarget.endTag("change");
-                    }
-                    paintablePainted(p);
-
-                    if (analyzeLayouts) {
-                        Window w = (Window) p;
-                        invalidComponentRelativeSizes = ComponentSizeValidator
-                                .validateComponentRelativeSizes(w.getContent(),
-                                        null, null);
-
-                        // Also check any existing subwindows
-                        if (w.getChildWindows() != null) {
-                            for (Window subWindow : w.getChildWindows()) {
-                                invalidComponentRelativeSizes = ComponentSizeValidator
-                                        .validateComponentRelativeSizes(
-                                                subWindow.getContent(),
-                                                invalidComponentRelativeSizes,
-                                                null);
-                            }
-                        }
-                    }
-                }
-            }
-
-            paintTarget.close();
-            outWriter.print("]"); // close changes
-
-            outWriter.print(", \"meta\" : {");
-            boolean metaOpen = false;
-
-            if (repaintAll) {
-                metaOpen = true;
-                outWriter.write("\"repaintAll\":true");
-                if (analyzeLayouts) {
-                    outWriter.write(", \"invalidLayouts\":");
-                    outWriter.write("[");
-                    if (invalidComponentRelativeSizes != null) {
-                        boolean first = true;
-                        for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
-                            if (!first) {
-                                outWriter.write(",");
-                            } else {
-                                first = false;
-                            }
-                            invalidLayout.reportErrors(outWriter, this,
-                                    System.err);
-                        }
-                    }
-                    outWriter.write("]");
-                }
-            }
-
-            SystemMessages ci = null;
-            try {
-                Method m = application.getClass().getMethod(
-                        "getSystemMessages", (Class[]) null);
-                ci = (Application.SystemMessages) m.invoke(null,
-                        (Object[]) null);
-            } catch (NoSuchMethodException e1) {
-                e1.printStackTrace();
-            } catch (IllegalArgumentException e) {
-                e.printStackTrace();
-            } catch (IllegalAccessException e) {
-                e.printStackTrace();
-            } catch (InvocationTargetException e) {
-                e.printStackTrace();
-            }
-
-            // meta instruction for client to enable auto-forward to
-            // sessionExpiredURL after timer expires.
-            if (ci != null && ci.getSessionExpiredMessage() == null
-                    && ci.getSessionExpiredCaption() == null
-                    && ci.isSessionExpiredNotificationEnabled()) {
-                int newTimeoutInterval;
-                if (request instanceof ResourceRequest) {
-                    newTimeoutInterval = ((ResourceRequest) request)
-                            .getPortletSession().getMaxInactiveInterval();
-                } else {
-                    newTimeoutInterval = ((HttpServletRequest) request)
-                            .getSession().getMaxInactiveInterval();
-                }
-                if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
-                    String escapedURL = ci.getSessionExpiredURL() == null ? ""
-                            : ci.getSessionExpiredURL().replace("/", "\\/");
-                    if (metaOpen) {
-                        outWriter.write(",");
-                    }
-                    outWriter.write("\"timedRedirect\":{\"interval\":"
-                            + (newTimeoutInterval + 15) + ",\"url\":\""
-                            + escapedURL + "\"}");
-                    metaOpen = true;
-                }
-                timeoutInterval = newTimeoutInterval;
-            }
-
-            outWriter.print("}, \"resources\" : {");
-
-            // Precache custom layouts
-            String themeName = window.getTheme();
-            String requestThemeName;
-            if (request instanceof ResourceRequest) {
-                requestThemeName = ((ResourceRequest) request)
-                        .getParameter("theme");
-            } else {
-                requestThemeName = ((HttpServletRequest) request)
-                        .getParameter("theme");
-            }
-            if (requestThemeName != null) {
-                themeName = requestThemeName;
-            }
-            if (themeName == null) {
-                themeName = AbstractApplicationServlet.getDefaultTheme();
-            }
-
-            // TODO We should only precache the layouts that are not
-            // cached already (plagiate from usedPaintableTypes)
-            int resourceIndex = 0;
-            for (final Iterator i = paintTarget.getUsedResources().iterator(); i
-                    .hasNext();) {
-                final String resource = (String) i.next();
-                InputStream is = null;
-                try {
-                    is = applicationServlet
-                            .getServletContext()
-                            .getResourceAsStream(
-                                    "/"
-                                            + ApplicationServlet.THEME_DIRECTORY_PATH
-                                            + themeName + "/" + resource);
-                } catch (final Exception e) {
-                    // FIXME: Handle exception
-                    e.printStackTrace();
-                }
-                if (is != null) {
-
-                    outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
-                            + resource + "\" : ");
-                    final StringBuffer layout = new StringBuffer();
-
-                    try {
-                        final InputStreamReader r = new InputStreamReader(is,
-                                "UTF-8");
-                        final char[] buffer = new char[20000];
-                        int charsRead = 0;
-                        while ((charsRead = r.read(buffer)) > 0) {
-                            layout.append(buffer, 0, charsRead);
-                        }
-                        r.close();
-                    } catch (final java.io.IOException e) {
-                        // FIXME: Handle exception
-                        if (request instanceof ResourceRequest) {
-                            System.err.println("Resource transfer failed:  "
-                                    + ((ResourceRequest) request)
-                                            .getResourceID() + ". ("
-                                    + e.getMessage() + ")");
-                        } else {
-                            System.err.println("Resource transfer failed:  "
-                                    + ((HttpServletRequest) request)
-                                            .getRequestURI() + ". ("
-                                    + e.getMessage() + ")");
-                        }
-                    }
-                    outWriter.print("\""
-                            + JsonPaintTarget.escapeJSON(layout.toString())
-                            + "\"");
-                } else {
-                    // FIXME: Handle exception
-                    System.err.println("CustomLayout " + "/"
-                            + ApplicationServlet.THEME_DIRECTORY_PATH
-                            + themeName + "/" + resource + " not found!");
-                }
-            }
-            outWriter.print("}");
-
-            Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget
-                    .getUsedPaintableTypes();
-            boolean typeMappingsOpen = false;
-            for (Class<? extends Paintable> class1 : usedPaintableTypes) {
-                if (windowCache.cache(class1)) {
-                    // client does not know the mapping key for this type, send
-                    // mapping to client
-                    if (!typeMappingsOpen) {
-                        typeMappingsOpen = true;
-                        outWriter.print(", \"typeMappings\" : { ");
-                    } else {
-                        outWriter.print(" , ");
-                    }
-                    String canonicalName = class1.getCanonicalName();
-                    outWriter.print("\"");
-                    outWriter.print(canonicalName);
-                    outWriter.print("\" : ");
-                    outWriter.print(getTagForType(class1));
-                }
-            }
-            if (typeMappingsOpen) {
-                outWriter.print(" }");
-            }
-
-            printLocaleDeclarations(outWriter);
-
-            outWriter.print("}]");
+        public void setAttribute(String name, Object o) {
+            request.setAttribute(name, o);
         }
-        outWriter.flush();
-        outWriter.close();
-
     }
 
-    /**
-     * If this method returns false, something was submitted that we did not
-     * expect; this is probably due to the client being out-of-sync and sending
-     * variable changes for non-existing pids
-     * 
-     * @param request
-     * @param application2
-     * @return true if successful, false if there was an inconsistency
-     * @throws IOException
-     */
-    private boolean handleVariables(Object request, Object response,
-            AbstractApplicationServlet applicationServlet,
-            Application application2, Window window) throws IOException,
-            InvalidUIDLSecurityKeyException {
-        boolean success = true;
-        int contentLength;
-        if (request instanceof ResourceRequest) {
-            contentLength = ((ResourceRequest) request).getContentLength();
-        } else {
-            contentLength = ((HttpServletRequest) request).getContentLength();
-        }
-
-        if (contentLength > 0) {
-            String changes = readRequest(request);
-
-            // Manage bursts one by one
-            final String[] bursts = changes.split(VAR_BURST_SEPARATOR);
-
-            // Security: double cookie submission pattern unless disabled by
-            // property
-            if (!"true"
-                    .equals(application2
-                            .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))) {
-                if (bursts.length == 1 && "init".equals(bursts[0])) {
-                    // init request; don't handle any variables, key sent in
-                    // response.
-                    if (request instanceof ResourceRequest) {
-                        ((ResourceRequest) request).setAttribute(
-                                WRITE_SECURITY_TOKEN_FLAG, true);
-                    } else {
-                        ((HttpServletRequest) request).setAttribute(
-                                WRITE_SECURITY_TOKEN_FLAG, true);
-                    }
-                    return true;
-                } else {
-                    // ApplicationServlet has stored the security token in the
-                    // session; check that it matched the one sent in the UIDL
-                    String sessId;
-                    if (request instanceof ResourceRequest) {
-                        sessId = (String) ((ResourceRequest) request)
-                                .getPortletSession()
-                                .getAttribute(
-                                        ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
-                    } else {
-                        sessId = (String) ((HttpServletRequest) request)
-                                .getSession()
-                                .getAttribute(
-                                        ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
-                    }
-                    if (sessId == null || !sessId.equals(bursts[0])) {
-                        throw new InvalidUIDLSecurityKeyException(
-                                "Security key mismatch");
-                    }
-                }
+    private static class HttpServletResponseWrapper implements Response {
 
-            }
+        private final HttpServletResponse response;
 
-            for (int bi = 1; bi < bursts.length; bi++) {
-
-                // extract variables to two dim string array
-                final String[] tmp = bursts[bi].split(VAR_RECORD_SEPARATOR);
-                final String[][] variableRecords = new String[tmp.length][4];
-                for (int i = 0; i < tmp.length; i++) {
-                    variableRecords[i] = tmp[i].split(VAR_FIELD_SEPARATOR);
-                }
-
-                for (int i = 0; i < variableRecords.length; i++) {
-                    String[] variable = variableRecords[i];
-                    String[] nextVariable = null;
-                    if (i + 1 < variableRecords.length) {
-                        nextVariable = variableRecords[i + 1];
-                    }
-                    final VariableOwner owner = (VariableOwner) idPaintableMap
-                            .get(variable[VAR_PID]);
-                    if (owner != null && owner.isEnabled()) {
-                        Map m;
-                        if (nextVariable != null
-                                && variable[VAR_PID]
-                                        .equals(nextVariable[VAR_PID])) {
-                            // we have more than one value changes in row for
-                            // one variable owner, collect em in HashMap
-                            m = new HashMap();
-                            m.put(variable[VAR_NAME], convertVariableValue(
-                                    variable[VAR_TYPE].charAt(0),
-                                    variable[VAR_VALUE]));
-                        } else {
-                            // use optimized single value map
-                            m = new SingleValueMap(variable[VAR_NAME],
-                                    convertVariableValue(variable[VAR_TYPE]
-                                            .charAt(0), variable[VAR_VALUE]));
-                        }
-
-                        // collect following variable changes for this owner
-                        while (nextVariable != null
-                                && variable[VAR_PID]
-                                        .equals(nextVariable[VAR_PID])) {
-                            i++;
-                            variable = nextVariable;
-                            if (i + 1 < variableRecords.length) {
-                                nextVariable = variableRecords[i + 1];
-                            } else {
-                                nextVariable = null;
-                            }
-                            m.put(variable[VAR_NAME], convertVariableValue(
-                                    variable[VAR_TYPE].charAt(0),
-                                    variable[VAR_VALUE]));
-                        }
-                        try {
-                            owner.changeVariables(request, m);
-
-                            // Special-case of closing browser-level windows:
-                            // track browser-windows currently open in client
-                            if (owner instanceof Window
-                                    && ((Window) owner).getParent() == null) {
-                                final Boolean close = (Boolean) m.get("close");
-                                if (close != null && close.booleanValue()) {
-                                    closingWindowName = ((Window) owner)
-                                            .getName();
-                                }
-                            }
-                        } catch (Exception e) {
-                            handleChangeVariablesError(application2,
-                                    (Component) owner, e, m);
-                        }
-                    } else {
-
-                        // Handle special case where window-close is called
-                        // after the window has been removed from the
-                        // application or the application has closed
-                        if ("close".equals(variable[VAR_NAME])
-                                && "true".equals(variable[VAR_VALUE])) {
-                            // Silently ignore this
-                            continue;
-                        }
-
-                        // Ignore variable change
-                        String msg = "Warning: Ignoring variable change for ";
-                        if (owner != null) {
-                            msg += "disabled component " + owner.getClass();
-                            String caption = ((Component) owner).getCaption();
-                            if (caption != null) {
-                                msg += ", caption=" + caption;
-                            }
-                        } else {
-                            msg += "non-existent component, VAR_PID="
-                                    + variable[VAR_PID];
-                            success = false;
-                        }
-                        System.err.println(msg);
-                        continue;
-                    }
-                }
-
-                // In case that there were multiple bursts, we know that this is
-                // a special synchronous case for closing window. Thus we are
-                // not interested in sending any UIDL changes back to client.
-                // Still we must clear component tree between bursts to ensure
-                // that no removed components are updated. The painting after
-                // the last burst is handled normally by the calling method.
-                if (bi < bursts.length - 1) {
-
-                    // We will be discarding all changes
-                    final PrintWriter outWriter = new PrintWriter(
-                            new CharArrayWriter());
-                    try {
-                        paintAfterVariablechanges(request, response,
-                                applicationServlet, true, outWriter, window,
-                                false);
-
-                    } catch (ServletException e) {
-                        // We will ignore all servlet exceptions
-                    }
-                }
-
-            }
+        public HttpServletResponseWrapper(HttpServletResponse response) {
+            this.response = response;
         }
-        return success;
-    }
-
-    /**
-     * Reads the request data from the HttpServletRequest or ResourceRequest and
-     * returns it converted to an UTF-8 string.
-     * 
-     * @param request
-     * @return
-     * @throws IOException
-     */
-    private static String readRequest(Object request) throws IOException {
 
-        int requestLength;
-
-        if (request instanceof ResourceRequest) {
-            requestLength = ((ResourceRequest) request).getContentLength();
-        } else { // Will throw ClassCastException if invalid request type
-            requestLength = ((HttpServletRequest) request).getContentLength();
+        public OutputStream getOutputStream() throws IOException {
+            return response.getOutputStream();
         }
-        byte[] buffer = new byte[requestLength];
-        InputStream inputStream;
 
-        if (request instanceof ResourceRequest) {
-            inputStream = ((ResourceRequest) request).getPortletInputStream();
-        } else {
-            inputStream = ((HttpServletRequest) request).getInputStream();
+        public Object getWrappedResponse() {
+            return response;
         }
 
-        int bytesRemaining = requestLength;
-        while (bytesRemaining > 0) {
-            int bytesToRead = Math.min(bytesRemaining, MAX_BUFFER_SIZE);
-            int bytesRead = inputStream.read(buffer, requestLength
-                    - bytesRemaining, bytesToRead);
-            if (bytesRead == -1) {
-                break;
-            }
-
-            bytesRemaining -= bytesRead;
+        public void setContentType(String type) {
+            response.setContentType(type);
         }
 
-        String result = new String(buffer, "utf-8");
-
-        return result;
     }
 
-    public class ErrorHandlerErrorEvent implements ErrorEvent, Serializable {
-        private final Throwable throwable;
+    private static class HttpSessionWrapper implements Session {
 
-        public ErrorHandlerErrorEvent(Throwable throwable) {
-            this.throwable = throwable;
-        }
+        private final HttpSession session;
 
-        public Throwable getThrowable() {
-            return throwable;
+        public HttpSessionWrapper(HttpSession session) {
+            this.session = session;
         }
 
-    }
-
-    private void handleChangeVariablesError(Application application,
-            Component owner, Exception e, Map m) {
-        boolean handled = false;
-        ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent(
-                owner, e, m);
-
-        if (owner instanceof AbstractField) {
-            try {
-                handled = ((AbstractField) owner).handleError(errorEvent);
-            } catch (Exception handlerException) {
-                /*
-                 * If there is an error in the component error handler we pass
-                 * the that error to the application error handler and continue
-                 * processing the actual error
-                 */
-                application.getErrorHandler().terminalError(
-                        new ErrorHandlerErrorEvent(handlerException));
-                handled = false;
-            }
+        public Object getAttribute(String name) {
+            return session.getAttribute(name);
         }
 
-        if (!handled) {
-            application.getErrorHandler().terminalError(errorEvent);
+        public int getMaxInactiveInterval() {
+            return session.getMaxInactiveInterval();
         }
 
-    }
-
-    private Object convertVariableValue(char variableType, String strValue) {
-        Object val = null;
-        switch (variableType) {
-        case 'a':
-            val = strValue.split(VAR_ARRAYITEM_SEPARATOR);
-            break;
-        case 's':
-            val = strValue;
-            break;
-        case 'i':
-            val = Integer.valueOf(strValue);
-            break;
-        case 'l':
-            val = Long.valueOf(strValue);
-            break;
-        case 'f':
-            val = Float.valueOf(strValue);
-            break;
-        case 'd':
-            val = Double.valueOf(strValue);
-            break;
-        case 'b':
-            val = Boolean.valueOf(strValue);
-            break;
-        case 'p':
-            val = idPaintableMap.get(strValue);
-            break;
+        public Object getWrappedSession() {
+            return session;
         }
 
-        return val;
-    }
-
-    private void printLocaleDeclarations(PrintWriter outWriter) {
-        /*
-         * ----------------------------- Sending Locale sensitive date
-         * -----------------------------
-         */
-
-        // Send locale informations to client
-        outWriter.print(", \"locales\":[");
-        for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
-
-            final Locale l = generateLocale(locales.get(pendingLocalesIndex));
-            // Locale name
-            outWriter.print("{\"name\":\"" + l.toString() + "\",");
-
-            /*
-             * Month names (both short and full)
-             */
-            final DateFormatSymbols dfs = new DateFormatSymbols(l);
-            final String[] short_months = dfs.getShortMonths();
-            final String[] months = dfs.getMonths();
-            outWriter.print("\"smn\":[\""
-                    + // ShortMonthNames
-                    short_months[0] + "\",\"" + short_months[1] + "\",\""
-                    + short_months[2] + "\",\"" + short_months[3] + "\",\""
-                    + short_months[4] + "\",\"" + short_months[5] + "\",\""
-                    + short_months[6] + "\",\"" + short_months[7] + "\",\""
-                    + short_months[8] + "\",\"" + short_months[9] + "\",\""
-                    + short_months[10] + "\",\"" + short_months[11] + "\""
-                    + "],");
-            outWriter.print("\"mn\":[\""
-                    + // MonthNames
-                    months[0] + "\",\"" + months[1] + "\",\"" + months[2]
-                    + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
-                    + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
-                    + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
-                    + months[10] + "\",\"" + months[11] + "\"" + "],");
-
-            /*
-             * Weekday names (both short and full)
-             */
-            final String[] short_days = dfs.getShortWeekdays();
-            final String[] days = dfs.getWeekdays();
-            outWriter.print("\"sdn\":[\""
-                    + // ShortDayNames
-                    short_days[1] + "\",\"" + short_days[2] + "\",\""
-                    + short_days[3] + "\",\"" + short_days[4] + "\",\""
-                    + short_days[5] + "\",\"" + short_days[6] + "\",\""
-                    + short_days[7] + "\"" + "],");
-            outWriter.print("\"dn\":[\""
-                    + // DayNames
-                    days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
-                    + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
-                    + days[7] + "\"" + "],");
-
-            /*
-             * First day of week (0 = sunday, 1 = monday)
-             */
-            final Calendar cal = new GregorianCalendar(l);
-            outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
-
-            /*
-             * Date formatting (MM/DD/YYYY etc.)
-             */
-
-            DateFormat dateFormat = DateFormat.getDateTimeInstance(
-                    DateFormat.SHORT, DateFormat.SHORT, l);
-            if (!(dateFormat instanceof SimpleDateFormat)) {
-                System.err
-                        .println("Unable to get default date pattern for locale "
-                                + l.toString());
-                dateFormat = new SimpleDateFormat();
-            }
-            final String df = ((SimpleDateFormat) dateFormat).toPattern();
-
-            int timeStart = df.indexOf("H");
-            if (timeStart < 0) {
-                timeStart = df.indexOf("h");
-            }
-            final int ampm_first = df.indexOf("a");
-            // E.g. in Korean locale AM/PM is before h:mm
-            // TODO should take that into consideration on client-side as well,
-            // now always h:mm a
-            if (ampm_first > 0 && ampm_first < timeStart) {
-                timeStart = ampm_first;
-            }
-            // Hebrew locale has time before the date
-            final boolean timeFirst = timeStart == 0;
-            String dateformat;
-            if (timeFirst) {
-                int dateStart = df.indexOf(' ');
-                if (ampm_first > dateStart) {
-                    dateStart = df.indexOf(' ', ampm_first);
-                }
-                dateformat = df.substring(dateStart + 1);
-            } else {
-                dateformat = df.substring(0, timeStart - 1);
-            }
-
-            outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
-
-            /*
-             * Time formatting (24 or 12 hour clock and AM/PM suffixes)
-             */
-            final String timeformat = df.substring(timeStart, df.length());
-            /*
-             * Doesn't return second or milliseconds.
-             * 
-             * We use timeformat to determine 12/24-hour clock
-             */
-            final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
-            // TODO there are other possibilities as well, like 'h' in french
-            // (ignore them, too complicated)
-            final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
-                    : ":";
-            // outWriter.print("\"tf\":\"" + timeformat + "\",");
-            outWriter.print("\"thc\":" + twelve_hour_clock + ",");
-            outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
-            if (twelve_hour_clock) {
-                final String[] ampm = dfs.getAmPmStrings();
-                outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
-                        + "\"]");
-            }
-            outWriter.print("}");
-            if (pendingLocalesIndex < locales.size() - 1) {
-                outWriter.print(",");
-            }
+        public boolean isNew() {
+            return session.isNew();
         }
-        outWriter.print("]"); // Close locales
-    }
 
-    /**
-     * Gets the existing application or create a new one. Get a window within an
-     * application based on the requested URI.
-     * 
-     * @param request
-     *            the HTTP Request.
-     * @param application
-     *            the Application to query for window.
-     * @param assumedWindow
-     *            if the window has been already resolved once, this parameter
-     *            must contain the window.
-     * @return Window mathing the given URI or null if not found.
-     * @throws ServletException
-     *             if an exception has occurred that interferes with the
-     *             servlet's normal operation.
-     */
-    Window getApplicationWindow(HttpServletRequest request,
-            AbstractApplicationServlet applicationServlet,
-            Application application, Window assumedWindow)
-            throws ServletException {
-        return doGetApplicationWindow((Object) request, applicationServlet,
-                application, assumedWindow);
-    }
-
-    /**
-     * TODO New method - document me!
-     * 
-     * @param request
-     * @param application
-     * @param assumedWindow
-     * @return
-     * @throws PortletException
-     */
-    Window getApplicationWindow(ResourceRequest request,
-            Application application, Window assumedWindow)
-            throws PortletException {
-        try {
-            return doGetApplicationWindow((Object) request, null, application,
-                    assumedWindow);
-        } catch (ServletException e) {
-            throw new PortletException(e.getMessage(), e.getCause());
+        public void setAttribute(String name, Object o) {
+            session.setAttribute(name, o);
         }
-    }
 
-    private Window doGetApplicationWindow(Object request,
-            AbstractApplicationServlet applicationServlet,
-            Application application, Window assumedWindow)
-            throws ServletException {
+    }
 
-        Window window = null;
+    private static class AbstractApplicationServletWrapper implements Callback {
 
-        // If the client knows which window to use, use it if possible
-        String windowClientRequestedName;
-        if (request instanceof ResourceRequest) {
-            windowClientRequestedName = ((ResourceRequest) request)
-                    .getParameter("windowName");
-        } else {
-            windowClientRequestedName = ((HttpServletRequest) request)
-                    .getParameter("windowName");
-        }
+        private final AbstractApplicationServlet servlet;
 
-        if (assumedWindow != null
-                && application.getWindows().contains(assumedWindow)) {
-            windowClientRequestedName = assumedWindow.getName();
-        }
-        if (windowClientRequestedName != null) {
-            window = application.getWindow(windowClientRequestedName);
-            if (window != null) {
-                return window;
-            }
+        public AbstractApplicationServletWrapper(
+                AbstractApplicationServlet servlet) {
+            this.servlet = servlet;
         }
 
-        // If client does not know what window it wants
-        if (window == null && request instanceof HttpServletRequest) {
-            // This is only supported if the application is running inside a
-            // servlet
-
-            // Get the path from URL
-            String path = applicationServlet
-                    .getRequestPathInfo((HttpServletRequest) request);
-            if (path != null && path.startsWith("/UIDL")) {
-                path = path.substring("/UIDL".length());
-            }
-
-            // If the path is specified, create name from it
-            if (path != null && path.length() > 0 && !path.equals("/")) {
-                String windowUrlName = null;
-                if (path.charAt(0) == '/') {
-                    path = path.substring(1);
-                }
-                final int index = path.indexOf('/');
-                if (index < 0) {
-                    windowUrlName = path;
-                    path = "";
-                } else {
-                    windowUrlName = path.substring(0, index);
-                    path = path.substring(index + 1);
-                }
-
-                window = application.getWindow(windowUrlName);
-            }
+        public void criticalNotification(Request request, Response response,
+                String cap, String msg, String details, String outOfSyncURL)
+                throws IOException {
+            servlet.criticalNotification((HttpServletRequest) request
+                    .getWrappedRequest(), (HttpServletResponse) response
+                    .getWrappedResponse(), cap, msg, details, outOfSyncURL);
         }
 
-        // By default, use mainwindow
-        if (window == null) {
-            window = application.getMainWindow();
-            // Return null if no main window was found
-            if (window == null) {
-                return null;
-            }
+        public String getRequestPathInfo(Request request) {
+            return servlet.getRequestPathInfo((HttpServletRequest) request
+                    .getWrappedRequest());
         }
 
-        // If the requested window is already open, resolve conflict
-        if (currentlyOpenWindowsInClient.containsKey(window.getName())) {
-            String newWindowName = window.getName();
-            while (currentlyOpenWindowsInClient.containsKey(newWindowName)) {
-                newWindowName = window.getName() + "_"
-                        + ((int) (Math.random() * 100000000));
-            }
-
-            window = application.getWindow(newWindowName);
-
-            // If everything else fails, use main window even in case of
-            // conflicts
-            if (window == null) {
-                window = application.getMainWindow();
-            }
+        public InputStream getThemeResourceAsStream(String themeName,
+                String resource) throws IOException {
+            return servlet.getServletContext().getResourceAsStream(
+                    "/" + AbstractApplicationServlet.THEME_DIRECTORY_PATH
+                            + themeName + "/" + resource);
         }
 
-        return window;
     }
 
     /**
-     * Ends the Application.
-     * 
-     * @param request
-     *            the HTTP/Resource request instance.
-     * @param response
-     *            the HTTP/Resource response to write to.
+     * @deprecated use {@link #CommunicationManager(Application)} instead
      * @param application
-     *            the Application to end.
-     * @throws IOException
-     *             if the writing failed due to input/output error.
+     * @param applicationServlet
      */
-    private void endApplication(Object request, Object response,
-            Application application) throws IOException {
-
-        String logoutUrl = application.getLogoutURL();
-        if (logoutUrl == null) {
-            logoutUrl = application.getURL().toString();
-        }
-        // clients JS app is still running, send a special json file to tell
-        // client that application has quit and where to point browser now
-        // Set the response type
-        final OutputStream out;
-        if (response instanceof ResourceResponse) {
-            ((ResourceResponse) response)
-                    .setContentType("application/json; charset=UTF-8");
-            out = ((ResourceResponse) response).getPortletOutputStream();
-        } else { // Will throw ClassCastException if invalid response
-            ((HttpServletResponse) response)
-                    .setContentType("application/json; charset=UTF-8");
-            out = ((HttpServletResponse) response).getOutputStream();
-        }
-        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
-                new OutputStreamWriter(out, "UTF-8")));
-        outWriter.print("for(;;);[{");
-        outWriter.print("\"redirect\":{");
-        outWriter.write("\"url\":\"" + logoutUrl + "\"}}]");
-        outWriter.flush();
-        outWriter.close();
-        out.flush();
+    @Deprecated
+    public CommunicationManager(Application application,
+            AbstractApplicationServlet applicationServlet) {
+        super(application);
     }
 
     /**
-     * Gets the Paintable Id. If Paintable has debug id set it will be used
-     * prefixed with "PID_S". Otherwise a sequenced ID is created.
+     * TODO New constructor - document me!
      * 
-     * @param paintable
-     * @return the paintable Id.
+     * @param application
      */
-    public String getPaintableId(Paintable paintable) {
-
-        String id = paintableIdMap.get(paintable);
-        if (id == null) {
-            // use testing identifier as id if set
-            id = paintable.getDebugId();
-            if (id == null) {
-                id = "PID" + Integer.toString(idSequence++);
-            } else {
-                id = "PID_S" + id;
-            }
-            Paintable old = idPaintableMap.put(id, paintable);
-            if (old != null && old != paintable) {
-                /*
-                 * Two paintables have the same id. We still make sure the old
-                 * one is a component which is still attached to the
-                 * application. This is just a precaution and should not be
-                 * absolutely necessary.
-                 */
-
-                if (old instanceof Component
-                        && ((Component) old).getApplication() != null) {
-                    throw new IllegalStateException("Two paintables ("
-                            + paintable.getClass().getSimpleName() + ","
-                            + old.getClass().getSimpleName()
-                            + ") have been assigned the same id: "
-                            + paintable.getDebugId());
-                }
-            }
-            paintableIdMap.put(paintable, id);
-        }
-
-        return id;
-    }
-
-    public boolean hasPaintableId(Paintable paintable) {
-        return paintableIdMap.containsKey(paintable);
+    public CommunicationManager(Application application) {
+        super(application);
     }
 
-    /**
-     * Returns dirty components which are in given window. Components in an
-     * invisible subtrees are omitted.
-     * 
-     * @param w
-     *            root window for which dirty components is to be fetched
-     * @return
-     */
-    private ArrayList<Paintable> getDirtyVisibleComponents(Window w) {
-        final ArrayList<Paintable> resultset = new ArrayList<Paintable>(
-                dirtyPaintabletSet);
-
-        // The following algorithm removes any components that would be painted
-        // as a direct descendant of other components from the dirty components
-        // list. The result is that each component should be painted exactly
-        // once and any unmodified components will be painted as "cached=true".
-
-        for (final Iterator<Paintable> i = dirtyPaintabletSet.iterator(); i
-                .hasNext();) {
-            final Paintable p = i.next();
-            if (p instanceof Component) {
-                final Component component = (Component) p;
-                if (component.getApplication() == null) {
-                    // component is detached after requestRepaint is called
-                    resultset.remove(p);
-                    i.remove();
-                } else {
-                    Window componentsRoot = component.getWindow();
-                    if (componentsRoot.getParent() != null) {
-                        // this is a subwindow
-                        componentsRoot = (Window) componentsRoot.getParent();
-                    }
-                    if (componentsRoot != w) {
-                        resultset.remove(p);
-                    } else if (component.getParent() != null
-                            && !component.getParent().isVisible()) {
-                        /*
-                         * Do not return components in an invisible subtree.
-                         * 
-                         * Components that are invisible in visible subree, must
-                         * be rendered (to let client know that they need to be
-                         * hidden).
-                         */
-                        resultset.remove(p);
-                    }
-                }
-            }
-        }
-
-        return resultset;
+    @Override
+    protected FileUpload createFileUpload() {
+        return new ServletFileUpload();
     }
 
-    /**
-     * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent)
-     */
-    public void repaintRequested(RepaintRequestEvent event) {
-        final Paintable p = event.getPaintable();
-        if (!dirtyPaintabletSet.contains(p)) {
-            dirtyPaintabletSet.add(p);
-        }
+    @Override
+    protected FileItemIterator getItemIterator(FileUpload upload,
+            Request request) throws IOException, FileUploadException {
+        return ((ServletFileUpload) upload)
+                .getItemIterator((HttpServletRequest) request
+                        .getWrappedRequest());
     }
 
     /**
+     * Handles file upload request submitted via Upload component.
      * 
-     * @param p
+     * @param request
+     * @param response
+     * @throws IOException
+     * @throws FileUploadException
      */
-    private void paintablePainted(Paintable p) {
-        dirtyPaintabletSet.remove(p);
-        p.requestRepaintRequests();
-    }
-
-    private final class SingleValueMap implements Map<Object, Object>,
-            Serializable {
-
-        private final String name;
-
-        private final Object value;
-
-        private SingleValueMap(String name, Object value) {
-            this.name = name;
-            this.value = value;
-        }
-
-        public void clear() {
-            throw new UnsupportedOperationException();
-        }
-
-        public boolean containsKey(Object key) {
-            if (name == null) {
-                return key == null;
-            }
-            return name.equals(key);
-        }
-
-        public boolean containsValue(Object v) {
-            if (value == null) {
-                return v == null;
-            }
-            return value.equals(v);
-        }
-
-        public Set entrySet() {
-            final Set s = new HashSet();
-            s.add(new Map.Entry() {
-
-                public Object getKey() {
-                    return name;
-                }
-
-                public Object getValue() {
-                    return value;
-                }
-
-                public Object setValue(Object value) {
-                    throw new UnsupportedOperationException();
-                }
-            });
-            return s;
-        }
-
-        public Object get(Object key) {
-            if (!name.equals(key)) {
-                return null;
-            }
-            return value;
-        }
-
-        public boolean isEmpty() {
-            return false;
-        }
-
-        public Set keySet() {
-            final Set s = new HashSet();
-            s.add(name);
-            return s;
-        }
-
-        public Object put(Object key, Object value) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void putAll(Map t) {
-            throw new UnsupportedOperationException();
-        }
-
-        public Object remove(Object key) {
-            throw new UnsupportedOperationException();
-        }
-
-        public int size() {
-            return 1;
-        }
-
-        public Collection values() {
-            final LinkedList s = new LinkedList();
-            s.add(value);
-            return s;
-
-        }
+    public void handleFileUpload(HttpServletRequest request,
+            HttpServletResponse response) throws IOException,
+            FileUploadException {
+        doHandleFileUpload(new HttpServletRequestWrapper(request),
+                new HttpServletResponseWrapper(response));
     }
 
     /**
-     * Implementation of URIHandler.ErrorEvent interface.
-     */
-    public class URIHandlerErrorImpl implements URIHandler.ErrorEvent,
-            Serializable {
-
-        private final URIHandler owner;
-
-        private final Throwable throwable;
-
-        /**
-         * 
-         * @param owner
-         * @param throwable
-         */
-        private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
-            this.owner = owner;
-            this.throwable = throwable;
-        }
-
-        /**
-         * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable()
-         */
-        public Throwable getThrowable() {
-            return throwable;
-        }
-
-        /**
-         * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler()
-         */
-        public URIHandler getURIHandler() {
-            return owner;
-        }
-    }
-
-    public void requireLocale(String value) {
-        if (locales == null) {
-            locales = new ArrayList<String>();
-            locales.add(application.getLocale().toString());
-            pendingLocalesIndex = 0;
-        }
-        if (!locales.contains(value)) {
-            locales.add(value);
-        }
-    }
-
-    private Locale generateLocale(String value) {
-        final String[] temp = value.split("_");
-        if (temp.length == 1) {
-            return new Locale(temp[0]);
-        } else if (temp.length == 2) {
-            return new Locale(temp[0], temp[1]);
-        } else {
-            return new Locale(temp[0], temp[1], temp[2]);
-        }
-    }
-
-    /*
-     * Upload progress listener notifies upload component once when Jakarta
-     * FileUpload can determine content length. Used to detect files total size,
-     * uploads progress can be tracked inside upload.
+     * Handles UIDL request
+     * 
+     * @param request
+     * @param response
+     * @throws IOException
+     * @throws ServletException
      */
-    private class UploadProgressListener implements ProgressListener,
-            Serializable {
-
-        Upload uploadComponent;
-
-        boolean updated = false;
-
-        public void setUpload(Upload u) {
-            uploadComponent = u;
-        }
-
-        public void update(long bytesRead, long contentLength, int items) {
-            if (!updated && uploadComponent != null) {
-                uploadComponent.setUploadSize(contentLength);
-                updated = true;
-            }
-        }
+    public void handleUidlRequest(HttpServletRequest request,
+            HttpServletResponse response,
+            AbstractApplicationServlet applicationServlet) throws IOException,
+            ServletException, InvalidUIDLSecurityKeyException {
+        doHandleUidlRequest(new HttpServletRequestWrapper(request),
+                new HttpServletResponseWrapper(response),
+                new AbstractApplicationServletWrapper(applicationServlet));
     }
 
     /**
-     * Helper method to test if a component contains another
+     * Gets the existing application or create a new one. Get a window within an
+     * application based on the requested URI.
      * 
-     * @param parent
-     * @param child
+     * @param request
+     *            the HTTP Request.
+     * @param application
+     *            the Application to query for window.
+     * @param assumedWindow
+     *            if the window has been already resolved once, this parameter
+     *            must contain the window.
+     * @return Window mathing the given URI or null if not found.
+     * @throws ServletException
+     *             if an exception has occurred that interferes with the
+     *             servlet's normal operation.
      */
-    private static boolean isChildOf(Component parent, Component child) {
-        Component p = child.getParent();
-        while (p != null) {
-            if (parent == p) {
-                return true;
-            }
-            p = p.getParent();
-        }
-        return false;
-    }
-
-    private class InvalidUIDLSecurityKeyException extends
-            GeneralSecurityException {
-
-        InvalidUIDLSecurityKeyException(String message) {
-            super(message);
-        }
-
+    Window getApplicationWindow(HttpServletRequest request,
+            AbstractApplicationServlet applicationServlet,
+            Application application, Window assumedWindow)
+            throws ServletException {
+        return doGetApplicationWindow(new HttpServletRequestWrapper(request),
+                new AbstractApplicationServletWrapper(applicationServlet),
+                application, assumedWindow);
     }
 
     /**
-     * Handles the requested URI. An application can add handlers to do special
-     * processing, when a certain URI is requested. The handlers are invoked
-     * before any windows URIs are processed and if a DownloadStream is returned
-     * it is sent to the client.
+     * TODO Document me!
      * 
-     * @param application
-     *            the Application owning the URI.
+     * @param window
      * @param request
-     *            the HTTP request instance.
      * @param response
-     *            the HTTP response to write to.
-     * @return boolean <code>true</code> if the request was handled and further
-     *         processing should be suppressed, <code>false</code> otherwise.
-     * @see com.vaadin.terminal.URIHandler
+     * @param applicationServlet
+     * @return
      */
     DownloadStream handleURI(Window window, HttpServletRequest request,
             HttpServletResponse response,
             AbstractApplicationServlet applicationServlet) {
-
-        String uri = applicationServlet.getRequestPathInfo(request);
-
-        // If no URI is available
-        if (uri == null) {
-            uri = "";
-        } else {
-            // Removes the leading /
-            while (uri.startsWith("/") && uri.length() > 0) {
-                uri = uri.substring(1);
-            }
-        }
-
-        // Handles the uri
-        try {
-            URL context = application.getURL();
-            if (window == application.getMainWindow()) {
-                DownloadStream stream = null;
-                /*
-                 * Application.handleURI run first. Handles possible
-                 * ApplicationResources.
-                 */
-                stream = application.handleURI(context, uri);
-                if (stream == null) {
-                    stream = window.handleURI(context, uri);
-                }
-                return stream;
-            } else {
-                // Resolve the prefix end inded
-                final int index = uri.indexOf('/');
-                if (index > 0) {
-                    String prefix = uri.substring(0, index);
-                    URL windowContext;
-                    windowContext = new URL(context, prefix + "/");
-                    final String windowUri = (uri.length() > prefix.length() + 1) ? uri
-                            .substring(prefix.length() + 1)
-                            : "";
-                    return window.handleURI(windowContext, windowUri);
-                } else {
-                    return null;
-                }
-            }
-
-        } catch (final Throwable t) {
-            application.getErrorHandler().terminalError(
-                    new URIHandlerErrorImpl(application, t));
-            return null;
-        }
+        return handleURI(window, new HttpServletRequestWrapper(request),
+                new HttpServletResponseWrapper(response),
+                new AbstractApplicationServletWrapper(applicationServlet));
     }
 
-    private static HashMap<Class<? extends Paintable>, Integer> typeToKey = new HashMap<Class<? extends Paintable>, Integer>();
-    private static int nextTypeKey = 0;
-
-    static String getTagForType(Class<? extends Paintable> class1) {
-        synchronized (typeToKey) {
-            Integer object = typeToKey.get(class1);
-            if (object == null) {
-                object = nextTypeKey++;
-                typeToKey.put(class1, object);
-            }
-            return object.toString();
-        }
-    }
-
-    /**
-     * Helper class for terminal to keep track of data that client is expected
-     * to know.
-     * 
-     * TODO make customlayout templates (from theme) to be cached here.
-     */
-    class OpenWindowCache implements Serializable {
-
-        private Set<Object> res = new HashSet<Object>();
-
-        /**
-         * 
-         * @param paintable
-         * @return true if the given class was added to cache
-         */
-        boolean cache(Object object) {
-            return res.add(object);
-        }
-
-        public void clear() {
-            res.clear();
-        }
-
-    }
 }
index 7f33605c8d69314b40fa9169b63c7484ebdff1cd..2997a76d208487ce5039d06d7d0aa19d1dd82539 100644 (file)
@@ -173,7 +173,7 @@ public class ComponentSizeValidator implements Serializable {
 
         @SuppressWarnings("deprecation")
         public void reportErrors(PrintWriter clientJSON,
-                CommunicationManager communicationManager,
+                AbstractCommunicationManager communicationManager,
                 PrintStream serverErrorStream) {
             clientJSON.write("{");
 
index 9497b26b00629ae64e1a5c5ad3417d0ac97836c2..d5c4ffcdd2fa9b6395181df64e4d9cd1136aa7cb 100644 (file)
@@ -52,7 +52,7 @@ public class JsonPaintTarget implements PaintTarget {
 
     private boolean closed = false;
 
-    private final CommunicationManager manager;
+    private final AbstractCommunicationManager manager;
 
     private int changes = 0;
 
@@ -82,7 +82,7 @@ public class JsonPaintTarget implements PaintTarget {
      * @throws PaintException
      *             if the paint operation failed.
      */
-    public JsonPaintTarget(CommunicationManager manager, PrintWriter outWriter,
+    public JsonPaintTarget(AbstractCommunicationManager manager, PrintWriter outWriter,
             boolean cachingRequired) throws PaintException {
 
         this.manager = manager;
index 765cccd2b61174e722521074d873c14bddee0169..e9d2f2abab6a32ddecd4a02be7d96afe0e02b6ae 100644 (file)
@@ -36,7 +36,7 @@ public class PortletApplicationContext2 implements ApplicationContext,
 
     protected WebBrowser browser = new WebBrowser();
 
-    protected HashMap<Application, CommunicationManager> applicationToAjaxAppMgrMap = new HashMap<Application, CommunicationManager>();
+    protected HashMap<Application, PortletCommunicationManager> applicationToAjaxAppMgrMap = new HashMap<Application, PortletCommunicationManager>();
 
     public void addTransactionListener(TransactionListener listener) {
         if (listeners == null) {
@@ -71,14 +71,14 @@ public class PortletApplicationContext2 implements ApplicationContext,
         }
     }
 
-    protected CommunicationManager getApplicationManager(
+    protected PortletCommunicationManager getApplicationManager(
             Application application) {
-        CommunicationManager mgr = applicationToAjaxAppMgrMap
+        PortletCommunicationManager mgr = applicationToAjaxAppMgrMap
                 .get(application);
 
         if (mgr == null) {
             // Creates a new manager
-            mgr = new CommunicationManager(application);
+            mgr = new PortletCommunicationManager(application);
             applicationToAjaxAppMgrMap.put(application, mgr);
         }
         return mgr;
diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
new file mode 100644 (file)
index 0000000..5d36373
--- /dev/null
@@ -0,0 +1,190 @@
+package com.vaadin.terminal.gwt.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.ClientDataRequest;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.PortletSession;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
+
+import com.vaadin.Application;
+import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
+import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
+import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
+import com.vaadin.external.org.apache.commons.fileupload.portlet.PortletFileUpload;
+
+/**
+ * TODO document me!
+ * 
+ * @author peholmst
+ * 
+ */
+@SuppressWarnings("serial")
+public class PortletCommunicationManager extends AbstractCommunicationManager {
+
+    private static class PortletRequestWrapper implements Request {
+
+        private final PortletRequest request;
+
+        public PortletRequestWrapper(PortletRequest request) {
+            this.request = request;
+        }
+
+        public Object getAttribute(String name) {
+            return request.getAttribute(name);
+        }
+
+        public int getContentLength() {
+            return ((ClientDataRequest) request).getContentLength();
+        }
+
+        public InputStream getInputStream() throws IOException {
+            return ((ClientDataRequest) request).getPortletInputStream();
+        }
+
+        public String getParameter(String name) {
+            return request.getParameter(name);
+        }
+
+        public String getRequestID() {
+            return "WindowID:" + request.getWindowID();
+        }
+
+        public Session getSession() {
+            return new PortletSessionWrapper(request.getPortletSession());
+        }
+
+        public Object getWrappedRequest() {
+            return request;
+        }
+
+        public boolean isRunningInPortlet() {
+            return true;
+        }
+
+        public void setAttribute(String name, Object o) {
+            request.setAttribute(name, o);
+        }
+
+    }
+
+    private static class PortletResponseWrapper implements Response {
+
+        private final PortletResponse response;
+
+        public PortletResponseWrapper(PortletResponse response) {
+            this.response = response;
+        }
+
+        public OutputStream getOutputStream() throws IOException {
+            return ((MimeResponse) response).getPortletOutputStream();
+        }
+
+        public Object getWrappedResponse() {
+            return response;
+        }
+
+        public void setContentType(String type) {
+            ((MimeResponse) response).setContentType(type);
+        }
+    }
+
+    private static class PortletSessionWrapper implements Session {
+
+        private final PortletSession session;
+
+        public PortletSessionWrapper(PortletSession session) {
+            this.session = session;
+        }
+
+        public Object getAttribute(String name) {
+            return session.getAttribute(name);
+        }
+
+        public int getMaxInactiveInterval() {
+            return session.getMaxInactiveInterval();
+        }
+
+        public Object getWrappedSession() {
+            return session;
+        }
+
+        public boolean isNew() {
+            return session.isNew();
+        }
+
+        public void setAttribute(String name, Object o) {
+            session.setAttribute(name, o);
+        }
+
+    }
+
+    private static class AbstractApplicationPortletWrapper implements Callback {
+
+        private final AbstractApplicationPortlet portlet;
+
+        public AbstractApplicationPortletWrapper(
+                AbstractApplicationPortlet portlet) {
+            this.portlet = portlet;
+        }
+
+        public void criticalNotification(Request request, Response response,
+                String cap, String msg, String details, String outOfSyncURL)
+                throws IOException {
+            // TODO Implement me!
+        }
+
+        public String getRequestPathInfo(Request request) {
+            // We do not use paths in portlet mode
+            throw new UnsupportedOperationException(
+                    "PathInfo not available when running in Portlet mode");
+        }
+
+        public InputStream getThemeResourceAsStream(String themeName,
+                String resource) throws IOException {
+            return portlet.getPortletContext().getResourceAsStream(
+                    "/" + AbstractApplicationPortlet.THEME_DIRECTORY_PATH
+                            + themeName + "/" + resource);
+        }
+
+    }
+
+    public PortletCommunicationManager(Application application) {
+        super(application);
+    }
+
+    @Override
+    protected FileUpload createFileUpload() {
+        return new PortletFileUpload();
+    }
+
+    @Override
+    protected FileItemIterator getItemIterator(FileUpload upload,
+            Request request) throws IOException, FileUploadException {
+        return ((PortletFileUpload) upload)
+                .getItemIterator((ActionRequest) request.getWrappedRequest());
+    }
+
+    public void handleFileUpload(ActionRequest request, ActionResponse response)
+            throws FileUploadException, IOException {
+        doHandleFileUpload(new PortletRequestWrapper(request),
+                new PortletResponseWrapper(response));
+    }
+
+    public void handleUidlRequest(ResourceRequest request,
+            ResourceResponse response,
+            AbstractApplicationPortlet applicationPortlet)
+            throws InvalidUIDLSecurityKeyException, IOException {
+        doHandleUidlRequest(new PortletRequestWrapper(request),
+                new PortletResponseWrapper(response),
+                new AbstractApplicationPortletWrapper(applicationPortlet));
+    }
+
+}
index 09d34c30b567f19145892dfbdb09197fa66fd83d..9f8e6c99fd1b490b741e5256179ae96ece77f05a 100644 (file)
@@ -77,7 +77,11 @@ public class WebBrowser implements Terminal {
         }
     }
 
-    // TODO: This method depends on the Portlet API.
+    /*
+     * TODO: This method depends on the Portlet API, although this should not be
+     * a problem as the portlet API will only be required if the method is
+     * invoked.
+     */
     void updateBrowserProperties(PortletRequest request) {
         locale = request.getLocale();
         address = null;