summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2013-03-25 18:47:09 +0200
committerVaadin Code Review <review@vaadin.com>2013-03-27 12:52:33 +0000
commit515f6444406cdcfaab6daaef82c72b6e898537f7 (patch)
treef928c174903d360bad4732c27b55e00d64c79094 /server
parent4570ac9d812bfe80cf6c44c6f9b2a0eeb5101d4f (diff)
downloadvaadin-framework-515f6444406cdcfaab6daaef82c72b6e898537f7.tar.gz
vaadin-framework-515f6444406cdcfaab6daaef82c72b6e898537f7.zip
Refactor server-side communications (#11192, #7891)
* Move UIDL writing from AbstractCommunicationManager to UidlWriter * Move request handling from ACM to RequestHandlers * Move server RPC message handling to ServerRpcHandler * Move portlet event notifications to PortletListenerNotifier (a RequestHandler) * Communication handlers reside in c.v.server.communication Change-Id: I087e923dbdf88c6b3fcaafbdb7f685d9d3dad0c1
Diffstat (limited to 'server')
-rw-r--r--server/src/com/vaadin/server/AbstractCommunicationManager.java2468
-rw-r--r--server/src/com/vaadin/server/CommunicationManager.java2
-rw-r--r--server/src/com/vaadin/server/ComponentSizeValidator.java4
-rw-r--r--server/src/com/vaadin/server/DragAndDropService.java6
-rw-r--r--server/src/com/vaadin/server/JsonPaintTarget.java8
-rw-r--r--server/src/com/vaadin/server/PortletCommunicationManager.java2
-rw-r--r--server/src/com/vaadin/server/ServerRpcManager.java2
-rw-r--r--server/src/com/vaadin/server/ServletPortletHelper.java21
-rw-r--r--server/src/com/vaadin/server/VaadinPortlet.java78
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java60
-rw-r--r--server/src/com/vaadin/server/WebBrowser.java2
-rw-r--r--server/src/com/vaadin/server/communication/AbstractStreamingEvent.java (renamed from server/src/com/vaadin/server/AbstractStreamingEvent.java)3
-rw-r--r--server/src/com/vaadin/server/communication/ClientRpcWriter.java141
-rw-r--r--server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java81
-rw-r--r--server/src/com/vaadin/server/communication/ConnectorTypeWriter.java73
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java586
-rw-r--r--server/src/com/vaadin/server/communication/HeartbeatHandler.java73
-rw-r--r--server/src/com/vaadin/server/communication/LegacyUidlWriter.java118
-rw-r--r--server/src/com/vaadin/server/communication/LocaleWriter.java204
-rw-r--r--server/src/com/vaadin/server/communication/MetadataWriter.java137
-rw-r--r--server/src/com/vaadin/server/communication/PortletListenerNotifier.java89
-rw-r--r--server/src/com/vaadin/server/communication/PublishedFileHandler.java145
-rw-r--r--server/src/com/vaadin/server/communication/ResourceWriter.java111
-rw-r--r--server/src/com/vaadin/server/communication/ServerRpcHandler.java494
-rw-r--r--server/src/com/vaadin/server/communication/SharedStateWriter.java75
-rw-r--r--server/src/com/vaadin/server/communication/StreamingEndEventImpl.java (renamed from server/src/com/vaadin/server/StreamingEndEventImpl.java)3
-rw-r--r--server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java (renamed from server/src/com/vaadin/server/StreamingErrorEventImpl.java)3
-rw-r--r--server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java (renamed from server/src/com/vaadin/server/StreamingProgressEventImpl.java)3
-rw-r--r--server/src/com/vaadin/server/communication/StreamingStartEventImpl.java (renamed from server/src/com/vaadin/server/StreamingStartEventImpl.java)3
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java249
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java274
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java315
-rw-r--r--server/src/com/vaadin/ui/ConnectorTracker.java17
-rw-r--r--server/tests/src/com/vaadin/tests/server/TestSimpleMultiPartInputStream.java2
-rw-r--r--server/tests/src/com/vaadin/tests/server/TestStreamVariableMapping.java2
35 files changed, 3364 insertions, 2490 deletions
diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java
index 17bbbda737..29dc5b2f81 100644
--- a/server/src/com/vaadin/server/AbstractCommunicationManager.java
+++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java
@@ -16,36 +16,17 @@
package com.vaadin.server;
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-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.io.StringWriter;
-import java.lang.reflect.Type;
+import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
-import java.text.CharacterIterator;
-import java.text.DateFormat;
-import java.text.DateFormatSymbols;
-import java.text.SimpleDateFormat;
-import java.text.StringCharacterIterator;
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;
@@ -54,37 +35,19 @@ import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.servlet.http.HttpServletResponse;
-
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import com.vaadin.annotations.JavaScript;
-import com.vaadin.annotations.PreserveOnRefresh;
-import com.vaadin.annotations.StyleSheet;
import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
-import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
-import com.vaadin.server.ServerRpcManager.RpcInvocationException;
-import com.vaadin.server.StreamVariable.StreamingEndEvent;
-import com.vaadin.server.StreamVariable.StreamingErrorEvent;
+import com.vaadin.server.communication.LocaleWriter;
import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.Connector;
import com.vaadin.shared.JavaScriptConnectorState;
-import com.vaadin.shared.Version;
-import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
-import com.vaadin.shared.communication.MethodInvocation;
-import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.shared.communication.SharedState;
-import com.vaadin.shared.communication.UidlValue;
-import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.ui.Component;
import com.vaadin.ui.ConnectorTracker;
import com.vaadin.ui.HasComponents;
-import com.vaadin.ui.LegacyComponent;
import com.vaadin.ui.SelectiveRenderer;
import com.vaadin.ui.UI;
-import com.vaadin.ui.Window;
/**
* This is a common base class for the server-side implementations of the
@@ -100,7 +63,8 @@ import com.vaadin.ui.Window;
@SuppressWarnings("serial")
public abstract class AbstractCommunicationManager implements Serializable {
- private static final String DASHDASH = "--";
+ // TODO PUSH move
+ public static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler();
@@ -122,47 +86,24 @@ public abstract class AbstractCommunicationManager implements Serializable {
String details, String outOfSyncURL) throws IOException;
}
- static class UploadInterruptedException extends Exception {
- public UploadInterruptedException() {
- super("Upload interrupted by other thread");
- }
- }
-
- // 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 */
- public static final char VAR_BURST_SEPARATOR = '\u001d';
-
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
-
+ // TODO Refactor (#11410)
private final HashMap<Integer, ClientCache> uiToClientCache = new HashMap<Integer, ClientCache>();
- private static final int MAX_BUFFER_SIZE = 64 * 1024;
-
- /* Same as in apache commons file upload library that was previously used. */
- private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
-
/**
* The session this communication manager is used for
*/
private final VaadinSession session;
+ // TODO Refactor to UI shared state (#11378)
private List<String> locales;
- private int pendingLocalesIndex;
-
- private int timeoutInterval = -1;
-
+ // TODO Move to VaadinSession (#11409)
private DragAndDropService dragAndDropService;
+ // TODO Refactor (#11412)
private String requestThemeName;
- private int maxInactiveInterval;
-
- private ClientConnector highlightedConnector;
-
+ // TODO Refactor (#11413)
private Map<String, Class<?>> publishedFileContexts = new HashMap<String, Class<?>>();
/**
@@ -172,6 +113,7 @@ public abstract class AbstractCommunicationManager implements Serializable {
*/
public AbstractCommunicationManager(VaadinSession session) {
this.session = session;
+ // TODO Common to all sessions - handle at VaadinService level
session.addRequestHandler(getBootstrapHandler());
session.addRequestHandler(UNSUPPORTED_BROWSER_HANDLER);
session.addRequestHandler(CONNECTOR_RESOURCE_HANDLER);
@@ -182,557 +124,6 @@ public abstract class AbstractCommunicationManager implements Serializable {
return session;
}
- private static final int LF = "\n".getBytes()[0];
-
- private static final String CRLF = "\r\n";
-
- private static final String UTF8 = "UTF-8";
-
- private static String readLine(InputStream stream) throws IOException {
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- int readByte = stream.read();
- while (readByte != LF) {
- bout.write(readByte);
- readByte = stream.read();
- }
- byte[] bytes = bout.toByteArray();
- return new String(bytes, 0, bytes.length - 1, UTF8);
- }
-
- /**
- * Method used to stream content from a multipart request (either from
- * servlet or portlet request) to given StreamVariable
- *
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param boundary
- * @throws IOException
- */
- protected void doHandleSimpleMultipartFileUpload(VaadinRequest request,
- VaadinResponse response, StreamVariable streamVariable,
- String variableName, ClientConnector owner, String boundary)
- throws IOException {
- // multipart parsing, supports only one file for request, but that is
- // fine for our current terminal
-
- final InputStream inputStream = request.getInputStream();
-
- int contentLength = request.getContentLength();
-
- boolean atStart = false;
- boolean firstFileFieldFound = false;
-
- String rawfilename = "unknown";
- String rawMimeType = "application/octet-stream";
-
- /*
- * Read the stream until the actual file starts (empty line). Read
- * filename and content type from multipart headers.
- */
- while (!atStart) {
- String readLine = readLine(inputStream);
- contentLength -= (readLine.getBytes(UTF8).length + CRLF.length());
- if (readLine.startsWith("Content-Disposition:")
- && readLine.indexOf("filename=") > 0) {
- rawfilename = readLine.replaceAll(".*filename=", "");
- char quote = rawfilename.charAt(0);
- rawfilename = rawfilename.substring(1);
- rawfilename = rawfilename.substring(0,
- rawfilename.indexOf(quote));
- firstFileFieldFound = true;
- } else if (firstFileFieldFound && readLine.equals("")) {
- atStart = true;
- } else if (readLine.startsWith("Content-Type")) {
- rawMimeType = readLine.split(": ")[1];
- }
- }
-
- contentLength -= (boundary.length() + CRLF.length() + 2
- * DASHDASH.length() + CRLF.length());
-
- /*
- * Reads bytes from the underlying stream. Compares the read bytes to
- * the boundary string and returns -1 if met.
- *
- * The matching happens so that if the read byte equals to the first
- * char of boundary string, the stream goes to "buffering mode". In
- * buffering mode bytes are read until the character does not match the
- * corresponding from boundary string or the full boundary string is
- * found.
- *
- * Note, if this is someday needed elsewhere, don't shoot yourself to
- * foot and split to a top level helper class.
- */
- InputStream simpleMultiPartReader = new SimpleMultiPartInputStream(
- inputStream, boundary);
-
- /*
- * Should report only the filename even if the browser sends the path
- */
- final String filename = removePath(rawfilename);
- final String mimeType = rawMimeType;
-
- try {
- // TODO Shouldn't this check connectorEnabled?
- if (owner == null) {
- throw new UploadException(
- "File upload ignored because the connector for the stream variable was not found");
- }
- if (owner instanceof Component) {
- if (((Component) owner).isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the componente was read-only");
- }
- }
- boolean forgetVariable = streamToReceiver(simpleMultiPartReader,
- streamVariable, filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- session.lock();
- try {
- handleConnectorRelatedException(owner, e);
- } finally {
- session.unlock();
- }
- }
- sendUploadResponse(request, response);
-
- }
-
- /**
- * Used to stream plain file post (aka XHR2.post(File))
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param contentLength
- * @throws IOException
- */
- protected void doHandleXhrFilePost(VaadinRequest request,
- VaadinResponse response, StreamVariable streamVariable,
- String variableName, ClientConnector owner, int contentLength)
- throws IOException {
-
- // These are unknown in filexhr ATM, maybe add to Accept header that
- // is accessible in portlets
- final String filename = "unknown";
- final String mimeType = filename;
- final InputStream stream = request.getInputStream();
- try {
- /*
- * safe cast as in GWT terminal all variable owners are expected to
- * be components.
- */
- Component component = (Component) owner;
- if (component.isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the component was read-only");
- }
- boolean forgetVariable = streamToReceiver(stream, streamVariable,
- filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- session.lock();
- try {
- handleConnectorRelatedException(owner, e);
- } finally {
- session.unlock();
- }
- }
- sendUploadResponse(request, response);
- }
-
- /**
- * @param in
- * @param streamVariable
- * @param filename
- * @param type
- * @param contentLength
- * @return true if the streamvariable has informed that the terminal can
- * forget this variable
- * @throws UploadException
- */
- protected final boolean streamToReceiver(final InputStream in,
- StreamVariable streamVariable, String filename, String type,
- int contentLength) throws UploadException {
- if (streamVariable == null) {
- throw new IllegalStateException(
- "StreamVariable for the post not found");
- }
-
- final VaadinSession session = getSession();
-
- OutputStream out = null;
- int totalBytes = 0;
- StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
- filename, type, contentLength);
- try {
- boolean listenProgress;
- session.lock();
- try {
- streamVariable.streamingStarted(startedEvent);
- out = streamVariable.getOutputStream();
- listenProgress = streamVariable.listenProgress();
- } finally {
- session.unlock();
- }
-
- // Gets the output target stream
- if (out == null) {
- throw new NoOutputStreamException();
- }
-
- if (null == in) {
- // No file, for instance non-existent filename in html upload
- throw new NoInputStreamException();
- }
-
- final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
- int bytesReadToBuffer = 0;
- while ((bytesReadToBuffer = in.read(buffer)) > 0) {
- out.write(buffer, 0, bytesReadToBuffer);
- totalBytes += bytesReadToBuffer;
- if (listenProgress) {
- // update progress if listener set and contentLength
- // received
- session.lock();
- try {
- StreamingProgressEventImpl progressEvent = new StreamingProgressEventImpl(
- filename, type, contentLength, totalBytes);
- streamVariable.onProgress(progressEvent);
- } finally {
- session.unlock();
- }
- }
- if (streamVariable.isInterrupted()) {
- throw new UploadInterruptedException();
- }
- }
-
- // upload successful
- out.close();
- StreamingEndEvent event = new StreamingEndEventImpl(filename, type,
- totalBytes);
- session.lock();
- try {
- streamVariable.streamingFinished(event);
- } finally {
- session.unlock();
- }
-
- } catch (UploadInterruptedException e) {
- // Download interrupted by application code
- tryToCloseStream(out);
- StreamingErrorEvent event = new StreamingErrorEventImpl(filename,
- type, contentLength, totalBytes, e);
- session.lock();
- try {
- streamVariable.streamingFailed(event);
- } finally {
- session.unlock();
- }
- // Note, we are not throwing interrupted exception forward as it is
- // not a terminal level error like all other exception.
- } catch (final Exception e) {
- tryToCloseStream(out);
- session.lock();
- try {
- StreamingErrorEvent event = new StreamingErrorEventImpl(
- filename, type, contentLength, totalBytes, e);
- streamVariable.streamingFailed(event);
- // throw exception for terminal to be handled (to be passed to
- // terminalErrorHandler)
- throw new UploadException(e);
- } finally {
- session.unlock();
- }
- }
- return startedEvent.isDisposed();
- }
-
- static void tryToCloseStream(OutputStream out) {
- try {
- // try to close output stream (e.g. file handle)
- if (out != null) {
- out.close();
- }
- } catch (IOException e1) {
- // NOP
- }
- }
-
- /**
- * Removes any possible path information from the filename and returns the
- * filename. Separators / and \\ are used.
- *
- * @param name
- * @return
- */
- private static String removePath(String filename) {
- if (filename != null) {
- filename = filename.replaceAll("^.*[/\\\\]", "");
- }
-
- return filename;
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @throws IOException
- */
- protected void sendUploadResponse(VaadinRequest request,
- VaadinResponse response) throws IOException {
- 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();
- }
-
- /**
- * Internally process a UIDL request from the client.
- *
- * This method calls
- * {@link #handleVariables(VaadinRequest, VaadinResponse, Callback, VaadinSession, UI)}
- * to process any changes to variables by the client and then repaints
- * affected components using {@link #paintAfterVariableChanges()}.
- *
- * Also, some cleanup is done when a request arrives for an session that has
- * already been closed.
- *
- * The method handleUidlRequest(...) in subclasses should call this method.
- *
- * TODO better documentation
- *
- * @param request
- * @param response
- * @param callback
- * @param uI
- * target window for the UIDL request, can be null if target not
- * found
- * @throws IOException
- * @throws InvalidUIDLSecurityKeyException
- * @throws JSONException
- */
- public void handleUidlRequest(VaadinRequest request,
- VaadinResponse response, Callback callback, UI uI)
- throws IOException, InvalidUIDLSecurityKeyException, JSONException {
-
- checkWidgetsetVersion(request);
- requestThemeName = request.getParameter("theme");
- maxInactiveInterval = request.getWrappedSession()
- .getMaxInactiveInterval();
- // repaint requested or session has timed out and new one is created
- boolean repaintAll;
- final OutputStream out;
-
- repaintAll = (request
- .getParameter(ApplicationConstants.URL_PARAMETER_REPAINT_ALL) != null);
- // || (request.getSession().isNew()); FIXME What the h*ll is this??
- out = response.getOutputStream();
-
- boolean analyzeLayouts = false;
- if (repaintAll) {
- // analyzing can be done only with repaintAll
- analyzeLayouts = (request
- .getParameter(ApplicationConstants.PARAM_ANALYZE_LAYOUTS) != null);
-
- String pid = request
- .getParameter(ApplicationConstants.PARAM_HIGHLIGHT_CONNECTOR);
- if (pid != null) {
- highlightedConnector = uI.getConnectorTracker().getConnector(
- pid);
- highlightConnector(highlightedConnector);
- }
- }
-
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
-
- // The rest of the process is synchronized with the session
- // in order to guarantee that no parallel variable handling is
- // made
- session.lock();
- try {
-
- // Verify that there's an UI
- if (uI == null) {
- // This should not happen, no windows exists but
- // session is still open.
- getLogger().warning("Could not get UI for session");
- return;
- }
-
- session.setLastRequestTimestamp(System.currentTimeMillis());
-
- // Change all variables based on request parameters
- if (!handleVariables(request, response, callback, session, uI)) {
-
- // var inconsistency; the client is probably out-of-sync
- SystemMessages ci = response.getService().getSystemMessages(
- uI.getLocale(), request);
- 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, uI, analyzeLayouts);
- postPaint(uI);
- } finally {
- session.unlock();
- }
-
- outWriter.close();
- requestThemeName = null;
- }
-
- /**
- * Checks that the version reported by the client (widgetset) matches that
- * of the server.
- *
- * @param request
- */
- private void checkWidgetsetVersion(VaadinRequest request) {
- String widgetsetVersion = request.getParameter("v-wsver");
- if (widgetsetVersion == null) {
- // Only check when the widgetset version is reported. It is reported
- // in the first UIDL request (not the initial request as it is a
- // plain GET /)
- return;
- }
-
- if (!Version.getFullVersion().equals(widgetsetVersion)) {
- getLogger().warning(
- String.format(Constants.WIDGETSET_MISMATCH_INFO,
- Version.getFullVersion(), widgetsetVersion));
- }
- }
-
- /**
- * Method called after the paint phase while still being synchronized on the
- * session
- *
- * @param uI
- *
- */
- protected void postPaint(UI uI) {
- // Remove connectors that have been detached from the session during
- // handling of the request
- uI.getConnectorTracker().cleanConnectorMap();
- }
-
- protected void highlightConnector(ClientConnector highlightedConnector) {
- StringBuilder sb = new StringBuilder();
- sb.append("*** Debug details of a connector: *** \n");
- sb.append("Type: ");
- sb.append(highlightedConnector.getClass().getName());
- sb.append("\nId:");
- sb.append(highlightedConnector.getConnectorId());
- if (highlightedConnector instanceof Component) {
- Component component = (Component) highlightedConnector;
- if (component.getCaption() != null) {
- sb.append("\nCaption:");
- sb.append(component.getCaption());
- }
- }
- printHighlightedConnectorHierarchy(sb, highlightedConnector);
- getLogger().info(sb.toString());
- }
-
- protected void printHighlightedConnectorHierarchy(StringBuilder sb,
- ClientConnector connector) {
- LinkedList<ClientConnector> h = new LinkedList<ClientConnector>();
- h.add(connector);
- ClientConnector parent = connector.getParent();
- while (parent != null) {
- h.addFirst(parent);
- parent = parent.getParent();
- }
-
- sb.append("\nConnector hierarchy:\n");
- VaadinSession session2 = connector.getUI().getSession();
- sb.append(session2.getClass().getName());
- sb.append("(");
- sb.append(session2.getClass().getSimpleName());
- sb.append(".java");
- sb.append(":1)");
- int l = 1;
- for (ClientConnector connector2 : h) {
- sb.append("\n");
- for (int i = 0; i < l; i++) {
- sb.append(" ");
- }
- l++;
- Class<? extends ClientConnector> connectorClass = connector2
- .getClass();
- Class<?> topClass = connectorClass;
- while (topClass.getEnclosingClass() != null) {
- topClass = topClass.getEnclosingClass();
- }
- sb.append(connectorClass.getName());
- sb.append("(");
- sb.append(topClass.getSimpleName());
- sb.append(".java:1)");
- }
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @param callback
- * @param repaintAll
- * @param outWriter
- * @param window
- * @param analyzeLayouts
- * @throws PaintException
- * @throws IOException
- * @throws JSONException
- */
- private void paintAfterVariableChanges(VaadinRequest request,
- VaadinResponse response, Callback callback, boolean repaintAll,
- final PrintWriter outWriter, UI uI, boolean analyzeLayouts)
- throws PaintException, IOException, JSONException {
- openJsonMessage(outWriter, response);
-
- // security key
- Object writeSecurityTokenFlag = request
- .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
-
- if (writeSecurityTokenFlag != null) {
- outWriter.print(getSecurityKeyUIDL(request));
- }
-
- writeUidlResponse(request, repaintAll, outWriter, uI, analyzeLayouts);
-
- closeJsonMessage(outWriter);
-
- outWriter.close();
-
- }
-
/**
* Gets the security key (and generates one if needed) as UIDL.
*
@@ -769,443 +160,10 @@ public abstract class AbstractCommunicationManager implements Serializable {
return seckey;
}
- @SuppressWarnings("unchecked")
- public void writeUidlResponse(VaadinRequest request, boolean repaintAll,
- final PrintWriter outWriter, UI ui, boolean analyzeLayouts)
- throws PaintException, JSONException {
- ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>();
- VaadinSession session = ui.getSession();
- // Paints components
- ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
- getLogger().log(Level.FINE, "* Creating response to client");
- if (repaintAll) {
- getClientCache(ui).clear();
- uiConnectorTracker.markAllConnectorsDirty();
- uiConnectorTracker.markAllClientSidesUninitialized();
-
- // Reset sent locales
- locales = null;
- requireLocale(session.getLocale().toString());
- }
-
- dirtyVisibleConnectors
- .addAll(getDirtyVisibleConnectors(uiConnectorTracker));
-
- getLogger().log(Level.FINE, "Found {0} dirty connectors to paint",
- dirtyVisibleConnectors.size());
- for (ClientConnector connector : dirtyVisibleConnectors) {
- boolean initialized = uiConnectorTracker
- .isClientSideInitialized(connector);
- connector.beforeClientResponse(!initialized);
- }
-
- uiConnectorTracker.setWritingResponse(true);
- try {
- outWriter.print("\"changes\":[");
-
- List<InvalidLayout> invalidComponentRelativeSizes = null;
-
- JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
- !repaintAll);
- legacyPaint(paintTarget, dirtyVisibleConnectors);
-
- if (analyzeLayouts) {
- invalidComponentRelativeSizes = ComponentSizeValidator
- .validateComponentRelativeSizes(ui.getContent(), null,
- null);
-
- // Also check any existing subwindows
- if (ui.getWindows() != null) {
- for (Window subWindow : ui.getWindows()) {
- invalidComponentRelativeSizes = ComponentSizeValidator
- .validateComponentRelativeSizes(
- subWindow.getContent(),
- invalidComponentRelativeSizes, null);
- }
- }
- }
-
- paintTarget.close();
- outWriter.print("], "); // close changes
-
- // send shared state to client
-
- // for now, send the complete state of all modified and new
- // components
-
- // Ideally, all this would be sent before "changes", but that causes
- // complications with legacy components that create sub-components
- // in their paint phase. Nevertheless, this will be processed on the
- // client after component creation but before legacy UIDL
- // processing.
- JSONObject sharedStates = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- // encode and send shared state
- try {
- JSONObject stateJson = connector.encodeState();
-
- if (stateJson != null && stateJson.length() != 0) {
- sharedStates.put(connector.getConnectorId(), stateJson);
- }
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to serialize shared state for connector "
- + connector.getClass().getName() + " ("
- + connector.getConnectorId() + "): "
- + e.getMessage(), e);
- }
- }
- outWriter.print("\"state\":");
- outWriter.append(sharedStates.toString());
- outWriter.print(", "); // close states
-
- // TODO This should be optimized. The type only needs to be
- // sent once for each connector id + on refresh. Use the same cache
- // as
- // widget mapping
-
- JSONObject connectorTypes = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- String connectorType = paintTarget.getTag(connector);
- try {
- connectorTypes.put(connector.getConnectorId(),
- connectorType);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to send connector type for connector "
- + connector.getConnectorId() + ": "
- + e.getMessage(), e);
- }
- }
- outWriter.print("\"types\":");
- outWriter.append(connectorTypes.toString());
- outWriter.print(", "); // close states
-
- // Send update hierarchy information to the client.
-
- // This could be optimized aswell to send only info if hierarchy has
- // actually changed. Much like with the shared state. Note though
- // that an empty hierarchy is information aswell (e.g. change from 1
- // child to 0 children)
-
- outWriter.print("\"hierarchy\":");
-
- JSONObject hierarchyInfo = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- String connectorId = connector.getConnectorId();
- JSONArray children = new JSONArray();
-
- for (ClientConnector child : AbstractClientConnector
- .getAllChildrenIterable(connector)) {
- if (isConnectorVisibleToClient(child)) {
- children.put(child.getConnectorId());
- }
- }
- try {
- hierarchyInfo.put(connectorId, children);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to send hierarchy information about "
- + connectorId + " to the client: "
- + e.getMessage(), e);
- }
- }
- outWriter.append(hierarchyInfo.toString());
- outWriter.print(", "); // close hierarchy
-
- uiConnectorTracker.markAllConnectorsClean();
-
- // send server to client RPC calls for components in the UI, in call
- // order
-
- // collect RPC calls from components in the UI in the order in
- // which they were performed, remove the calls from components
-
- LinkedList<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>(
- dirtyVisibleConnectors);
- List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors);
-
- JSONArray rpcCalls = new JSONArray();
- for (ClientMethodInvocation invocation : pendingInvocations) {
- // add invocation to rpcCalls
- try {
- JSONArray invocationJson = new JSONArray();
- invocationJson.put(invocation.getConnector()
- .getConnectorId());
- invocationJson.put(invocation.getInterfaceName());
- invocationJson.put(invocation.getMethodName());
- JSONArray paramJson = new JSONArray();
- for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
- Type parameterType = invocation.getParameterTypes()[i];
- Object referenceParameter = null;
- // TODO Use default values for RPC parameter types
- // if (!JsonCodec.isInternalType(parameterType)) {
- // try {
- // referenceParameter = parameterType.newInstance();
- // } catch (Exception e) {
- // logger.log(Level.WARNING,
- // "Error creating reference object for parameter of type "
- // + parameterType.getName());
- // }
- // }
- EncodeResult encodeResult = JsonCodec.encode(
- invocation.getParameters()[i],
- referenceParameter, parameterType,
- ui.getConnectorTracker());
- paramJson.put(encodeResult.getEncodedValue());
- }
- invocationJson.put(paramJson);
- rpcCalls.put(invocationJson);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to serialize RPC method call parameters for connector "
- + invocation.getConnector()
- .getConnectorId() + " method "
- + invocation.getInterfaceName() + "."
- + invocation.getMethodName() + ": "
- + e.getMessage(), e);
- }
-
- }
-
- if (rpcCalls.length() > 0) {
- outWriter.print("\"rpc\" : ");
- outWriter.append(rpcCalls.toString());
- outWriter.print(", "); // close rpc
- }
-
- 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("]");
- }
- if (highlightedConnector != null) {
- outWriter.write(", \"hl\":\"");
- outWriter.write(highlightedConnector.getConnectorId());
- outWriter.write("\"");
- highlightedConnector = null;
- }
- }
-
- SystemMessages ci = request.getService().getSystemMessages(
- ui.getLocale(), request);
-
- // 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 = getTimeoutInterval();
- 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
-
- // TODO We should only precache the layouts that are not
- // cached already (plagiate from usedPaintableTypes)
- int resourceIndex = 0;
- for (final Iterator<Object> i = paintTarget.getUsedResources()
- .iterator(); i.hasNext();) {
- final String resource = (String) i.next();
- InputStream is = null;
- try {
- is = getThemeResourceAsStream(ui, getTheme(ui), resource);
- } catch (final Exception e) {
- // FIXME: Handle exception
- getLogger().log(Level.FINER,
- "Failed to get theme resource stream.", e);
- }
- 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
- getLogger().log(Level.INFO, "Resource transfer failed",
- e);
- }
- outWriter.print("\""
- + JsonPaintTarget.escapeJSON(layout.toString())
- + "\"");
- } else {
- // FIXME: Handle exception
- getLogger().severe("CustomLayout not found: " + resource);
- }
- }
- outWriter.print("}");
-
- Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
- .getUsedClientConnectors();
- boolean typeMappingsOpen = false;
- ClientCache clientCache = getClientCache(ui);
-
- List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
-
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (clientCache.cache(class1)) {
- // client does not know the mapping key for this type, send
- // mapping to client
- newConnectorTypes.add(class1);
-
- 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(" }");
- }
-
- boolean typeInheritanceMapOpen = false;
- if (typeMappingsOpen) {
- // send the whole type inheritance map if any new mappings
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (!ClientConnector.class.isAssignableFrom(class1
- .getSuperclass())) {
- continue;
- }
- if (!typeInheritanceMapOpen) {
- typeInheritanceMapOpen = true;
- outWriter.print(", \"typeInheritanceMap\" : { ");
- } else {
- outWriter.print(" , ");
- }
- outWriter.print("\"");
- outWriter.print(getTagForType(class1));
- outWriter.print("\" : ");
- outWriter
- .print(getTagForType((Class<? extends ClientConnector>) class1
- .getSuperclass()));
- }
- if (typeInheritanceMapOpen) {
- outWriter.print(" }");
- }
- }
-
- /*
- * Ensure super classes come before sub classes to get script
- * dependency order right. Sub class @JavaScript might assume that
- *
- * @JavaScript defined by super class is already loaded.
- */
- Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
- @Override
- public int compare(Class<?> o1, Class<?> o2) {
- // TODO optimize using Class.isAssignableFrom?
- return hierarchyDepth(o1) - hierarchyDepth(o2);
- }
-
- private int hierarchyDepth(Class<?> type) {
- if (type == Object.class) {
- return 0;
- } else {
- return hierarchyDepth(type.getSuperclass()) + 1;
- }
- }
- });
-
- List<String> scriptDependencies = new ArrayList<String>();
- List<String> styleDependencies = new ArrayList<String>();
-
- for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
- JavaScript jsAnnotation = class1
- .getAnnotation(JavaScript.class);
- if (jsAnnotation != null) {
- for (String uri : jsAnnotation.value()) {
- scriptDependencies.add(registerDependency(uri, class1));
- }
- }
-
- StyleSheet styleAnnotation = class1
- .getAnnotation(StyleSheet.class);
- if (styleAnnotation != null) {
- for (String uri : styleAnnotation.value()) {
- styleDependencies.add(registerDependency(uri, class1));
- }
- }
- }
-
- // Include script dependencies in output if there are any
- if (!scriptDependencies.isEmpty()) {
- outWriter.print(", \"scriptDependencies\": "
- + new JSONArray(scriptDependencies).toString());
- }
-
- // Include style dependencies in output if there are any
- if (!styleDependencies.isEmpty()) {
- outWriter.print(", \"styleDependencies\": "
- + new JSONArray(styleDependencies).toString());
- }
-
- // add any pending locale definitions requested by the client
- printLocaleDeclarations(outWriter);
-
- if (dragAndDropService != null) {
- dragAndDropService.printJSONResponse(outWriter);
- }
-
- for (ClientConnector connector : dirtyVisibleConnectors) {
- uiConnectorTracker.markClientSideInitialized(connector);
- }
-
- assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
-
- writePerformanceData(outWriter);
- } finally {
- uiConnectorTracker.setWritingResponse(false);
- }
- }
-
+ /**
+ * @deprecated As of 7.1. See #11411.
+ */
+ @Deprecated
public static JSONObject encodeState(ClientConnector connector,
SharedState state) throws JSONException {
UI uI = connector.getUI();
@@ -1243,8 +201,11 @@ public abstract class AbstractCommunicationManager implements Serializable {
* Resolves a dependency URI, registering the URI with this
* {@code AbstractCommunicationManager} if needed and returns a fully
* qualified URI.
+ *
+ * @deprecated As of 7.1. See #11413.
*/
- private String registerDependency(String resourceUri, Class<?> context) {
+ @Deprecated
+ public String registerDependency(String resourceUri, Class<?> context) {
try {
URI uri = new URI(resourceUri);
String protocol = uri.getScheme();
@@ -1268,6 +229,14 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
}
+ /**
+ * @deprecated As of 7.1. See #11413.
+ */
+ @Deprecated
+ public Map<String, Class<?>> getDependencies() {
+ return publishedFileContexts;
+ }
+
private String registerPublishedFile(String name, Class<?> context) {
synchronized (publishedFileContexts) {
// Add to map of names accepted by servePublishedFile
@@ -1288,75 +257,10 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
/**
- * Adds the performance timing data (used by TestBench 3) to the UIDL
- * response.
+ * @deprecated As of 7.1. See #11410.
*/
- private void writePerformanceData(final PrintWriter outWriter) {
- outWriter.write(String.format(", \"timings\":[%d, %d]",
- session.getCumulativeRequestDuration(),
- session.getLastRequestDuration()));
- }
-
- private void legacyPaint(PaintTarget paintTarget,
- ArrayList<ClientConnector> dirtyVisibleConnectors)
- throws PaintException {
- List<LegacyComponent> legacyComponents = new ArrayList<LegacyComponent>();
- for (Connector connector : dirtyVisibleConnectors) {
- // All Components that want to use paintContent must implement
- // LegacyComponent
- if (connector instanceof LegacyComponent) {
- legacyComponents.add((LegacyComponent) connector);
- }
- }
- sortByHierarchy((List) legacyComponents);
- for (LegacyComponent c : legacyComponents) {
- if (getLogger().isLoggable(Level.FINE)) {
- getLogger().log(
- Level.FINE,
- "Painting LegacyComponent {0}@{1}",
- new Object[] { c.getClass().getName(),
- Integer.toHexString(c.hashCode()) });
- }
- paintTarget.startTag("change");
- final String pid = c.getConnectorId();
- paintTarget.addAttribute("pid", pid);
- LegacyPaint.paint(c, paintTarget);
- paintTarget.endTag("change");
- }
-
- }
-
- private void sortByHierarchy(List<Component> paintables) {
- // Vaadin 6 requires parents to be painted before children as component
- // containers rely on that their updateFromUIDL method has been called
- // before children start calling e.g. updateCaption
- Collections.sort(paintables, new Comparator<Component>() {
-
- @Override
- public int compare(Component c1, Component c2) {
- int depth1 = 0;
- while (c1.getParent() != null) {
- depth1++;
- c1 = c1.getParent();
- }
- int depth2 = 0;
- while (c2.getParent() != null) {
- depth2++;
- c2 = c2.getParent();
- }
- if (depth1 < depth2) {
- return -1;
- }
- if (depth1 > depth2) {
- return 1;
- }
- return 0;
- }
- });
-
- }
-
- private ClientCache getClientCache(UI uI) {
+ @Deprecated
+ public ClientCache getClientCache(UI uI) {
Integer uiId = Integer.valueOf(uI.getUIId());
ClientCache cache = uiToClientCache.get(uiId);
if (cache == null) {
@@ -1372,11 +276,14 @@ public abstract class AbstractCommunicationManager implements Serializable {
* of connectors, the contextual visibility of its first Component ancestor
* is used. If no Component ancestor is found, the connector is not visible.
*
+ * @deprecated As of 7.1. See #11411.
+ *
* @param connector
* The connector to check
* @return <code>true</code> if the connector is visible to the client,
* <code>false</code> otherwise
*/
+ @Deprecated
public static boolean isConnectorVisibleToClient(ClientConnector connector) {
if (connector instanceof Component) {
return isComponentVisibleToClient((Component) connector);
@@ -1394,10 +301,13 @@ public abstract class AbstractCommunicationManager implements Serializable {
* Checks if the component should be visible to the client. Returns false if
* the child should not be sent to the client, true otherwise.
*
+ * @deprecated As of 7.1. See #11411.
+ *
* @param child
* The child to check
* @return true if the child is visible to the client, false otherwise
*/
+ @Deprecated
public static boolean isComponentVisibleToClient(Component child) {
if (!child.isVisible()) {
return false;
@@ -1423,71 +333,18 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
}
- private static class NullIterator<E> implements Iterator<E> {
-
- @Override
- public boolean hasNext() {
- return false;
- }
-
- @Override
- public E next() {
- return null;
- }
-
- @Override
- public void remove() {
- }
-
- }
-
/**
- * Collects all pending RPC calls from listed {@link ClientConnector}s and
- * clears their RPC queues.
- *
- * @param rpcPendingQueue
- * list of {@link ClientConnector} of interest
- * @return ordered list of pending RPC calls
+ * @deprecated As of 7.1. Likely to be removed or replaced in the future.
*/
- private List<ClientMethodInvocation> collectPendingRpcCalls(
- List<ClientConnector> rpcPendingQueue) {
- List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
- for (ClientConnector connector : rpcPendingQueue) {
- List<ClientMethodInvocation> paintablePendingRpc = connector
- .retrievePendingRpcCalls();
- if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
- List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
- int totalCalls = pendingInvocations.size()
- + paintablePendingRpc.size();
- pendingInvocations = new ArrayList<ClientMethodInvocation>(
- totalCalls);
-
- // merge two ordered comparable lists
- for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
- if (paintableIndex >= paintablePendingRpc.size()
- || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
- .get(oldIndex))
- .compareTo(paintablePendingRpc
- .get(paintableIndex)) <= 0)) {
- pendingInvocations.add(oldPendingRpc.get(oldIndex++));
- } else {
- pendingInvocations.add(paintablePendingRpc
- .get(paintableIndex++));
- }
- }
- }
- }
- return pendingInvocations;
- }
-
- protected abstract InputStream getThemeResourceAsStream(UI uI,
+ @Deprecated
+ public abstract InputStream getThemeResourceAsStream(UI uI,
String themeName, String resource);
- private int getTimeoutInterval() {
- return maxInactiveInterval;
- }
-
- private String getTheme(UI uI) {
+ /**
+ * @deprecated As of 7.1. See #11412.
+ */
+ @Deprecated
+ public String getTheme(UI uI) {
String themeName = uI.getTheme();
String requestThemeName = getRequestTheme();
@@ -1505,391 +362,10 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
/**
- * Returns false if the cross site request forgery protection is turned off.
- *
- * @param session
- * @return false if the XSRF is turned off, true otherwise
- */
- public boolean isXSRFEnabled(VaadinSession session) {
- return session.getConfiguration().isXsrfProtectionEnabled();
- }
-
- /**
- * TODO document
- *
- * 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(VaadinRequest request,
- VaadinResponse response, Callback callback, VaadinSession session,
- UI uI) throws IOException, InvalidUIDLSecurityKeyException,
- JSONException {
- boolean success = true;
-
- String changes = getRequestPayload(request);
- if (changes != null) {
-
- // Manage bursts one by one
- final String[] bursts = changes.split(String
- .valueOf(VAR_BURST_SEPARATOR));
-
- // Security: double cookie submission pattern unless disabled by
- // property
- if (isXSRFEnabled(session)) {
- 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
- .getWrappedSession()
- .getAttribute(
- ApplicationConstants.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++) {
- // unescape any encoded separator characters in the burst
- final String burst = unescapeBurst(bursts[bi]);
- success &= handleBurst(request, uI, burst);
-
- // 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, uI, false);
-
- }
-
- }
- }
- /*
- * Note that we ignore inconsistencies while handling unload request.
- * The client can't remove invalid variable changes from the burst, and
- * we don't have the required logic implemented on the server side. E.g.
- * a component is removed in a previous burst.
- */
- return success;
- }
-
- /**
- * Processes a message burst received from the client.
- *
- * A burst can contain any number of RPC calls, including legacy variable
- * change calls that are processed separately.
- *
- * Consecutive changes to the value of the same variable are combined and
- * changeVariables() is only called once for them. This preserves the Vaadin
- * 6 semantics for components and add-ons that do not use Vaadin 7 RPC
- * directly.
- *
- * @param source
- * @param uI
- * the UI receiving the burst
- * @param burst
- * the content of the burst as a String to be parsed
- * @return true if the processing of the burst was successful and there were
- * no messages to non-existent components
- */
- public boolean handleBurst(VaadinRequest source, UI uI, final String burst) {
- boolean success = true;
- try {
- Set<Connector> enabledConnectors = new HashSet<Connector>();
-
- List<MethodInvocation> invocations = parseInvocations(
- uI.getConnectorTracker(), burst);
- for (MethodInvocation invocation : invocations) {
- final ClientConnector connector = getConnector(uI,
- invocation.getConnectorId());
-
- if (connector != null && connector.isConnectorEnabled()) {
- enabledConnectors.add(connector);
- }
- }
-
- for (int i = 0; i < invocations.size(); i++) {
- MethodInvocation invocation = invocations.get(i);
-
- final ClientConnector connector = getConnector(uI,
- invocation.getConnectorId());
- if (connector == null) {
- getLogger()
- .log(Level.WARNING,
- "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
- new Object[] { invocation.getConnectorId(),
- invocation.getInterfaceName(),
- invocation.getMethodName() });
- continue;
- }
-
- if (!enabledConnectors.contains(connector)) {
-
- if (invocation instanceof LegacyChangeVariablesInvocation) {
- LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
- // TODO convert window close to a separate RPC call and
- // handle above - not a variable change
-
- // Handle special case where window-close is called
- // after the window has been removed from the
- // application or the application has closed
- Map<String, Object> changes = legacyInvocation
- .getVariableChanges();
- if (changes.size() == 1 && changes.containsKey("close")
- && Boolean.TRUE.equals(changes.get("close"))) {
- // Silently ignore this
- continue;
- }
- }
-
- // Connector is disabled, log a warning and move to the next
- String msg = "Ignoring RPC call for disabled connector "
- + connector.getClass().getName();
- if (connector instanceof Component) {
- String caption = ((Component) connector).getCaption();
- if (caption != null) {
- msg += ", caption=" + caption;
- }
- }
- getLogger().warning(msg);
- continue;
- }
-
- if (invocation instanceof ServerRpcMethodInvocation) {
- try {
- ServerRpcManager.applyInvocation(connector,
- (ServerRpcMethodInvocation) invocation);
- } catch (RpcInvocationException e) {
- handleConnectorRelatedException(connector, e);
- }
- } else {
-
- // All code below is for legacy variable changes
- LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
- Map<String, Object> changes = legacyInvocation
- .getVariableChanges();
- try {
- if (connector instanceof VariableOwner) {
- changeVariables(source, (VariableOwner) connector,
- changes);
- } else {
- throw new IllegalStateException(
- "Received legacy variable change for "
- + connector.getClass().getName()
- + " ("
- + connector.getConnectorId()
- + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
- + changes.keySet());
- }
- } catch (Exception e) {
- handleConnectorRelatedException(connector, e);
- }
- }
- }
- } catch (JSONException e) {
- getLogger().log(Level.WARNING,
- "Unable to parse RPC call from the client: {0}",
- e.getMessage());
- // TODO or return success = false?
- throw new RuntimeException(e);
- }
-
- return success;
- }
-
- /**
- * Handles an exception that occurred when processing Rpc calls or a file
- * upload.
- *
- * @param ui
- * The UI where the exception occured
- * @param throwable
- * The exception
- * @param connector
- * The Rpc target
- */
- private void handleConnectorRelatedException(ClientConnector connector,
- Throwable throwable) {
- ErrorEvent errorEvent = new ConnectorErrorEvent(connector, throwable);
- ErrorHandler handler = ErrorEvent.findErrorHandler(connector);
- handler.error(errorEvent);
- }
-
- /**
- * Parse a message burst from the client into a list of MethodInvocation
- * instances.
- *
- * @param connectorTracker
- * The ConnectorTracker used to lookup connectors
- * @param burst
- * message string (JSON)
- * @return list of MethodInvocation to perform
- * @throws JSONException
+ * @deprecated As of 7.1. See #11411.
*/
- private List<MethodInvocation> parseInvocations(
- ConnectorTracker connectorTracker, final String burst)
- throws JSONException {
- JSONArray invocationsJson = new JSONArray(burst);
-
- ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
-
- MethodInvocation previousInvocation = null;
- // parse JSON to MethodInvocations
- for (int i = 0; i < invocationsJson.length(); ++i) {
-
- JSONArray invocationJson = invocationsJson.getJSONArray(i);
-
- MethodInvocation invocation = parseInvocation(invocationJson,
- previousInvocation, connectorTracker);
- if (invocation != null) {
- // Can be null if the invocation was a legacy invocation and it
- // was merged with the previous one or if the invocation was
- // rejected because of an error.
- invocations.add(invocation);
- previousInvocation = invocation;
- }
- }
- return invocations;
- }
-
- private MethodInvocation parseInvocation(JSONArray invocationJson,
- MethodInvocation previousInvocation,
- ConnectorTracker connectorTracker) throws JSONException {
- String connectorId = invocationJson.getString(0);
- String interfaceName = invocationJson.getString(1);
- String methodName = invocationJson.getString(2);
-
- if (connectorTracker.getConnector(connectorId) == null
- && !connectorId
- .equals(ApplicationConstants.DRAG_AND_DROP_CONNECTOR_ID)) {
- getLogger()
- .log(Level.WARNING,
- "RPC call to "
- + interfaceName
- + "."
- + methodName
- + " received for connector "
- + connectorId
- + " but no such connector could be found. Resynchronizing client.");
- // This is likely an out of sync issue (client tries to update a
- // connector which is not present). Force resync.
- connectorTracker.markAllConnectorsDirty();
- return null;
- }
-
- JSONArray parametersJson = invocationJson.getJSONArray(3);
-
- if (LegacyChangeVariablesInvocation.isLegacyVariableChange(
- interfaceName, methodName)) {
- if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
- previousInvocation = null;
- }
-
- return parseLegacyChangeVariablesInvocation(connectorId,
- interfaceName, methodName,
- (LegacyChangeVariablesInvocation) previousInvocation,
- parametersJson, connectorTracker);
- } else {
- return parseServerRpcInvocation(connectorId, interfaceName,
- methodName, parametersJson, connectorTracker);
- }
-
- }
-
- private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
- String connectorId, String interfaceName, String methodName,
- LegacyChangeVariablesInvocation previousInvocation,
- JSONArray parametersJson, ConnectorTracker connectorTracker)
- throws JSONException {
- if (parametersJson.length() != 2) {
- throw new JSONException(
- "Invalid parameters in legacy change variables call. Expected 2, was "
- + parametersJson.length());
- }
- String variableName = parametersJson.getString(0);
- UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
- UidlValue.class, true, parametersJson.get(1), connectorTracker);
-
- Object value = uidlValue.getValue();
-
- if (previousInvocation != null
- && previousInvocation.getConnectorId().equals(connectorId)) {
- previousInvocation.setVariableChange(variableName, value);
- return null;
- } else {
- return new LegacyChangeVariablesInvocation(connectorId,
- variableName, value);
- }
- }
-
- private ServerRpcMethodInvocation parseServerRpcInvocation(
- String connectorId, String interfaceName, String methodName,
- JSONArray parametersJson, ConnectorTracker connectorTracker)
- throws JSONException {
- ClientConnector connector = connectorTracker.getConnector(connectorId);
-
- ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
- if (rpcManager == null) {
- /*
- * Security: Don't even decode the json parameters if no RpcManager
- * corresponding to the received method invocation has been
- * registered.
- */
- getLogger()
- .log(Level.WARNING,
- "Ignoring RPC call to {0}.{1} in connector {2} ({3}) as no RPC implementation is regsitered",
- new Object[] { interfaceName, methodName,
- connector.getClass().getName(), connectorId });
- return null;
- }
-
- // Use interface from RpcManager instead of loading the class based on
- // the string name to avoid problems with OSGi
- Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
-
- ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
- connectorId, rpcInterface, methodName, parametersJson.length());
-
- Object[] parameters = new Object[parametersJson.length()];
- Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
- .getGenericParameterTypes();
-
- for (int j = 0; j < parametersJson.length(); ++j) {
- Object parameterValue = parametersJson.get(j);
- Type parameterType = declaredRpcMethodParameterTypes[j];
- parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
- parameterValue, connectorTracker);
- }
- invocation.setParameters(parameters);
- return invocation;
- }
-
- protected void changeVariables(Object source, final VariableOwner owner,
- Map<String, Object> m) {
- owner.changeVariables(source, m);
- }
-
- protected ClientConnector getConnector(UI uI, String connectorId) {
+ @Deprecated
+ public ClientConnector getConnector(UI uI, String connectorId) {
ClientConnector c = uI.getConnectorTracker().getConnector(connectorId);
if (c == null
&& connectorId.equals(getDragAndDropService().getConnectorId())) {
@@ -1899,7 +375,11 @@ public abstract class AbstractCommunicationManager implements Serializable {
return c;
}
- private DragAndDropService getDragAndDropService() {
+ /**
+ * @deprecated As of 7.1. See #11409.
+ */
+ @Deprecated
+ public DragAndDropService getDragAndDropService() {
if (dragAndDropService == null) {
dragAndDropService = new DragAndDropService(this);
}
@@ -1907,255 +387,17 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
/**
- * Reads the request data from the Request and returns it converted to an
- * UTF-8 string.
- *
- * @param request
- * @return
- * @throws IOException
- */
- protected String getRequestPayload(VaadinRequest request)
- throws IOException {
-
- int requestLength = request.getContentLength();
- if (requestLength == 0) {
- return null;
- }
-
- ByteArrayOutputStream bout = requestLength <= 0 ? new ByteArrayOutputStream()
- : new ByteArrayOutputStream(requestLength);
-
- InputStream inputStream = request.getInputStream();
- byte[] buffer = new byte[MAX_BUFFER_SIZE];
-
- while (true) {
- int read = inputStream.read(buffer);
- if (read == -1) {
- break;
- }
- bout.write(buffer, 0, read);
- }
- String result = new String(bout.toByteArray(), "utf-8");
-
- return result;
- }
-
- /**
- * Unescape encoded burst separator characters in a burst received from the
- * client. This protects from separator injection attacks.
- *
- * @param encodedValue
- * to decode
- * @return decoded value
- */
- protected String unescapeBurst(String encodedValue) {
- final StringBuilder result = new StringBuilder();
- final StringCharacterIterator iterator = new StringCharacterIterator(
- encodedValue);
- char character = iterator.current();
- while (character != CharacterIterator.DONE) {
- if (VAR_ESCAPE_CHARACTER == character) {
- character = iterator.next();
- switch (character) {
- case VAR_ESCAPE_CHARACTER + 0x30:
- // escaped escape character
- result.append(VAR_ESCAPE_CHARACTER);
- break;
- case VAR_BURST_SEPARATOR + 0x30:
- // +0x30 makes these letters for easier reading
- result.append((char) (character - 0x30));
- break;
- case CharacterIterator.DONE:
- // error
- throw new RuntimeException(
- "Communication error: Unexpected end of message");
- default:
- // other escaped character - probably a client-server
- // version mismatch
- throw new RuntimeException(
- "Invalid escaped character from the client - check that the widgetset and server versions match");
- }
- } else {
- // not a special character - add it to the result as is
- result.append(character);
- }
- character = iterator.next();
- }
- return result.toString();
- }
-
- /**
* Prints the queued (pending) locale definitions to a {@link PrintWriter}
* in a (UIDL) format that can be sent to the client and used there in
* formatting dates, times etc.
*
- * @param outWriter
- */
- 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)) {
- getLogger().log(Level.WARNING,
- "Unable to get default date pattern for locale {0}", l);
- 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
- }
-
- protected void closeJsonMessage(PrintWriter outWriter) {
- outWriter.print("}]");
- }
-
- /**
- * Writes the opening of JSON message to be sent to client.
+ * @deprecated As of 7.1. See #11378.
*
* @param outWriter
- * @param response
- */
- protected void openJsonMessage(PrintWriter outWriter,
- VaadinResponse response) {
- // Sets the response type
- response.setContentType("application/json; charset=UTF-8");
- // some dirt to prevent cross site scripting
- outWriter.print("for(;;);[{");
- }
-
- /**
- * Returns dirty components which are in given window. Components in an
- * invisible subtrees are omitted.
- *
- * @param w
- * UI window for which dirty components is to be fetched
- * @return
*/
- private ArrayList<ClientConnector> getDirtyVisibleConnectors(
- ConnectorTracker connectorTracker) {
- ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>();
- for (ClientConnector c : connectorTracker.getDirtyConnectors()) {
- if (isConnectorVisibleToClient(c)) {
- dirtyConnectors.add(c);
- }
- }
-
- return dirtyConnectors;
+ @Deprecated
+ public void printLocaleDeclarations(Writer writer) throws IOException {
+ new LocaleWriter().write(locales, writer);
}
/**
@@ -2165,15 +407,17 @@ public abstract class AbstractCommunicationManager implements Serializable {
* the need to use the {@link Locale} class and all the framework behind it
* on the client.
*
+ * @deprecated As of 7.1. See #11378.
+ *
* @see Locale#toString()
*
* @param value
*/
+ @Deprecated
public void requireLocale(String value) {
if (locales == null) {
locales = new ArrayList<String>();
locales.add(session.getLocale().toString());
- pendingLocalesIndex = 0;
}
if (!locales.contains(value)) {
locales.add(value);
@@ -2181,32 +425,23 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
/**
- * Constructs a {@link Locale} instance to be sent to the client based on a
- * short locale description string.
- *
- * @see #requireLocale(String)
- *
- * @param value
- * @return
+ * @deprecated As of 7.1. See #11378.
*/
- 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]);
- }
+ @Deprecated
+ public void resetLocales() {
+ locales = null;
}
- protected class InvalidUIDLSecurityKeyException extends
+ /**
+ * @deprecated As of 7.1. Will be removed in the future.
+ */
+ @Deprecated
+ public static class InvalidUIDLSecurityKeyException extends
GeneralSecurityException {
- InvalidUIDLSecurityKeyException(String message) {
+ public InvalidUIDLSecurityKeyException(String message) {
super(message);
}
-
}
private final HashMap<Class<? extends ClientConnector>, Integer> typeToKey = new HashMap<Class<? extends ClientConnector>, Integer>();
@@ -2214,7 +449,11 @@ public abstract class AbstractCommunicationManager implements Serializable {
private BootstrapHandler bootstrapHandler;
- String getTagForType(Class<? extends ClientConnector> class1) {
+ /**
+ * @deprecated As of 7.1. Will be removed in the future.
+ */
+ @Deprecated
+ public String getTagForType(Class<? extends ClientConnector> class1) {
Integer id = typeToKey.get(class1);
if (id == null) {
id = nextTypeKey++;
@@ -2232,8 +471,11 @@ public abstract class AbstractCommunicationManager implements Serializable {
* to know.
*
* TODO make customlayout templates (from theme) to be cached here.
+ *
+ * @deprecated As of 7.1. See #11410.
*/
- class ClientCache implements Serializable {
+ @Deprecated
+ public class ClientCache implements Serializable {
private final Set<Object> res = new HashSet<Object>();
@@ -2242,7 +484,7 @@ public abstract class AbstractCommunicationManager implements Serializable {
* @param paintable
* @return true if the given class was added to cache
*/
- boolean cache(Object object) {
+ public boolean cache(Object object) {
return res.add(object);
}
@@ -2252,6 +494,10 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
+ /**
+ * @deprecated As of 7.1. See #11411.
+ */
+ @Deprecated
public String getStreamVariableTargetUrl(ClientConnector owner,
String name, StreamVariable value) {
/*
@@ -2280,11 +526,6 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
- public void cleanStreamVariable(ClientConnector owner, String name) {
- owner.getUI().getConnectorTracker()
- .cleanStreamVariable(owner.getConnectorId(), name);
- }
-
/**
* Gets the bootstrap handler that should be used for generating the pages
* bootstrapping applications for this communication manager.
@@ -2335,7 +576,10 @@ public abstract class AbstractCommunicationManager implements Serializable {
* @see RequestHandler
*
* @since 7.0
+ *
+ * @deprecated As of 7.1. Should be moved to VaadinService.
*/
+ @Deprecated
protected boolean handleOtherRequest(VaadinRequest request,
VaadinResponse response) throws IOException {
// Use a copy to avoid ConcurrentModificationException
@@ -2349,530 +593,44 @@ public abstract class AbstractCommunicationManager implements Serializable {
return false;
}
- public void handleBrowserDetailsRequest(VaadinRequest request,
- VaadinResponse response, VaadinSession session) throws IOException {
-
- session.lock();
-
- try {
- assert UI.getCurrent() == null;
-
- response.setContentType("application/json; charset=UTF-8");
-
- UI uI = getBrowserDetailsUI(request, session);
-
- JSONObject params = new JSONObject();
- params.put(UIConstants.UI_ID_PARAMETER, uI.getUIId());
- String initialUIDL = getInitialUIDL(request, uI);
- params.put("uidl", initialUIDL);
-
- // NOTE! GateIn requires, for some weird reason, getOutputStream
- // to be used instead of getWriter() (it seems to interpret
- // application/json as a binary content type)
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
-
- outWriter.write(params.toString());
- // NOTE GateIn requires the buffers to be flushed to work
- outWriter.flush();
- out.flush();
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } finally {
- session.unlock();
- }
- }
-
- private UI getBrowserDetailsUI(VaadinRequest request, VaadinSession session) {
- VaadinService vaadinService = request.getService();
-
- List<UIProvider> uiProviders = session.getUIProviders();
-
- UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
- request);
-
- UIProvider provider = null;
- Class<? extends UI> uiClass = null;
- for (UIProvider p : uiProviders) {
- // Check for existing LegacyWindow
- if (p instanceof LegacyApplicationUIProvider) {
- LegacyApplicationUIProvider legacyProvider = (LegacyApplicationUIProvider) p;
-
- UI existingUi = legacyProvider
- .getExistingUI(classSelectionEvent);
- if (existingUi != null) {
- reinitUI(existingUi, request);
- return existingUi;
- }
- }
-
- uiClass = p.getUIClass(classSelectionEvent);
- if (uiClass != null) {
- provider = p;
- break;
- }
- }
-
- if (provider == null || uiClass == null) {
- return null;
- }
-
- // Check for an existing UI based on window.name
-
- // Special parameter sent by vaadinBootstrap.js
- String windowName = request.getParameter("v-wn");
-
- Map<String, Integer> retainOnRefreshUIs = session
- .getPreserveOnRefreshUIs();
- if (windowName != null && !retainOnRefreshUIs.isEmpty()) {
- // Check for a known UI
-
- Integer retainedUIId = retainOnRefreshUIs.get(windowName);
-
- if (retainedUIId != null) {
- UI retainedUI = session.getUIById(retainedUIId.intValue());
- if (uiClass.isInstance(retainedUI)) {
- reinitUI(retainedUI, request);
- return retainedUI;
- } else {
- getLogger().log(
- Level.INFO,
- "Not using retained UI in {0} because retained UI was of type {1}"
- + " but {2} is expected for the request.",
- new Object[] { windowName, retainedUI.getClass(),
- uiClass });
- }
- }
- }
-
- // No existing UI found - go on by creating and initializing one
-
- Integer uiId = Integer.valueOf(session.getNextUIid());
-
- // Explicit Class.cast to detect if the UIProvider does something
- // unexpected
- UICreateEvent event = new UICreateEvent(request, uiClass, uiId);
- UI ui = uiClass.cast(provider.createInstance(event));
-
- // Initialize some fields for a newly created UI
- if (ui.getSession() != session) {
- // Session already set for LegacyWindow
- ui.setSession(session);
- }
-
- // Set thread local here so it is available in init
- UI.setCurrent(ui);
-
- ui.doInit(request, uiId.intValue());
-
- session.addUI(ui);
-
- // Remember if it should be remembered
- if (vaadinService.preserveUIOnRefresh(provider, event)) {
- // Remember this UI
- if (windowName == null) {
- getLogger()
- .log(Level.WARNING,
- "There is no window.name available for UI {0} that should be preserved.",
- uiClass);
- } else {
- session.getPreserveOnRefreshUIs().put(windowName, uiId);
- }
- }
-
- return ui;
- }
-
- /**
- * Updates a UI that has already been initialized but is now loaded again,
- * e.g. because of {@link PreserveOnRefresh}.
- *
- * @param ui
- * @param request
- */
- private void reinitUI(UI ui, VaadinRequest request) {
- UI.setCurrent(ui);
-
- // Fire fragment change if the fragment has changed
- String location = request.getParameter("v-loc");
- if (location != null) {
- ui.getPage().updateLocation(location);
- }
- }
-
/**
- * Generates the initial UIDL message that can e.g. be included in a html
- * page to avoid a separate round trip just for getting the UIDL.
- *
- * @param request
- * the request that caused the initialization
- * @param uI
- * the UI for which the UIDL should be generated
- * @return a string with the initial UIDL message
- * @throws PaintException
- * if an exception occurs while painting
- * @throws JSONException
- * if an exception occurs while encoding output
- */
- protected String getInitialUIDL(VaadinRequest request, UI uI)
- throws PaintException, JSONException {
- // TODO maybe unify writeUidlResponse()?
- StringWriter sWriter = new StringWriter();
- PrintWriter pWriter = new PrintWriter(sWriter);
- pWriter.print("{");
- if (isXSRFEnabled(uI.getSession())) {
- pWriter.print(getSecurityKeyUIDL(request));
- }
- writeUidlResponse(request, true, pWriter, uI, false);
- pWriter.print("}");
- String initialUIDL = sWriter.toString();
- getLogger().log(Level.FINE, "Initial UIDL:{0}", initialUIDL);
- return initialUIDL;
- }
-
- /**
- * Serve a connector resource from the classpath if the resource has
- * previously been registered by calling
- * {@link #registerPublishedFile(String, Class)}. Sending arbitrary files
- * from the classpath is prevented by only accepting resource names that
- * have explicitly been registered. Resources can currently only be
- * registered by including a {@link JavaScript} or {@link StyleSheet}
- * annotation on a Connector class.
- *
- * @param request
- * @param response
- *
- * @throws IOException
- */
- public void servePublishedFile(VaadinRequest request,
- VaadinResponse response) throws IOException {
-
- String pathInfo = request.getPathInfo();
- // + 2 to also remove beginning and ending slashes
- String fileName = pathInfo
- .substring(ApplicationConstants.PUBLISHED_FILE_PATH.length() + 2);
-
- final String mimetype = response.getService().getMimeType(fileName);
-
- // Security check: avoid accidentally serving from the UI of the
- // classpath instead of relative to the context class
- if (fileName.startsWith("/")) {
- getLogger().log(Level.WARNING,
- "Published file request starting with / rejected: {0}",
- fileName);
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // Check that the resource name has been registered
- Class<?> context;
- synchronized (publishedFileContexts) {
- context = publishedFileContexts.get(fileName);
- }
-
- // Security check: don't serve resource if the name hasn't been
- // registered in the map
- if (context == null) {
- getLogger()
- .log(Level.WARNING,
- "Rejecting published file request for file that has not been published: {0}",
- fileName);
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // Resolve file relative to the location of the context class
- InputStream in = context.getResourceAsStream(fileName);
- if (in == null) {
- getLogger()
- .log(Level.WARNING,
- "{0} published by {1} not found. Verify that the file {2}/{3} is available on the classpath.",
- new Object[] {
- fileName,
- context.getName(),
- context.getPackage().getName()
- .replace('.', '/'), fileName });
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // TODO Check and set cache headers
-
- OutputStream out = null;
- try {
- if (mimetype != null) {
- response.setContentType(mimetype);
- }
-
- out = response.getOutputStream();
-
- final byte[] buffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
-
- int bytesRead = 0;
- while ((bytesRead = in.read(buffer)) > 0) {
- out.write(buffer, 0, bytesRead);
- }
- out.flush();
- } finally {
- try {
- in.close();
- } catch (Exception e) {
- // Do nothing
- }
- if (out != null) {
- try {
- out.close();
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
- }
-
- /**
- * Handles file upload request submitted via Upload component.
- *
- * @param UI
- * The UI for this request
+ * Handles an exception that occurred when processing RPC calls or a file
+ * upload.
*
- * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable)
+ * @deprecated As of 7.1. See #11411.
*
- * @param request
- * @param response
- * @throws IOException
- * @throws InvalidUIDLSecurityKeyException
- */
- public void handleFileUpload(VaadinSession session, VaadinRequest request,
- VaadinResponse response) throws IOException,
- InvalidUIDLSecurityKeyException {
-
- /*
- * URI pattern: APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY] See
- * #createReceiverUrl
- */
-
- String pathInfo = request.getPathInfo();
- // strip away part until the data we are interested starts
- int startOfData = pathInfo
- .indexOf(ServletPortletHelper.UPLOAD_URL_PREFIX)
- + ServletPortletHelper.UPLOAD_URL_PREFIX.length();
- String uppUri = pathInfo.substring(startOfData);
- String[] parts = uppUri.split("/", 4); // 0= UIid, 1 = cid, 2= name, 3
- // = sec key
- String uiId = parts[0];
- String connectorId = parts[1];
- String variableName = parts[2];
- UI uI = session.getUIById(Integer.parseInt(uiId));
- UI.setCurrent(uI);
-
- StreamVariable streamVariable = uI.getConnectorTracker()
- .getStreamVariable(connectorId, variableName);
- String secKey = uI.getConnectorTracker().getSeckey(streamVariable);
- if (secKey.equals(parts[3])) {
-
- ClientConnector source = getConnector(uI, connectorId);
- String contentType = request.getContentType();
- if (contentType.contains("boundary")) {
- // Multipart requests contain boundary string
- doHandleSimpleMultipartFileUpload(request, response,
- streamVariable, variableName, source,
- contentType.split("boundary=")[1]);
- } else {
- // if boundary string does not exist, the posted file is from
- // XHR2.post(File)
- doHandleXhrFilePost(request, response, streamVariable,
- variableName, source, request.getContentLength());
- }
- } else {
- throw new InvalidUIDLSecurityKeyException(
- "Security key in upload post did not match!");
- }
-
- }
-
- /**
- * Handles a heartbeat request. Heartbeat requests are periodically sent by
- * the client-side to inform the server that the UI sending the heartbeat is
- * still alive (the browser window is open, the connection is up) even when
- * there are no UIDL requests for a prolonged period of time. UIs that do
- * not receive either heartbeat or UIDL requests are eventually removed from
- * the session and garbage collected.
- *
- * @param request
- * @param response
- * @param session
- * @throws IOException
+ * @param ui
+ * The UI where the exception occured
+ * @param throwable
+ * The exception
+ * @param connector
+ * The Rpc target
*/
- public void handleHeartbeatRequest(VaadinRequest request,
- VaadinResponse response, VaadinSession session) throws IOException {
- UI ui = null;
- try {
- int uiId = Integer.parseInt(request
- .getParameter(UIConstants.UI_ID_PARAMETER));
- ui = session.getUIById(uiId);
- } catch (NumberFormatException nfe) {
- // null-check below handles this as well
- }
- if (ui != null) {
- ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
- // Ensure that the browser does not cache heartbeat responses.
- // iOS 6 Safari requires this (#10370)
- response.setHeader("Cache-Control", "no-cache");
- } else {
- response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
- }
+ @Deprecated
+ public void handleConnectorRelatedException(ClientConnector connector,
+ Throwable throwable) {
+ ErrorEvent errorEvent = new ConnectorErrorEvent(connector, throwable);
+ ErrorHandler handler = ErrorEvent.findErrorHandler(connector);
+ handler.error(errorEvent);
}
/**
- * Stream that extracts content from another stream until the boundary
- * string is encountered.
+ * Requests that the given UI should be fully re-rendered on the client
+ * side.
*
- * Public only for unit tests, should be considered private for all other
- * purposes.
+ * @since 7.1
+ * @deprecated. As of 7.1. Should be refactored once locales are fixed
+ * (#11378)
*/
- public static class SimpleMultiPartInputStream extends InputStream {
-
- /**
- * Counter of how many characters have been matched to boundary string
- * from the stream
- */
- int matchedCount = -1;
-
- /**
- * Used as pointer when returning bytes after partly matched boundary
- * string.
- */
- int curBoundaryIndex = 0;
- /**
- * The byte found after a "promising start for boundary"
- */
- private int bufferedByte = -1;
- private boolean atTheEnd = false;
-
- private final char[] boundary;
-
- private final InputStream realInputStream;
-
- public SimpleMultiPartInputStream(InputStream realInputStream,
- String boundaryString) {
- boundary = (CRLF + DASHDASH + boundaryString).toCharArray();
- this.realInputStream = realInputStream;
- }
-
- @Override
- public int read() throws IOException {
- if (atTheEnd) {
- // End boundary reached, nothing more to read
- return -1;
- } else if (bufferedByte >= 0) {
- /* Purge partially matched boundary if there was such */
- return getBuffered();
- } else if (matchedCount != -1) {
- /*
- * Special case where last "failed" matching ended with first
- * character from boundary string
- */
- return matchForBoundary();
- } else {
- int fromActualStream = realInputStream.read();
- if (fromActualStream == -1) {
- // unexpected end of stream
- throw new IOException(
- "The multipart stream ended unexpectedly");
- }
- if (boundary[0] == fromActualStream) {
- /*
- * If matches the first character in boundary string, start
- * checking if the boundary is fetched.
- */
- return matchForBoundary();
- }
- return fromActualStream;
- }
- }
-
- /**
- * Reads the input to expect a boundary string. Expects that the first
- * character has already been matched.
- *
- * @return -1 if the boundary was matched, else returns the first byte
- * from boundary
- * @throws IOException
- */
- private int matchForBoundary() throws IOException {
- matchedCount = 0;
- /*
- * Going to "buffered mode". Read until full boundary match or a
- * different character.
- */
- while (true) {
- matchedCount++;
- if (matchedCount == boundary.length) {
- /*
- * The whole boundary matched so we have reached the end of
- * file
- */
- atTheEnd = true;
- return -1;
- }
- int fromActualStream = realInputStream.read();
- if (fromActualStream != boundary[matchedCount]) {
- /*
- * Did not find full boundary, cache the mismatching byte
- * and start returning the partially matched boundary.
- */
- bufferedByte = fromActualStream;
- return getBuffered();
- }
- }
- }
+ @Deprecated
+ public void repaintAll(UI ui) {
+ getClientCache(ui).clear();
+ ui.getConnectorTracker().markAllConnectorsDirty();
+ ui.getConnectorTracker().markAllClientSidesUninitialized();
- /**
- * Returns the partly matched boundary string and the byte following
- * that.
- *
- * @return
- * @throws IOException
- */
- private int getBuffered() throws IOException {
- int b;
- if (matchedCount == 0) {
- // The boundary has been returned, return the buffered byte.
- b = bufferedByte;
- bufferedByte = -1;
- matchedCount = -1;
- } else {
- b = boundary[curBoundaryIndex++];
- if (curBoundaryIndex == matchedCount) {
- // The full boundary has been returned, remaining is the
- // char that did not match the boundary.
-
- curBoundaryIndex = 0;
- if (bufferedByte != boundary[0]) {
- /*
- * next call for getBuffered will return the
- * bufferedByte that came after the partial boundary
- * match
- */
- matchedCount = 0;
- } else {
- /*
- * Special case where buffered byte again matches the
- * boundaryString. This could be the start of the real
- * end boundary.
- */
- matchedCount = 0;
- bufferedByte = -1;
- }
- }
- }
- if (b == -1) {
- throw new IOException("The multipart stream ended unexpectedly");
- }
- return b;
- }
+ // Reset sent locales
+ resetLocales();
+ requireLocale(session.getLocale().toString());
}
private static final Logger getLogger() {
diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java
index 8b3550481d..44e8f87d9f 100644
--- a/server/src/com/vaadin/server/CommunicationManager.java
+++ b/server/src/com/vaadin/server/CommunicationManager.java
@@ -80,7 +80,7 @@ public class CommunicationManager extends AbstractCommunicationManager {
}
@Override
- protected InputStream getThemeResourceAsStream(UI uI, String themeName,
+ public InputStream getThemeResourceAsStream(UI uI, String themeName,
String resource) {
VaadinServletService service = (VaadinServletService) uI.getSession()
.getService();
diff --git a/server/src/com/vaadin/server/ComponentSizeValidator.java b/server/src/com/vaadin/server/ComponentSizeValidator.java
index f5e2e2fe12..27d087a2b2 100644
--- a/server/src/com/vaadin/server/ComponentSizeValidator.java
+++ b/server/src/com/vaadin/server/ComponentSizeValidator.java
@@ -191,7 +191,6 @@ public class ComponentSizeValidator implements Serializable {
}
public void reportErrors(PrintWriter clientJSON,
- AbstractCommunicationManager communicationManager,
PrintStream serverErrorStream) {
clientJSON.write("{");
@@ -269,8 +268,7 @@ public class ComponentSizeValidator implements Serializable {
} else {
first = false;
}
- subError.reportErrors(clientJSON, communicationManager,
- serverErrorStream);
+ subError.reportErrors(clientJSON, serverErrorStream);
}
clientJSON.write("]");
serverErrorStream.println("<< Sub erros");
diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java
index 5a54b5ae3a..e403f4d4cb 100644
--- a/server/src/com/vaadin/server/DragAndDropService.java
+++ b/server/src/com/vaadin/server/DragAndDropService.java
@@ -16,7 +16,7 @@
package com.vaadin.server;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -209,10 +209,10 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
return true;
}
- void printJSONResponse(PrintWriter outWriter) throws PaintException {
+ public void printJSONResponse(Writer outWriter) throws IOException {
if (isDirty()) {
- outWriter.print(", \"dd\":");
+ outWriter.write(", \"dd\":");
JsonPaintTarget jsonPaintTarget = new JsonPaintTarget(manager,
outWriter, false);
diff --git a/server/src/com/vaadin/server/JsonPaintTarget.java b/server/src/com/vaadin/server/JsonPaintTarget.java
index 11bfb33fe1..35ff8659ad 100644
--- a/server/src/com/vaadin/server/JsonPaintTarget.java
+++ b/server/src/com/vaadin/server/JsonPaintTarget.java
@@ -18,6 +18,7 @@ package com.vaadin.server;
import java.io.PrintWriter;
import java.io.Serializable;
+import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -87,13 +88,12 @@ public class JsonPaintTarget implements PaintTarget {
* if the paint operation failed.
*/
public JsonPaintTarget(AbstractCommunicationManager manager,
- PrintWriter outWriter, boolean cachingRequired)
- throws PaintException {
+ Writer outWriter, boolean cachingRequired) throws PaintException {
this.manager = manager;
// Sets the target for UIDL writing
- uidlBuffer = outWriter;
+ uidlBuffer = new PrintWriter(outWriter);
// Initialize tag-writing
mOpenTags = new Stack<String>();
@@ -1007,7 +1007,7 @@ public class JsonPaintTarget implements PaintTarget {
return manager.getTagForType(clientConnectorClass);
}
- Collection<Class<? extends ClientConnector>> getUsedClientConnectors() {
+ public Collection<Class<? extends ClientConnector>> getUsedClientConnectors() {
return usedClientConnectors;
}
diff --git a/server/src/com/vaadin/server/PortletCommunicationManager.java b/server/src/com/vaadin/server/PortletCommunicationManager.java
index cece75847c..fdec421741 100644
--- a/server/src/com/vaadin/server/PortletCommunicationManager.java
+++ b/server/src/com/vaadin/server/PortletCommunicationManager.java
@@ -134,7 +134,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
}
@Override
- protected InputStream getThemeResourceAsStream(UI uI, String themeName,
+ public InputStream getThemeResourceAsStream(UI uI, String themeName,
String resource) {
VaadinPortletSession session = (VaadinPortletSession) uI.getSession();
PortletContext portletContext = session.getPortletSession()
diff --git a/server/src/com/vaadin/server/ServerRpcManager.java b/server/src/com/vaadin/server/ServerRpcManager.java
index ec25ce83ca..a1682cb453 100644
--- a/server/src/com/vaadin/server/ServerRpcManager.java
+++ b/server/src/com/vaadin/server/ServerRpcManager.java
@@ -139,7 +139,7 @@ public class ServerRpcManager<T extends ServerRpc> implements Serializable {
*
* @return RPC interface type
*/
- protected Class<T> getRpcInterface() {
+ public Class<T> getRpcInterface() {
return rpcInterface;
}
diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java
index ce9872f40e..baf697cae3 100644
--- a/server/src/com/vaadin/server/ServletPortletHelper.java
+++ b/server/src/com/vaadin/server/ServletPortletHelper.java
@@ -23,23 +23,14 @@ import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.Component;
import com.vaadin.ui.UI;
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
+/**
+ * Contains helper methods shared by {@link VaadinServlet} and
+ * {@link VaadinPortlet}.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * @deprecated As of 7.1. Will be removed or refactored in the future.
*/
-
-class ServletPortletHelper implements Serializable {
+@Deprecated
+public class ServletPortletHelper implements Serializable {
public static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/";
/**
* The default SystemMessages (read-only).
diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java
index b4a2390fa5..2abf140a3d 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -24,7 +24,6 @@ import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
-import java.security.GeneralSecurityException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
@@ -52,7 +51,12 @@ import javax.servlet.http.HttpServletResponse;
import com.liferay.portal.kernel.util.PortalClassInvoker;
import com.liferay.portal.kernel.util.PropsUtil;
import com.vaadin.server.AbstractCommunicationManager.Callback;
-import com.vaadin.ui.UI;
+import com.vaadin.server.communication.FileUploadHandler;
+import com.vaadin.server.communication.HeartbeatHandler;
+import com.vaadin.server.communication.PortletListenerNotifier;
+import com.vaadin.server.communication.PublishedFileHandler;
+import com.vaadin.server.communication.UIInitHandler;
+import com.vaadin.server.communication.UidlRequestHandler;
import com.vaadin.util.CurrentInstance;
/**
@@ -422,86 +426,44 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
return;
}
- PortletCommunicationManager communicationManager = (PortletCommunicationManager) vaadinSession
- .getCommunicationManager();
-
if (requestType == RequestType.PUBLISHED_FILE) {
- communicationManager.servePublishedFile(vaadinRequest,
- vaadinResponse);
+ new PublishedFileHandler().handleRequest(vaadinSession,
+ vaadinRequest, vaadinResponse);
return;
} else if (requestType == RequestType.HEARTBEAT) {
- communicationManager.handleHeartbeatRequest(
- vaadinRequest, vaadinResponse, vaadinSession);
+ new HeartbeatHandler().handleRequest(vaadinSession,
+ vaadinRequest, vaadinResponse);
return;
}
- /* Update browser information from request */
- vaadinSession.getBrowser().updateRequestDetails(
- vaadinRequest);
-
- /* Notify listeners */
-
- // Finds the right UI
- UI uI = null;
- if (requestType == RequestType.UIDL) {
- uI = getService().findUI(vaadinRequest);
- }
-
- // TODO Should this happen before or after the transaction
- // starts?
- if (request instanceof RenderRequest) {
- vaadinSession.firePortletRenderRequest(uI,
- (RenderRequest) request,
- (RenderResponse) response);
- } else if (request instanceof ActionRequest) {
- vaadinSession.firePortletActionRequest(uI,
- (ActionRequest) request,
- (ActionResponse) response);
- } else if (request instanceof EventRequest) {
- vaadinSession.firePortletEventRequest(uI,
- (EventRequest) request,
- (EventResponse) response);
- } else if (request instanceof ResourceRequest) {
- vaadinSession.firePortletResourceRequest(uI,
- (ResourceRequest) request,
- (ResourceResponse) response);
- }
+ // Notify listeners
+ new PortletListenerNotifier().handleRequest(vaadinSession,
+ vaadinRequest, vaadinResponse);
/* Handle the request */
if (requestType == RequestType.FILE_UPLOAD) {
- // UI is resolved in handleFileUpload by
- // PortletCommunicationManager
- communicationManager.handleFileUpload(vaadinSession,
+ new FileUploadHandler().handleRequest(vaadinSession,
vaadinRequest, vaadinResponse);
return;
} else if (requestType == RequestType.BROWSER_DETAILS) {
- communicationManager.handleBrowserDetailsRequest(
- vaadinRequest, vaadinResponse, vaadinSession);
+ new UIInitHandler().handleRequest(vaadinSession,
+ vaadinRequest, vaadinResponse);
return;
} else if (requestType == RequestType.UIDL) {
// Handles AJAX UIDL requests
- communicationManager.handleUidlRequest(vaadinRequest,
- vaadinResponse, portletWrapper, uI);
+ new UidlRequestHandler(portletWrapper).handleRequest(
+ vaadinSession, vaadinRequest, vaadinResponse);
- // Ensure that the browser does not cache UIDL
- // responses.
- // iOS 6 Safari requires this (#9732)
- response.setProperty("Cache-Control", "no-cache");
return;
} else {
handleOtherRequest(vaadinRequest, vaadinResponse,
requestType, vaadinSession,
- communicationManager);
+ vaadinSession.getCommunicationManager());
}
} catch (final SessionExpiredException e) {
// TODO Figure out a better way to deal with
// SessionExpiredExceptions
getLogger().finest("A user session has expired");
- } catch (final GeneralSecurityException e) {
- // TODO Figure out a better way to deal with
- // GeneralSecurityExceptions
- getLogger()
- .fine("General security exception, the security key was probably incorrect.");
} catch (final Throwable e) {
handleServiceException(vaadinRequest, vaadinResponse,
vaadinSession, e);
@@ -566,7 +528,7 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
private void handleOtherRequest(VaadinPortletRequest request,
VaadinResponse response, RequestType requestType,
VaadinSession vaadinSession,
- PortletCommunicationManager communicationManager)
+ AbstractCommunicationManager communicationManager)
throws PortletException, IOException, MalformedURLException {
if (requestType == RequestType.APP || requestType == RequestType.RENDER) {
if (!communicationManager.handleOtherRequest(request, response)) {
diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java
index 11a7439c66..fefa8699e1 100644
--- a/server/src/com/vaadin/server/VaadinServlet.java
+++ b/server/src/com/vaadin/server/VaadinServlet.java
@@ -24,7 +24,6 @@ import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
@@ -35,15 +34,18 @@ import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.vaadin.sass.internal.ScssStylesheet;
import com.vaadin.server.AbstractCommunicationManager.Callback;
+import com.vaadin.server.communication.FileUploadHandler;
+import com.vaadin.server.communication.HeartbeatHandler;
+import com.vaadin.server.communication.PublishedFileHandler;
+import com.vaadin.server.communication.UIInitHandler;
+import com.vaadin.server.communication.UidlRequestHandler;
import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.ui.UI;
import com.vaadin.util.CurrentInstance;
@SuppressWarnings("serial")
@@ -285,49 +287,29 @@ public class VaadinServlet extends HttpServlet implements Constants {
return;
}
- CommunicationManager communicationManager = (CommunicationManager) vaadinSession
- .getCommunicationManager();
-
if (requestType == RequestType.PUBLISHED_FILE) {
- communicationManager.servePublishedFile(request, response);
+ new PublishedFileHandler().handleRequest(vaadinSession,
+ request, response);
return;
} else if (requestType == RequestType.HEARTBEAT) {
- communicationManager.handleHeartbeatRequest(request, response,
- vaadinSession);
+ new HeartbeatHandler().handleRequest(vaadinSession, request,
+ response);
return;
- }
-
- /* Update browser information from the request */
- vaadinSession.getBrowser().updateRequestDetails(request);
-
- /* Handle the request */
- if (requestType == RequestType.FILE_UPLOAD) {
- // UI is resolved in communication manager
- communicationManager.handleFileUpload(vaadinSession, request,
+ } else if (requestType == RequestType.FILE_UPLOAD) {
+ new FileUploadHandler().handleRequest(vaadinSession, request,
response);
return;
} else if (requestType == RequestType.UIDL) {
- UI uI = getService().findUI(request);
- if (uI == null) {
- throw new ServletException(ERROR_NO_UI_FOUND);
- }
- // Handles AJAX UIDL requests
- communicationManager.handleUidlRequest(request, response,
- servletWrapper, uI);
-
- // Ensure that the browser does not cache UIDL responses.
- // iOS 6 Safari requires this (#9732)
- response.setHeader("Cache-Control", "no-cache");
-
+ new UidlRequestHandler(servletWrapper).handleRequest(
+ vaadinSession, request, response);
return;
} else if (requestType == RequestType.BROWSER_DETAILS) {
// Browser details - not related to a specific UI
- communicationManager.handleBrowserDetailsRequest(request,
- response, vaadinSession);
+ new UIInitHandler().handleRequest(vaadinSession, request,
+ response);
return;
- }
-
- if (communicationManager.handleOtherRequest(request, response)) {
+ } else if (vaadinSession.getCommunicationManager()
+ .handleOtherRequest(request, response)) {
return;
}
@@ -337,8 +319,6 @@ public class VaadinServlet extends HttpServlet implements Constants {
} catch (final SessionExpiredException e) {
// Session has expired, notify user
handleServiceSessionExpired(request, response);
- } catch (final GeneralSecurityException e) {
- handleServiceSecurityException(request, response);
} catch (final Throwable e) {
handleServiceException(request, response, vaadinSession, e);
} finally {
@@ -442,7 +422,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
*/
@Deprecated
protected void criticalNotification(VaadinServletRequest request,
- HttpServletResponse response, String caption, String message,
+ VaadinServletResponse response, String caption, String message,
String details, String url) throws IOException {
if (ServletPortletHelper.isUIDLRequest(request)) {
@@ -493,9 +473,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
output += "</a>";
}
writeResponse(response, "text/html; charset=UTF-8", output);
-
}
-
}
/**
@@ -511,7 +489,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
private void writeResponse(HttpServletResponse response,
String contentType, String output) throws IOException {
response.setContentType(contentType);
- final ServletOutputStream out = response.getOutputStream();
+ final OutputStream out = response.getOutputStream();
// Set the response type
final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(out, "UTF-8")));
diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java
index 4122f053ae..28b92e28b8 100644
--- a/server/src/com/vaadin/server/WebBrowser.java
+++ b/server/src/com/vaadin/server/WebBrowser.java
@@ -413,7 +413,7 @@ public class WebBrowser implements Serializable {
* @param request
* the Vaadin request to read the information from
*/
- void updateRequestDetails(VaadinRequest request) {
+ public void updateRequestDetails(VaadinRequest request) {
locale = request.getLocale();
address = request.getRemoteAddr();
secureConnection = request.isSecure();
diff --git a/server/src/com/vaadin/server/AbstractStreamingEvent.java b/server/src/com/vaadin/server/communication/AbstractStreamingEvent.java
index b7bf4e042f..054bc14f2d 100644
--- a/server/src/com/vaadin/server/AbstractStreamingEvent.java
+++ b/server/src/com/vaadin/server/communication/AbstractStreamingEvent.java
@@ -13,8 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.StreamVariable.StreamingEvent;
/**
diff --git a/server/src/com/vaadin/server/communication/ClientRpcWriter.java b/server/src/com/vaadin/server/communication/ClientRpcWriter.java
new file mode 100644
index 0000000000..285adac7a5
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ClientRpcWriter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ClientMethodInvocation;
+import com.vaadin.server.EncodeResult;
+import com.vaadin.server.JsonCodec;
+import com.vaadin.server.PaintException;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes {@link ClientRpc client RPC} invocations to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ClientRpcWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing all pending client RPC invocations in the
+ * given UI.
+ *
+ * @param ui
+ * The {@link UI} whose RPC calls to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(ui
+ .getConnectorTracker().getDirtyVisibleConnectors());
+
+ JSONArray rpcCalls = new JSONArray();
+ for (ClientMethodInvocation invocation : pendingInvocations) {
+ // add invocation to rpcCalls
+ try {
+ JSONArray invocationJson = new JSONArray();
+ invocationJson.put(invocation.getConnector().getConnectorId());
+ invocationJson.put(invocation.getInterfaceName());
+ invocationJson.put(invocation.getMethodName());
+ JSONArray paramJson = new JSONArray();
+ for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
+ Type parameterType = invocation.getParameterTypes()[i];
+ Object referenceParameter = null;
+ // TODO Use default values for RPC parameter types
+ // if (!JsonCodec.isInternalType(parameterType)) {
+ // try {
+ // referenceParameter = parameterType.newInstance();
+ // } catch (Exception e) {
+ // logger.log(Level.WARNING,
+ // "Error creating reference object for parameter of type "
+ // + parameterType.getName());
+ // }
+ // }
+ EncodeResult encodeResult = JsonCodec.encode(
+ invocation.getParameters()[i], referenceParameter,
+ parameterType, ui.getConnectorTracker());
+ paramJson.put(encodeResult.getEncodedValue());
+ }
+ invocationJson.put(paramJson);
+ rpcCalls.put(invocationJson);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to serialize RPC method call parameters for connector "
+ + invocation.getConnector().getConnectorId()
+ + " method " + invocation.getInterfaceName()
+ + "." + invocation.getMethodName() + ": "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(rpcCalls.toString());
+ }
+
+ /**
+ * Collects all pending RPC calls from listed {@link ClientConnector}s and
+ * clears their RPC queues.
+ *
+ * @param rpcPendingQueue
+ * list of {@link ClientConnector} of interest
+ * @return ordered list of pending RPC calls
+ */
+ private Collection<ClientMethodInvocation> collectPendingRpcCalls(
+ Collection<ClientConnector> rpcPendingQueue) {
+ List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
+ for (ClientConnector connector : rpcPendingQueue) {
+ List<ClientMethodInvocation> paintablePendingRpc = connector
+ .retrievePendingRpcCalls();
+ if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
+ List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
+ int totalCalls = pendingInvocations.size()
+ + paintablePendingRpc.size();
+ pendingInvocations = new ArrayList<ClientMethodInvocation>(
+ totalCalls);
+
+ // merge two ordered comparable lists
+ for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
+ if (paintableIndex >= paintablePendingRpc.size()
+ || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
+ .get(oldIndex))
+ .compareTo(paintablePendingRpc
+ .get(paintableIndex)) <= 0)) {
+ pendingInvocations.add(oldPendingRpc.get(oldIndex++));
+ } else {
+ pendingInvocations.add(paintablePendingRpc
+ .get(paintableIndex++));
+ }
+ }
+ }
+ }
+ return pendingInvocations;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java
new file mode 100644
index 0000000000..0d1370528d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.PaintException;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes a connector hierarchy to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ConnectorHierarchyWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing the connector hierarchy (parent-child
+ * mappings) of the dirty connectors in the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose hierarchy to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject hierarchyInfo = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ String connectorId = connector.getConnectorId();
+ JSONArray children = new JSONArray();
+
+ for (ClientConnector child : AbstractClientConnector
+ .getAllChildrenIterable(connector)) {
+ if (AbstractCommunicationManager
+ .isConnectorVisibleToClient(child)) {
+ children.put(child.getConnectorId());
+ }
+ }
+ try {
+ hierarchyInfo.put(connectorId, children);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to send hierarchy information about "
+ + connectorId + " to the client: "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(hierarchyInfo.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java b/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java
new file mode 100644
index 0000000000..eaa1c83ff2
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes connector type mappings to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ConnectorTypeWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing connector-ID-to-type-ID mappings for each
+ * dirty Connector in the given UI.
+ *
+ * @param ui
+ * The {@link UI} containing dirty connectors
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @param target
+ * The paint target containing the connector type IDs.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer, PaintTarget target)
+ throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject connectorTypes = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ String connectorType = target.getTag(connector);
+ try {
+ connectorTypes.put(connector.getConnectorId(), connectorType);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to send connector type for connector "
+ + connector.getConnectorId() + ": "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(connectorTypes.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java
new file mode 100644
index 0000000000..444e0c64cd
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.NoInputStreamException;
+import com.vaadin.server.NoOutputStreamException;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.StreamVariable;
+import com.vaadin.server.StreamVariable.StreamingEndEvent;
+import com.vaadin.server.StreamVariable.StreamingErrorEvent;
+import com.vaadin.server.UploadException;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles a file upload request submitted via an Upload component.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class FileUploadHandler implements RequestHandler {
+
+ /**
+ * Stream that extracts content from another stream until the boundary
+ * string is encountered.
+ *
+ * Public only for unit tests, should be considered private for all other
+ * purposes.
+ */
+ public static class SimpleMultiPartInputStream extends InputStream {
+
+ /**
+ * Counter of how many characters have been matched to boundary string
+ * from the stream
+ */
+ int matchedCount = -1;
+
+ /**
+ * Used as pointer when returning bytes after partly matched boundary
+ * string.
+ */
+ int curBoundaryIndex = 0;
+ /**
+ * The byte found after a "promising start for boundary"
+ */
+ private int bufferedByte = -1;
+ private boolean atTheEnd = false;
+
+ private final char[] boundary;
+
+ private final InputStream realInputStream;
+
+ public SimpleMultiPartInputStream(InputStream realInputStream,
+ String boundaryString) {
+ boundary = (CRLF + DASHDASH + boundaryString).toCharArray();
+ this.realInputStream = realInputStream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (atTheEnd) {
+ // End boundary reached, nothing more to read
+ return -1;
+ } else if (bufferedByte >= 0) {
+ /* Purge partially matched boundary if there was such */
+ return getBuffered();
+ } else if (matchedCount != -1) {
+ /*
+ * Special case where last "failed" matching ended with first
+ * character from boundary string
+ */
+ return matchForBoundary();
+ } else {
+ int fromActualStream = realInputStream.read();
+ if (fromActualStream == -1) {
+ // unexpected end of stream
+ throw new IOException(
+ "The multipart stream ended unexpectedly");
+ }
+ if (boundary[0] == fromActualStream) {
+ /*
+ * If matches the first character in boundary string, start
+ * checking if the boundary is fetched.
+ */
+ return matchForBoundary();
+ }
+ return fromActualStream;
+ }
+ }
+
+ /**
+ * Reads the input to expect a boundary string. Expects that the first
+ * character has already been matched.
+ *
+ * @return -1 if the boundary was matched, else returns the first byte
+ * from boundary
+ * @throws IOException
+ */
+ private int matchForBoundary() throws IOException {
+ matchedCount = 0;
+ /*
+ * Going to "buffered mode". Read until full boundary match or a
+ * different character.
+ */
+ while (true) {
+ matchedCount++;
+ if (matchedCount == boundary.length) {
+ /*
+ * The whole boundary matched so we have reached the end of
+ * file
+ */
+ atTheEnd = true;
+ return -1;
+ }
+ int fromActualStream = realInputStream.read();
+ if (fromActualStream != boundary[matchedCount]) {
+ /*
+ * Did not find full boundary, cache the mismatching byte
+ * and start returning the partially matched boundary.
+ */
+ bufferedByte = fromActualStream;
+ return getBuffered();
+ }
+ }
+ }
+
+ /**
+ * Returns the partly matched boundary string and the byte following
+ * that.
+ *
+ * @return
+ * @throws IOException
+ */
+ private int getBuffered() throws IOException {
+ int b;
+ if (matchedCount == 0) {
+ // The boundary has been returned, return the buffered byte.
+ b = bufferedByte;
+ bufferedByte = -1;
+ matchedCount = -1;
+ } else {
+ b = boundary[curBoundaryIndex++];
+ if (curBoundaryIndex == matchedCount) {
+ // The full boundary has been returned, remaining is the
+ // char that did not match the boundary.
+
+ curBoundaryIndex = 0;
+ if (bufferedByte != boundary[0]) {
+ /*
+ * next call for getBuffered will return the
+ * bufferedByte that came after the partial boundary
+ * match
+ */
+ matchedCount = 0;
+ } else {
+ /*
+ * Special case where buffered byte again matches the
+ * boundaryString. This could be the start of the real
+ * end boundary.
+ */
+ matchedCount = 0;
+ bufferedByte = -1;
+ }
+ }
+ }
+ if (b == -1) {
+ throw new IOException("The multipart stream ended unexpectedly");
+ }
+ return b;
+ }
+ }
+
+ private static class UploadInterruptedException extends Exception {
+ public UploadInterruptedException() {
+ super("Upload interrupted by other thread");
+ }
+ }
+
+ private static final int LF = "\n".getBytes()[0];
+
+ private static final String CRLF = "\r\n";
+
+ private static final String UTF8 = "UTF-8";
+
+ private static final String DASHDASH = "--";
+
+ /* Same as in apache commons file upload library that was previously used. */
+ private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ /*
+ * URI pattern: APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY] See
+ * #createReceiverUrl
+ */
+
+ String pathInfo = request.getPathInfo();
+ // strip away part until the data we are interested starts
+ int startOfData = pathInfo
+ .indexOf(ServletPortletHelper.UPLOAD_URL_PREFIX)
+ + ServletPortletHelper.UPLOAD_URL_PREFIX.length();
+ String uppUri = pathInfo.substring(startOfData);
+ String[] parts = uppUri.split("/", 4); // 0= UIid, 1 = cid, 2= name, 3
+ // = sec key
+ String uiId = parts[0];
+ String connectorId = parts[1];
+ String variableName = parts[2];
+ UI uI = session.getUIById(Integer.parseInt(uiId));
+ UI.setCurrent(uI);
+
+ StreamVariable streamVariable = uI.getConnectorTracker()
+ .getStreamVariable(connectorId, variableName);
+ String secKey = uI.getConnectorTracker().getSeckey(streamVariable);
+ if (secKey.equals(parts[3])) {
+
+ ClientConnector source = session.getCommunicationManager()
+ .getConnector(uI, connectorId);
+ String contentType = request.getContentType();
+ if (contentType.contains("boundary")) {
+ // Multipart requests contain boundary string
+ doHandleSimpleMultipartFileUpload(session, request, response,
+ streamVariable, variableName, source,
+ contentType.split("boundary=")[1]);
+ } else {
+ // if boundary string does not exist, the posted file is from
+ // XHR2.post(File)
+ doHandleXhrFilePost(session, request, response, streamVariable,
+ variableName, source, request.getContentLength());
+ }
+ } else {
+ // TODO Should rethink error handling
+ }
+
+ return true;
+ }
+
+ private static String readLine(InputStream stream) throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ int readByte = stream.read();
+ while (readByte != LF) {
+ bout.write(readByte);
+ readByte = stream.read();
+ }
+ byte[] bytes = bout.toByteArray();
+ return new String(bytes, 0, bytes.length - 1, UTF8);
+ }
+
+ /**
+ * Method used to stream content from a multipart request (either from
+ * servlet or portlet request) to given StreamVariable
+ *
+ *
+ * @param request
+ * @param response
+ * @param streamVariable
+ * @param owner
+ * @param boundary
+ * @throws IOException
+ */
+ protected void doHandleSimpleMultipartFileUpload(VaadinSession session,
+ VaadinRequest request, VaadinResponse response,
+ StreamVariable streamVariable, String variableName,
+ ClientConnector owner, String boundary) throws IOException {
+ // multipart parsing, supports only one file for request, but that is
+ // fine for our current terminal
+
+ final InputStream inputStream = request.getInputStream();
+
+ int contentLength = request.getContentLength();
+
+ boolean atStart = false;
+ boolean firstFileFieldFound = false;
+
+ String rawfilename = "unknown";
+ String rawMimeType = "application/octet-stream";
+
+ /*
+ * Read the stream until the actual file starts (empty line). Read
+ * filename and content type from multipart headers.
+ */
+ while (!atStart) {
+ String readLine = readLine(inputStream);
+ contentLength -= (readLine.getBytes(UTF8).length + CRLF.length());
+ if (readLine.startsWith("Content-Disposition:")
+ && readLine.indexOf("filename=") > 0) {
+ rawfilename = readLine.replaceAll(".*filename=", "");
+ char quote = rawfilename.charAt(0);
+ rawfilename = rawfilename.substring(1);
+ rawfilename = rawfilename.substring(0,
+ rawfilename.indexOf(quote));
+ firstFileFieldFound = true;
+ } else if (firstFileFieldFound && readLine.equals("")) {
+ atStart = true;
+ } else if (readLine.startsWith("Content-Type")) {
+ rawMimeType = readLine.split(": ")[1];
+ }
+ }
+
+ contentLength -= (boundary.length() + CRLF.length() + 2
+ * DASHDASH.length() + CRLF.length());
+
+ /*
+ * Reads bytes from the underlying stream. Compares the read bytes to
+ * the boundary string and returns -1 if met.
+ *
+ * The matching happens so that if the read byte equals to the first
+ * char of boundary string, the stream goes to "buffering mode". In
+ * buffering mode bytes are read until the character does not match the
+ * corresponding from boundary string or the full boundary string is
+ * found.
+ *
+ * Note, if this is someday needed elsewhere, don't shoot yourself to
+ * foot and split to a top level helper class.
+ */
+ InputStream simpleMultiPartReader = new SimpleMultiPartInputStream(
+ inputStream, boundary);
+
+ /*
+ * Should report only the filename even if the browser sends the path
+ */
+ final String filename = removePath(rawfilename);
+ final String mimeType = rawMimeType;
+
+ try {
+ // TODO Shouldn't this check connectorEnabled?
+ if (owner == null) {
+ throw new UploadException(
+ "File upload ignored because the connector for the stream variable was not found");
+ }
+ if (owner instanceof Component) {
+ if (((Component) owner).isReadOnly()) {
+ throw new UploadException(
+ "Warning: file upload ignored because the componente was read-only");
+ }
+ }
+ boolean forgetVariable = streamToReceiver(session,
+ simpleMultiPartReader, streamVariable, filename, mimeType,
+ contentLength);
+ if (forgetVariable) {
+ cleanStreamVariable(owner, variableName);
+ }
+ } catch (Exception e) {
+ session.lock();
+ try {
+ session.getCommunicationManager()
+ .handleConnectorRelatedException(owner, e);
+ } finally {
+ session.unlock();
+ }
+ }
+ sendUploadResponse(request, response);
+
+ }
+
+ /**
+ * Used to stream plain file post (aka XHR2.post(File))
+ *
+ * @param request
+ * @param response
+ * @param streamVariable
+ * @param owner
+ * @param contentLength
+ * @throws IOException
+ */
+ protected void doHandleXhrFilePost(VaadinSession session,
+ VaadinRequest request, VaadinResponse response,
+ StreamVariable streamVariable, String variableName,
+ ClientConnector owner, int contentLength) throws IOException {
+
+ // These are unknown in filexhr ATM, maybe add to Accept header that
+ // is accessible in portlets
+ final String filename = "unknown";
+ final String mimeType = filename;
+ final InputStream stream = request.getInputStream();
+ try {
+ /*
+ * safe cast as in GWT terminal all variable owners are expected to
+ * be components.
+ */
+ Component component = (Component) owner;
+ if (component.isReadOnly()) {
+ throw new UploadException(
+ "Warning: file upload ignored because the component was read-only");
+ }
+ boolean forgetVariable = streamToReceiver(session, stream,
+ streamVariable, filename, mimeType, contentLength);
+ if (forgetVariable) {
+ cleanStreamVariable(owner, variableName);
+ }
+ } catch (Exception e) {
+ session.lock();
+ try {
+ session.getCommunicationManager()
+ .handleConnectorRelatedException(owner, e);
+ } finally {
+ session.unlock();
+ }
+ }
+ sendUploadResponse(request, response);
+ }
+
+ /**
+ * @param in
+ * @param streamVariable
+ * @param filename
+ * @param type
+ * @param contentLength
+ * @return true if the streamvariable has informed that the terminal can
+ * forget this variable
+ * @throws UploadException
+ */
+ protected final boolean streamToReceiver(VaadinSession session,
+ final InputStream in, StreamVariable streamVariable,
+ String filename, String type, int contentLength)
+ throws UploadException {
+ if (streamVariable == null) {
+ throw new IllegalStateException(
+ "StreamVariable for the post not found");
+ }
+
+ OutputStream out = null;
+ int totalBytes = 0;
+ StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
+ filename, type, contentLength);
+ try {
+ boolean listenProgress;
+ session.lock();
+ try {
+ streamVariable.streamingStarted(startedEvent);
+ out = streamVariable.getOutputStream();
+ listenProgress = streamVariable.listenProgress();
+ } finally {
+ session.unlock();
+ }
+
+ // Gets the output target stream
+ if (out == null) {
+ throw new NoOutputStreamException();
+ }
+
+ if (null == in) {
+ // No file, for instance non-existent filename in html upload
+ throw new NoInputStreamException();
+ }
+
+ final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
+ int bytesReadToBuffer = 0;
+ while ((bytesReadToBuffer = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesReadToBuffer);
+ totalBytes += bytesReadToBuffer;
+ if (listenProgress) {
+ // update progress if listener set and contentLength
+ // received
+ session.lock();
+ try {
+ StreamingProgressEventImpl progressEvent = new StreamingProgressEventImpl(
+ filename, type, contentLength, totalBytes);
+ streamVariable.onProgress(progressEvent);
+ } finally {
+ session.unlock();
+ }
+ }
+ if (streamVariable.isInterrupted()) {
+ throw new UploadInterruptedException();
+ }
+ }
+
+ // upload successful
+ out.close();
+ StreamingEndEvent event = new StreamingEndEventImpl(filename, type,
+ totalBytes);
+ session.lock();
+ try {
+ streamVariable.streamingFinished(event);
+ } finally {
+ session.unlock();
+ }
+
+ } catch (UploadInterruptedException e) {
+ // Download interrupted by application code
+ tryToCloseStream(out);
+ StreamingErrorEvent event = new StreamingErrorEventImpl(filename,
+ type, contentLength, totalBytes, e);
+ session.lock();
+ try {
+ streamVariable.streamingFailed(event);
+ } finally {
+ session.unlock();
+ }
+ // Note, we are not throwing interrupted exception forward as it is
+ // not a terminal level error like all other exception.
+ } catch (final Exception e) {
+ tryToCloseStream(out);
+ session.lock();
+ try {
+ StreamingErrorEvent event = new StreamingErrorEventImpl(
+ filename, type, contentLength, totalBytes, e);
+ streamVariable.streamingFailed(event);
+ // throw exception for terminal to be handled (to be passed to
+ // terminalErrorHandler)
+ throw new UploadException(e);
+ } finally {
+ session.unlock();
+ }
+ }
+ return startedEvent.isDisposed();
+ }
+
+ static void tryToCloseStream(OutputStream out) {
+ try {
+ // try to close output stream (e.g. file handle)
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e1) {
+ // NOP
+ }
+ }
+
+ /**
+ * Removes any possible path information from the filename and returns the
+ * filename. Separators / and \\ are used.
+ *
+ * @param name
+ * @return
+ */
+ private static String removePath(String filename) {
+ if (filename != null) {
+ filename = filename.replaceAll("^.*[/\\\\]", "");
+ }
+
+ return filename;
+ }
+
+ /**
+ * TODO document
+ *
+ * @param request
+ * @param response
+ * @throws IOException
+ */
+ protected void sendUploadResponse(VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ 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();
+ }
+
+ private void cleanStreamVariable(ClientConnector owner, String name) {
+ owner.getUI().getConnectorTracker()
+ .cleanStreamVariable(owner.getConnectorId(), name);
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/HeartbeatHandler.java b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
new file mode 100644
index 0000000000..75d4f870c1
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ui.ui.UIConstants;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles heartbeat requests. Heartbeat requests are periodically sent by the
+ * client-side to inform the server that the UI sending the heartbeat is still
+ * alive (the browser window is open, the connection is up) even when there are
+ * no UIDL requests for a prolonged period of time. UIs that do not receive
+ * either heartbeat or UIDL requests are eventually removed from the session and
+ * garbage collected.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class HeartbeatHandler implements RequestHandler {
+
+ /**
+ * Handles a heartbeat request for the given session. Reads the GET
+ * parameter named {@link UIConstants#UI_ID_PARAMETER} to identify the UI.
+ * If the UI is found in the session, sets it
+ * {@link UI#getLastHeartbeatTimestamp() heartbeat timestamp} to the current
+ * time. Otherwise, writes a HTTP Not Found error to the response.
+ */
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ UI ui = null;
+ try {
+ int uiId = Integer.parseInt(request
+ .getParameter(UIConstants.UI_ID_PARAMETER));
+ ui = session.getUIById(uiId);
+ } catch (NumberFormatException nfe) {
+ // null-check below handles this as well
+ }
+ if (ui != null) {
+ ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
+ // Ensure that the browser does not cache heartbeat responses.
+ // iOS 6 Safari requires this (#10370)
+ response.setHeader("Cache-Control", "no-cache");
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
+ }
+
+ return true;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/LegacyUidlWriter.java b/server/src/com/vaadin/server/communication/LegacyUidlWriter.java
new file mode 100644
index 0000000000..ad99a2d8b5
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/LegacyUidlWriter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.LegacyPaint;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.LegacyComponent;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes legacy UIDL changes to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class LegacyUidlWriter implements Serializable {
+
+ /**
+ * Writes a JSON array containing the changes of all dirty
+ * {@link LegacyComponent}s in the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose legacy changes to write
+ * @param writer
+ * The {@link Writer} to write the JSON with
+ * @param target
+ * The {@link PaintTarget} to use
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer, PaintTarget target)
+ throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ List<Component> legacyComponents = new ArrayList<Component>();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ // All Components that want to use paintContent must implement
+ // LegacyComponent
+ if (connector instanceof LegacyComponent) {
+ legacyComponents.add((Component) connector);
+ }
+ }
+ sortByHierarchy(legacyComponents);
+
+ writer.write("[");
+ for (Component c : legacyComponents) {
+ getLogger().fine(
+ "Painting LegacyComponent " + c.getClass().getName() + "@"
+ + Integer.toHexString(c.hashCode()));
+ target.startTag("change");
+ final String pid = c.getConnectorId();
+ target.addAttribute("pid", pid);
+ LegacyPaint.paint(c, target);
+ target.endTag("change");
+ }
+ writer.write("]");
+ }
+
+ private void sortByHierarchy(List<Component> paintables) {
+ // Vaadin 6 requires parents to be painted before children as component
+ // containers rely on that their updateFromUIDL method has been called
+ // before children start calling e.g. updateCaption
+ Collections.sort(paintables, new Comparator<Component>() {
+ @Override
+ public int compare(Component c1, Component c2) {
+ int depth1 = 0;
+ while (c1.getParent() != null) {
+ depth1++;
+ c1 = c1.getParent();
+ }
+ int depth2 = 0;
+ while (c2.getParent() != null) {
+ depth2++;
+ c2 = c2.getParent();
+ }
+ if (depth1 < depth2) {
+ return -1;
+ }
+ if (depth1 > depth2) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(LegacyUidlWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/LocaleWriter.java b/server/src/com/vaadin/server/communication/LocaleWriter.java
new file mode 100644
index 0000000000..c05649da19
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/LocaleWriter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Logger;
+
+/**
+ * Serializes locale information to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ * @deprecated See <a href="http://dev.vaadin.com/ticket/11378">ticket
+ * #11378</a>.
+ */
+@Deprecated
+public class LocaleWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing localized strings of the given locales.
+ *
+ * @param locales
+ * The list of {@link Locale}s to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ *
+ */
+ public void write(List<String> locales, Writer writer) throws IOException {
+
+ // Send locale informations to client
+ writer.write("[");
+ // TODO locales are currently sent on each request; this will be fixed
+ // by implementing #11378.
+ for (int pendingLocalesIndex = 0; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
+
+ final Locale l = generateLocale(locales.get(pendingLocalesIndex));
+ // Locale name
+ writer.write("{\"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();
+ writer.write("\"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] + "\""
+ + "],");
+ writer.write("\"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();
+ writer.write("\"sdn\":[\""
+ + // ShortDayNames
+ short_days[1] + "\",\"" + short_days[2] + "\",\""
+ + short_days[3] + "\",\"" + short_days[4] + "\",\""
+ + short_days[5] + "\",\"" + short_days[6] + "\",\""
+ + short_days[7] + "\"" + "],");
+ writer.write("\"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);
+ writer.write("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
+
+ /*
+ * Date formatting (MM/DD/YYYY etc.)
+ */
+
+ DateFormat dateFormat = DateFormat.getDateTimeInstance(
+ DateFormat.SHORT, DateFormat.SHORT, l);
+ if (!(dateFormat instanceof SimpleDateFormat)) {
+ getLogger().warning(
+ "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);
+ }
+
+ writer.write("\"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 + "\",");
+ writer.write("\"thc\":" + twelve_hour_clock + ",");
+ writer.write("\"hmd\":\"" + hour_min_delimiter + "\"");
+ if (twelve_hour_clock) {
+ final String[] ampm = dfs.getAmPmStrings();
+ writer.write(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
+ + "\"]");
+ }
+ writer.write("}");
+ if (pendingLocalesIndex < locales.size() - 1) {
+ writer.write(",");
+ }
+ }
+ writer.write("]"); // Close locales
+ }
+
+ /**
+ * Constructs a {@link Locale} instance to be sent to the client based on a
+ * short locale description string.
+ *
+ * @see #requireLocale(String)
+ *
+ * @param value
+ * @return
+ */
+ 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]);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(LocaleWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/MetadataWriter.java b/server/src/com/vaadin/server/communication/MetadataWriter.java
new file mode 100644
index 0000000000..7119e0ffeb
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/MetadataWriter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.List;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ComponentSizeValidator;
+import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.Window;
+
+/**
+ * Serializes miscellaneous metadata to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class MetadataWriter implements Serializable {
+
+ private int timeoutInterval = -1;
+
+ /**
+ * Writes a JSON object containing metadata related to the given UI.
+ *
+ * @param ui
+ * The UI whose metadata to write.
+ * @param writer
+ * The writer used.
+ * @param repaintAll
+ * Whether the client should repaint everything.
+ * @param analyzeLayouts
+ * Whether detected layout problems should be reported in client
+ * and server console.
+ * @param hilightedConnector
+ * The connector that should be highlighted on the client or null
+ * if none.
+ * @param messages
+ * a {@link SystemMessages} containing client-side error
+ * messages.
+ * @throws IOException
+ * If the serialization fails.
+ *
+ */
+ public void write(UI ui, Writer writer, boolean repaintAll,
+ boolean analyzeLayouts, ClientConnector hilightedConnector,
+ SystemMessages messages) throws IOException {
+
+ List<InvalidLayout> invalidComponentRelativeSizes = null;
+
+ if (analyzeLayouts) {
+ invalidComponentRelativeSizes = ComponentSizeValidator
+ .validateComponentRelativeSizes(ui.getContent(), null, null);
+
+ // Also check any existing subwindows
+ if (ui.getWindows() != null) {
+ for (Window subWindow : ui.getWindows()) {
+ invalidComponentRelativeSizes = ComponentSizeValidator
+ .validateComponentRelativeSizes(
+ subWindow.getContent(),
+ invalidComponentRelativeSizes, null);
+ }
+ }
+ }
+
+ writer.write("{");
+
+ boolean metaOpen = false;
+ if (repaintAll) {
+ metaOpen = true;
+ writer.write("\"repaintAll\":true");
+ if (analyzeLayouts) {
+ writer.write(", \"invalidLayouts\":");
+ writer.write("[");
+ if (invalidComponentRelativeSizes != null) {
+ boolean first = true;
+ for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
+ if (!first) {
+ writer.write(",");
+ } else {
+ first = false;
+ }
+ invalidLayout.reportErrors(new PrintWriter(writer),
+ System.err);
+ }
+ }
+ writer.write("]");
+ }
+ if (hilightedConnector != null) {
+ writer.write(", \"hl\":\"");
+ writer.write(hilightedConnector.getConnectorId());
+ writer.write("\"");
+ }
+ }
+
+ // meta instruction for client to enable auto-forward to
+ // sessionExpiredURL after timer expires.
+ if (messages != null && messages.getSessionExpiredMessage() == null
+ && messages.getSessionExpiredCaption() == null
+ && messages.isSessionExpiredNotificationEnabled()) {
+ int newTimeoutInterval = ui.getSession().getSession()
+ .getMaxInactiveInterval();
+ if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
+ String escapedURL = messages.getSessionExpiredURL() == null ? ""
+ : messages.getSessionExpiredURL().replace("/", "\\/");
+ if (metaOpen) {
+ writer.write(",");
+ }
+ writer.write("\"timedRedirect\":{\"interval\":"
+ + (newTimeoutInterval + 15) + ",\"url\":\""
+ + escapedURL + "\"}");
+ metaOpen = true;
+ }
+ timeoutInterval = newTimeoutInterval;
+ }
+ writer.write("}");
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PortletListenerNotifier.java b/server/src/com/vaadin/server/communication/PortletListenerNotifier.java
new file mode 100644
index 0000000000..aff5c8a80e
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PortletListenerNotifier.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.EventRequest;
+import javax.portlet.EventResponse;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.VaadinPortletRequest;
+import com.vaadin.server.VaadinPortletResponse;
+import com.vaadin.server.VaadinPortletSession;
+import com.vaadin.server.VaadinPortletSession.PortletListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.UI;
+
+/**
+ * Notifies {@link PortletListener}s of a received portlet request.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PortletListenerNotifier implements RequestHandler {
+
+ /**
+ * Fires portlet request events to any {@link PortletListener}s registered
+ * to the given session using
+ * {@link VaadinPortletSession#addPortletListener(PortletListener)}. The
+ * PortletListener method corresponding to the request type is invoked.
+ */
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ VaadinPortletSession sess = (VaadinPortletSession) session;
+ PortletRequest req = ((VaadinPortletRequest) request)
+ .getPortletRequest();
+ PortletResponse resp = ((VaadinPortletResponse) response)
+ .getPortletResponse();
+
+ // Finds the right UI
+ UI uI = null;
+ if (ServletPortletHelper.isUIDLRequest(request)) {
+ uI = session.getService().findUI(request);
+ }
+
+ if (request instanceof RenderRequest) {
+ sess.firePortletRenderRequest(uI, (RenderRequest) req,
+ (RenderResponse) resp);
+ } else if (request instanceof ActionRequest) {
+ sess.firePortletActionRequest(uI, (ActionRequest) req,
+ (ActionResponse) resp);
+ } else if (request instanceof EventRequest) {
+ sess.firePortletEventRequest(uI, (EventRequest) req,
+ (EventResponse) resp);
+ } else if (request instanceof ResourceRequest) {
+ sess.firePortletResourceRequest(uI, (ResourceRequest) req,
+ (ResourceResponse) resp);
+ }
+
+ return false;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PublishedFileHandler.java b/server/src/com/vaadin/server/communication/PublishedFileHandler.java
new file mode 100644
index 0000000000..3261dc7b9f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PublishedFileHandler.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.annotations.StyleSheet;
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.Constants;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+
+/**
+ * Serves a connector resource from the classpath if the resource has previously
+ * been registered by calling
+ * {@link AbstractCommunicationManager#registerDependency(String, Class)}.
+ * Sending arbitrary files from the classpath is prevented by only accepting
+ * resource names that have explicitly been registered. Resources can currently
+ * only be registered by including a {@link JavaScript} or {@link StyleSheet}
+ * annotation on a Connector class.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PublishedFileHandler implements RequestHandler {
+
+ /**
+ * Writes the connector resource identified by the request URI to the
+ * response. If a published resource corresponding to the URI path is not
+ * found, writes a HTTP Not Found error to the response.
+ */
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ String pathInfo = request.getPathInfo();
+ // + 2 to also remove beginning and ending slashes
+ String fileName = pathInfo
+ .substring(ApplicationConstants.PUBLISHED_FILE_PATH.length() + 2);
+
+ final String mimetype = response.getService().getMimeType(fileName);
+
+ // Security check: avoid accidentally serving from the UI of the
+ // classpath instead of relative to the context class
+ if (fileName.startsWith("/")) {
+ getLogger().warning(
+ "Published file request starting with / rejected: "
+ + fileName);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // Check that the resource name has been registered
+ // TODO PUSH refactor - is the synchronization correct at all?
+ Class<?> context;
+ synchronized (session.getCommunicationManager().getDependencies()) {
+ context = session.getCommunicationManager().getDependencies()
+ .get(fileName);
+ }
+
+ // Security check: don't serve resource if the name hasn't been
+ // registered in the map
+ if (context == null) {
+ getLogger().warning(
+ "Rejecting published file request for file that has not been published: "
+ + fileName);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // Resolve file relative to the location of the context class
+ InputStream in = context.getResourceAsStream(fileName);
+ if (in == null) {
+ getLogger().warning(
+ fileName + " published by " + context.getName()
+ + " not found. Verify that the file "
+ + context.getPackage().getName().replace('.', '/')
+ + '/' + fileName
+ + " is available on the classpath.");
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // TODO Check and set cache headers
+
+ OutputStream out = null;
+ try {
+ if (mimetype != null) {
+ response.setContentType(mimetype);
+ }
+
+ out = response.getOutputStream();
+
+ final byte[] buffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
+
+ int bytesRead = 0;
+ while ((bytesRead = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ out.flush();
+ } finally {
+ try {
+ in.close();
+ } catch (Exception e) {
+ // Do nothing
+ }
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) {
+ // Do nothing
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(PublishedFileHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ResourceWriter.java b/server/src/com/vaadin/server/communication/ResourceWriter.java
new file mode 100644
index 0000000000..3ba3f3f598
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ResourceWriter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.JsonPaintTarget;
+import com.vaadin.ui.CustomLayout;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes resources to JSON. Currently only used for {@link CustomLayout}
+ * templates.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ResourceWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing registered resources.
+ *
+ * @param ui
+ * The {@link UI} whose resources to write.
+ * @param writer
+ * The {@link Writer} to use.
+ * @param target
+ * The {@link JsonPaintTarget} containing the resources.
+ * @throws IOException
+ */
+ public void write(UI ui, Writer writer, JsonPaintTarget target)
+ throws IOException {
+
+ // TODO PUSH Refactor so that this is not needed
+ AbstractCommunicationManager manager = ui.getSession()
+ .getCommunicationManager();
+
+ // Precache custom layouts
+
+ // TODO We should only precache the layouts that are not
+ // cached already (plagiate from usedPaintableTypes)
+
+ writer.write("{");
+ int resourceIndex = 0;
+ for (final Iterator<Object> i = target.getUsedResources().iterator(); i
+ .hasNext();) {
+ final String resource = (String) i.next();
+ InputStream is = null;
+ try {
+ is = manager.getThemeResourceAsStream(ui, manager.getTheme(ui),
+ resource);
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.FINER,
+ "Failed to get theme resource stream.", e);
+ }
+ if (is != null) {
+
+ writer.write((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
+ getLogger().log(Level.INFO, "Resource transfer failed", e);
+ }
+ writer.write("\""
+ + JsonPaintTarget.escapeJSON(layout.toString()) + "\"");
+ } else {
+ // FIXME: Handle exception
+ getLogger().severe("CustomLayout not found: " + resource);
+ }
+ }
+ writer.write("}");
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ResourceWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
new file mode 100644
index 0000000000..8d33ea8f85
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.lang.reflect.Type;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.AbstractCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.JsonCodec;
+import com.vaadin.server.ServerRpcManager;
+import com.vaadin.server.ServerRpcManager.RpcInvocationException;
+import com.vaadin.server.ServerRpcMethodInvocation;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VariableOwner;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
+import com.vaadin.shared.communication.MethodInvocation;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.communication.UidlValue;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles a client-to-server message containing serialized {@link ServerRpc
+ * server RPC} invocations.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ServerRpcHandler implements Serializable {
+
+ /* Variable records indexes */
+ public static final char VAR_BURST_SEPARATOR = '\u001d';
+
+ public static final char VAR_ESCAPE_CHARACTER = '\u001b';
+
+ private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+ // 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";
+
+ /**
+ * Reads JSON containing zero or more serialized RPC calls (including legacy
+ * variable changes) and executes the calls.
+ *
+ * @param ui
+ * The {@link UI} receiving the calls.
+ * @param reader
+ * The {@link Reader} used to read the JSON.
+ * @param request
+ * @throws IOException
+ * If reading the message fails.
+ * @throws InvalidUIDLSecurityKeyException
+ * If the received security key does not match the one stored in
+ * the session.
+ * @throws JSONException
+ * If deserializing the JSON fails.
+ */
+ public void handleRpc(UI ui, Reader reader, VaadinRequest request)
+ throws IOException, InvalidUIDLSecurityKeyException, JSONException {
+
+ // Verify that there's an UI
+ if (ui == null) {
+ // This should not happen, no windows exists but
+ // session is still open.
+ getLogger().warning("Could not get UI for session");
+ return;
+ }
+
+ ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());
+
+ // Change all variables based on request parameters
+ handleVariables(ui, reader, request);
+ }
+
+ private void handleVariables(UI uI, Reader reader, VaadinRequest request)
+ throws IOException, InvalidUIDLSecurityKeyException, JSONException {
+
+ String changes = getMessage(reader);
+
+ final String[] bursts = changes.split(String
+ .valueOf(VAR_BURST_SEPARATOR));
+
+ if (bursts.length > 2) {
+ throw new RuntimeException(
+ "Multiple variable bursts not supported in Vaadin 7");
+ } else if (bursts.length <= 2) {
+ // The client sometimes sends empty messages, this is probably a bug
+ return;
+ }
+
+ // Security: double cookie submission pattern unless disabled by
+ // property
+ if (uI.getSession().getConfiguration().isXsrfProtectionEnabled()) {
+ if (bursts.length == 1 && "init".equals(bursts[0])) {
+ // init request; don't handle any variables, key sent in
+ // response.
+ // TODO This seems to be dead code
+ request.setAttribute(
+ AbstractCommunicationManager.WRITE_SECURITY_TOKEN_FLAG,
+ true);
+ return;
+ } else {
+ // ApplicationServlet has stored the security token in the
+ // session; check that it matched the one sent in the UIDL
+ String sessId = (String) uI
+ .getSession()
+ .getSession()
+ .getAttribute(
+ ApplicationConstants.UIDL_SECURITY_TOKEN_ID);
+
+ if (sessId == null || !sessId.equals(bursts[0])) {
+ throw new InvalidUIDLSecurityKeyException("");
+ }
+ }
+
+ }
+ handleBurst(uI, unescapeBurst(bursts[1]));
+ }
+
+ /**
+ * Processes a message burst received from the client.
+ *
+ * A burst can contain any number of RPC calls, including legacy variable
+ * change calls that are processed separately.
+ *
+ * Consecutive changes to the value of the same variable are combined and
+ * changeVariables() is only called once for them. This preserves the Vaadin
+ * 6 semantics for components and add-ons that do not use Vaadin 7 RPC
+ * directly.
+ *
+ * @param source
+ * @param uI
+ * the UI receiving the burst
+ * @param burst
+ * the content of the burst as a String to be parsed
+ */
+ private void handleBurst(UI uI, String burst) {
+ // TODO PUSH Refactor so that this is not needed
+ AbstractCommunicationManager manager = uI.getSession()
+ .getCommunicationManager();
+
+ try {
+ Set<Connector> enabledConnectors = new HashSet<Connector>();
+
+ List<MethodInvocation> invocations = parseInvocations(
+ uI.getConnectorTracker(), burst);
+ for (MethodInvocation invocation : invocations) {
+ final ClientConnector connector = manager.getConnector(uI,
+ invocation.getConnectorId());
+
+ if (connector != null && connector.isConnectorEnabled()) {
+ enabledConnectors.add(connector);
+ }
+ }
+
+ for (int i = 0; i < invocations.size(); i++) {
+ MethodInvocation invocation = invocations.get(i);
+
+ final ClientConnector connector = manager.getConnector(uI,
+ invocation.getConnectorId());
+ if (connector == null) {
+ getLogger()
+ .log(Level.WARNING,
+ "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
+ new Object[] { invocation.getConnectorId(),
+ invocation.getInterfaceName(),
+ invocation.getMethodName() });
+ continue;
+ }
+
+ if (!enabledConnectors.contains(connector)) {
+
+ if (invocation instanceof LegacyChangeVariablesInvocation) {
+ LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
+ // TODO convert window close to a separate RPC call and
+ // handle above - not a variable change
+
+ // Handle special case where window-close is called
+ // after the window has been removed from the
+ // application or the application has closed
+ Map<String, Object> changes = legacyInvocation
+ .getVariableChanges();
+ if (changes.size() == 1 && changes.containsKey("close")
+ && Boolean.TRUE.equals(changes.get("close"))) {
+ // Silently ignore this
+ continue;
+ }
+ }
+
+ // Connector is disabled, log a warning and move to the next
+ String msg = "Ignoring RPC call for disabled connector "
+ + connector.getClass().getName();
+ if (connector instanceof Component) {
+ String caption = ((Component) connector).getCaption();
+ if (caption != null) {
+ msg += ", caption=" + caption;
+ }
+ }
+ getLogger().warning(msg);
+ continue;
+ }
+
+ if (invocation instanceof ServerRpcMethodInvocation) {
+ try {
+ ServerRpcManager.applyInvocation(connector,
+ (ServerRpcMethodInvocation) invocation);
+ } catch (RpcInvocationException e) {
+ manager.handleConnectorRelatedException(connector, e);
+ }
+ } else {
+
+ // All code below is for legacy variable changes
+ LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
+ Map<String, Object> changes = legacyInvocation
+ .getVariableChanges();
+ try {
+ if (connector instanceof VariableOwner) {
+ // The source parameter is never used anywhere
+ changeVariables(null, (VariableOwner) connector,
+ changes);
+ } else {
+ throw new IllegalStateException(
+ "Received legacy variable change for "
+ + connector.getClass().getName()
+ + " ("
+ + connector.getConnectorId()
+ + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
+ + changes.keySet());
+ }
+ } catch (Exception e) {
+ manager.handleConnectorRelatedException(connector, e);
+ }
+ }
+ }
+ } catch (JSONException e) {
+ getLogger().warning(
+ "Unable to parse RPC call from the client: "
+ + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Parse a message burst from the client into a list of MethodInvocation
+ * instances.
+ *
+ * @param connectorTracker
+ * The ConnectorTracker used to lookup connectors
+ * @param burst
+ * message string (JSON)
+ * @return list of MethodInvocation to perform
+ * @throws JSONException
+ */
+ private List<MethodInvocation> parseInvocations(
+ ConnectorTracker connectorTracker, String burst)
+ throws JSONException {
+ JSONArray invocationsJson = new JSONArray(burst);
+
+ ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
+
+ MethodInvocation previousInvocation = null;
+ // parse JSON to MethodInvocations
+ for (int i = 0; i < invocationsJson.length(); ++i) {
+
+ JSONArray invocationJson = invocationsJson.getJSONArray(i);
+
+ MethodInvocation invocation = parseInvocation(invocationJson,
+ previousInvocation, connectorTracker);
+ if (invocation != null) {
+ // Can be null if the invocation was a legacy invocation and it
+ // was merged with the previous one or if the invocation was
+ // rejected because of an error.
+ invocations.add(invocation);
+ previousInvocation = invocation;
+ }
+ }
+ return invocations;
+ }
+
+ private MethodInvocation parseInvocation(JSONArray invocationJson,
+ MethodInvocation previousInvocation,
+ ConnectorTracker connectorTracker) throws JSONException {
+ String connectorId = invocationJson.getString(0);
+ String interfaceName = invocationJson.getString(1);
+ String methodName = invocationJson.getString(2);
+
+ if (connectorTracker.getConnector(connectorId) == null
+ && !connectorId
+ .equals(ApplicationConstants.DRAG_AND_DROP_CONNECTOR_ID)) {
+ getLogger()
+ .log(Level.WARNING,
+ "RPC call to "
+ + interfaceName
+ + "."
+ + methodName
+ + " received for connector "
+ + connectorId
+ + " but no such connector could be found. Resynchronizing client.");
+ // This is likely an out of sync issue (client tries to update a
+ // connector which is not present). Force resync.
+ connectorTracker.markAllConnectorsDirty();
+ return null;
+ }
+
+ JSONArray parametersJson = invocationJson.getJSONArray(3);
+
+ if (LegacyChangeVariablesInvocation.isLegacyVariableChange(
+ interfaceName, methodName)) {
+ if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
+ previousInvocation = null;
+ }
+
+ return parseLegacyChangeVariablesInvocation(connectorId,
+ interfaceName, methodName,
+ (LegacyChangeVariablesInvocation) previousInvocation,
+ parametersJson, connectorTracker);
+ } else {
+ return parseServerRpcInvocation(connectorId, interfaceName,
+ methodName, parametersJson, connectorTracker);
+ }
+
+ }
+
+ private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
+ String connectorId, String interfaceName, String methodName,
+ LegacyChangeVariablesInvocation previousInvocation,
+ JSONArray parametersJson, ConnectorTracker connectorTracker)
+ throws JSONException {
+ if (parametersJson.length() != 2) {
+ throw new JSONException(
+ "Invalid parameters in legacy change variables call. Expected 2, was "
+ + parametersJson.length());
+ }
+ String variableName = parametersJson.getString(0);
+ UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
+ UidlValue.class, true, parametersJson.get(1), connectorTracker);
+
+ Object value = uidlValue.getValue();
+
+ if (previousInvocation != null
+ && previousInvocation.getConnectorId().equals(connectorId)) {
+ previousInvocation.setVariableChange(variableName, value);
+ return null;
+ } else {
+ return new LegacyChangeVariablesInvocation(connectorId,
+ variableName, value);
+ }
+ }
+
+ private ServerRpcMethodInvocation parseServerRpcInvocation(
+ String connectorId, String interfaceName, String methodName,
+ JSONArray parametersJson, ConnectorTracker connectorTracker)
+ throws JSONException {
+ ClientConnector connector = connectorTracker.getConnector(connectorId);
+
+ ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
+ if (rpcManager == null) {
+ /*
+ * Security: Don't even decode the json parameters if no RpcManager
+ * corresponding to the received method invocation has been
+ * registered.
+ */
+ getLogger().warning(
+ "Ignoring RPC call to " + interfaceName + "." + methodName
+ + " in connector " + connector.getClass().getName()
+ + "(" + connectorId
+ + ") as no RPC implementation is regsitered");
+ return null;
+ }
+
+ // Use interface from RpcManager instead of loading the class based on
+ // the string name to avoid problems with OSGi
+ Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
+
+ ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
+ connectorId, rpcInterface, methodName, parametersJson.length());
+
+ Object[] parameters = new Object[parametersJson.length()];
+ Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
+ .getGenericParameterTypes();
+
+ for (int j = 0; j < parametersJson.length(); ++j) {
+ Object parameterValue = parametersJson.get(j);
+ Type parameterType = declaredRpcMethodParameterTypes[j];
+ parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
+ parameterValue, connectorTracker);
+ }
+ invocation.setParameters(parameters);
+ return invocation;
+ }
+
+ protected void changeVariables(Object source, VariableOwner owner,
+ Map<String, Object> m) {
+ owner.changeVariables(source, m);
+ }
+
+ /**
+ * Unescape encoded burst separator characters in a burst received from the
+ * client. This protects from separator injection attacks.
+ *
+ * @param encodedValue
+ * to decode
+ * @return decoded value
+ */
+ protected String unescapeBurst(String encodedValue) {
+ final StringBuilder result = new StringBuilder();
+ final StringCharacterIterator iterator = new StringCharacterIterator(
+ encodedValue);
+ char character = iterator.current();
+ while (character != CharacterIterator.DONE) {
+ if (VAR_ESCAPE_CHARACTER == character) {
+ character = iterator.next();
+ switch (character) {
+ case VAR_ESCAPE_CHARACTER + 0x30:
+ // escaped escape character
+ result.append(VAR_ESCAPE_CHARACTER);
+ break;
+ case VAR_BURST_SEPARATOR + 0x30:
+ // +0x30 makes these letters for easier reading
+ result.append((char) (character - 0x30));
+ break;
+ case CharacterIterator.DONE:
+ // error
+ throw new RuntimeException(
+ "Communication error: Unexpected end of message");
+ default:
+ // other escaped character - probably a client-server
+ // version mismatch
+ throw new RuntimeException(
+ "Invalid escaped character from the client - check that the widgetset and server versions match");
+ }
+ } else {
+ // not a special character - add it to the result as is
+ result.append(character);
+ }
+ character = iterator.next();
+ }
+ return result.toString();
+ }
+
+ protected String getMessage(Reader reader) throws IOException {
+
+ StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE);
+ char[] buffer = new char[MAX_BUFFER_SIZE];
+
+ while (true) {
+ int read = reader.read(buffer);
+ if (read == -1) {
+ break;
+ }
+ sb.append(buffer, 0, read);
+ }
+
+ return sb.toString();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ServerRpcHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/SharedStateWriter.java b/server/src/com/vaadin/server/communication/SharedStateWriter.java
new file mode 100644
index 0000000000..fdf834387f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/SharedStateWriter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.PaintException;
+import com.vaadin.shared.communication.SharedState;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes {@link SharedState shared state} changes to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class SharedStateWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing the pending state changes of the dirty
+ * connectors of the given UI.
+ *
+ * @param ui
+ * The UI whose state changes should be written.
+ * @param writer
+ * The writer to use.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject sharedStates = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ // encode and send shared state
+ try {
+ JSONObject stateJson = connector.encodeState();
+
+ if (stateJson != null && stateJson.length() != 0) {
+ sharedStates.put(connector.getConnectorId(), stateJson);
+ }
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to serialize shared state for connector "
+ + connector.getClass().getName() + " ("
+ + connector.getConnectorId() + "): "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(sharedStates.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/StreamingEndEventImpl.java b/server/src/com/vaadin/server/communication/StreamingEndEventImpl.java
index 756cadee6b..e241bbbfaf 100644
--- a/server/src/com/vaadin/server/StreamingEndEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingEndEventImpl.java
@@ -13,8 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.StreamVariable.StreamingEndEvent;
@SuppressWarnings("serial")
diff --git a/server/src/com/vaadin/server/StreamingErrorEventImpl.java b/server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java
index 53e25399cd..1ee74c68b6 100644
--- a/server/src/com/vaadin/server/StreamingErrorEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java
@@ -13,8 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.StreamVariable.StreamingErrorEvent;
@SuppressWarnings("serial")
diff --git a/server/src/com/vaadin/server/StreamingProgressEventImpl.java b/server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java
index 610cd30c13..c07e37e196 100644
--- a/server/src/com/vaadin/server/StreamingProgressEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java
@@ -13,8 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.StreamVariable.StreamingProgressEvent;
@SuppressWarnings("serial")
diff --git a/server/src/com/vaadin/server/StreamingStartEventImpl.java b/server/src/com/vaadin/server/communication/StreamingStartEventImpl.java
index 3cd41bbb6d..a7f13be499 100644
--- a/server/src/com/vaadin/server/StreamingStartEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingStartEventImpl.java
@@ -13,8 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.StreamVariable.StreamingStartEvent;
@SuppressWarnings("serial")
diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java
new file mode 100644
index 0000000000..aea8bbd5cd
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UIInitHandler.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.annotations.PreserveOnRefresh;
+import com.vaadin.server.LegacyApplicationUIProvider;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.UIClassSelectionEvent;
+import com.vaadin.server.UICreateEvent;
+import com.vaadin.server.UIProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ui.ui.UIConstants;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles an initial request from the client to initialize a {@link UI}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UIInitHandler implements RequestHandler {
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ // NOTE! GateIn requires, for some weird reason, getOutputStream
+ // to be used instead of getWriter() (it seems to interpret
+ // application/json as a binary content type)
+ final OutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+
+ session.lock();
+ try {
+ assert UI.getCurrent() == null;
+
+ // Set browser information from the request
+ session.getBrowser().updateRequestDetails(request);
+
+ response.setContentType("application/json; charset=UTF-8");
+
+ UI uI = getBrowserDetailsUI(request, session);
+
+ session.getCommunicationManager().repaintAll(uI);
+
+ JSONObject params = new JSONObject();
+ params.put(UIConstants.UI_ID_PARAMETER, uI.getUIId());
+ String initialUIDL = getInitialUidl(request, uI);
+ params.put("uidl", initialUIDL);
+
+ outWriter.write(params.toString());
+ // NOTE GateIn requires the buffers to be flushed to work
+ outWriter.flush();
+ out.flush();
+ } catch (JSONException e) {
+ // TODO PUSH handle
+ e.printStackTrace();
+ } finally {
+ session.unlock();
+ outWriter.close();
+ }
+
+ return true;
+ }
+
+ private UI getBrowserDetailsUI(VaadinRequest request, VaadinSession session) {
+ VaadinService vaadinService = request.getService();
+
+ List<UIProvider> uiProviders = session.getUIProviders();
+
+ UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
+ request);
+
+ UIProvider provider = null;
+ Class<? extends UI> uiClass = null;
+ for (UIProvider p : uiProviders) {
+ // Check for existing LegacyWindow
+ if (p instanceof LegacyApplicationUIProvider) {
+ LegacyApplicationUIProvider legacyProvider = (LegacyApplicationUIProvider) p;
+
+ UI existingUi = legacyProvider
+ .getExistingUI(classSelectionEvent);
+ if (existingUi != null) {
+ reinitUI(existingUi, request);
+ return existingUi;
+ }
+ }
+
+ uiClass = p.getUIClass(classSelectionEvent);
+ if (uiClass != null) {
+ provider = p;
+ break;
+ }
+ }
+
+ if (provider == null || uiClass == null) {
+ return null;
+ }
+
+ // Check for an existing UI based on window.name
+
+ // Special parameter sent by vaadinBootstrap.js
+ String windowName = request.getParameter("v-wn");
+
+ Map<String, Integer> retainOnRefreshUIs = session
+ .getPreserveOnRefreshUIs();
+ if (windowName != null && !retainOnRefreshUIs.isEmpty()) {
+ // Check for a known UI
+
+ Integer retainedUIId = retainOnRefreshUIs.get(windowName);
+
+ if (retainedUIId != null) {
+ UI retainedUI = session.getUIById(retainedUIId.intValue());
+ if (uiClass.isInstance(retainedUI)) {
+ reinitUI(retainedUI, request);
+ return retainedUI;
+ } else {
+ getLogger().info(
+ "Not using retained UI in " + windowName
+ + " because retained UI was of type "
+ + retainedUI.getClass() + " but " + uiClass
+ + " is expected for the request.");
+ }
+ }
+ }
+
+ // No existing UI found - go on by creating and initializing one
+
+ Integer uiId = Integer.valueOf(session.getNextUIid());
+
+ // Explicit Class.cast to detect if the UIProvider does something
+ // unexpected
+ UICreateEvent event = new UICreateEvent(request, uiClass, uiId);
+ UI ui = uiClass.cast(provider.createInstance(event));
+
+ // Initialize some fields for a newly created UI
+ if (ui.getSession() != session) {
+ // Session already set for LegacyWindow
+ ui.setSession(session);
+ }
+
+ // Set thread local here so it is available in init
+ UI.setCurrent(ui);
+
+ ui.doInit(request, uiId.intValue());
+
+ session.addUI(ui);
+
+ // Remember if it should be remembered
+ if (vaadinService.preserveUIOnRefresh(provider, event)) {
+ // Remember this UI
+ if (windowName == null) {
+ getLogger().warning(
+ "There is no window.name available for UI " + uiClass
+ + " that should be preserved.");
+ } else {
+ session.getPreserveOnRefreshUIs().put(windowName, uiId);
+ }
+ }
+
+ return ui;
+ }
+
+ /**
+ * Updates a UI that has already been initialized but is now loaded again,
+ * e.g. because of {@link PreserveOnRefresh}.
+ *
+ * @param ui
+ * @param request
+ */
+ private void reinitUI(UI ui, VaadinRequest request) {
+ UI.setCurrent(ui);
+
+ // Fire fragment change if the fragment has changed
+ String location = request.getParameter("v-loc");
+ if (location != null) {
+ ui.getPage().updateLocation(location);
+ }
+ }
+
+ /**
+ * Generates the initial UIDL message that can e.g. be included in a html
+ * page to avoid a separate round trip just for getting the UIDL.
+ *
+ * @param request
+ * the request that caused the initialization
+ * @param uI
+ * the UI for which the UIDL should be generated
+ * @return a string with the initial UIDL message
+ * @throws JSONException
+ * if an exception occurs while encoding output
+ * @throws IOException
+ */
+ protected String getInitialUidl(VaadinRequest request, UI uI)
+ throws JSONException, IOException {
+ StringWriter writer = new StringWriter();
+ try {
+ writer.write("{");
+ if (uI.getSession().getConfiguration().isXsrfProtectionEnabled()) {
+ writer.write(uI.getSession().getCommunicationManager()
+ .getSecurityKeyUIDL(request));
+ }
+ new UidlWriter().write(uI, writer, true, false);
+ writer.write("}");
+
+ String initialUIDL = writer.toString();
+ getLogger().log(Level.FINE, "Initial UIDL:" + initialUIDL);
+ return initialUIDL;
+ } finally {
+ writer.close();
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UIInitHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
new file mode 100644
index 0000000000..c200abb31d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.LinkedList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONException;
+
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.AbstractCommunicationManager.Callback;
+import com.vaadin.server.AbstractCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.Constants;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.Version;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.UI;
+
+/**
+ * Processes a UIDL request from the client.
+ *
+ * Uses {@link ServerRpcHandler} to execute client-to-server RPC invocations and
+ * {@link UidlWriter} to write state changes and client RPC calls back to the
+ * client.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UidlRequestHandler implements RequestHandler {
+
+ private Callback criticalNotifier;
+
+ private ServerRpcHandler rpcHandler = new ServerRpcHandler();
+
+ public UidlRequestHandler(Callback criticalNotifier) {
+ this.criticalNotifier = criticalNotifier;
+ }
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ UI uI = session.getService().findUI(request);
+
+ checkWidgetsetVersion(request);
+ String requestThemeName = request.getParameter("theme");
+ ClientConnector highlightedConnector;
+ // repaint requested or session has timed out and new one is created
+ boolean repaintAll;
+ final OutputStream out = response.getOutputStream();
+
+ // TODO PUSH repaintAll, analyzeLayouts, highlightConnector should be
+ // part of the message payload to make the functionality transport
+ // agnostic
+
+ repaintAll = (request
+ .getParameter(ApplicationConstants.URL_PARAMETER_REPAINT_ALL) != null);
+
+ boolean analyzeLayouts = false;
+ if (repaintAll) {
+ // analyzing can be done only with repaintAll
+ analyzeLayouts = (request
+ .getParameter(ApplicationConstants.PARAM_ANALYZE_LAYOUTS) != null);
+
+ String pid = request
+ .getParameter(ApplicationConstants.PARAM_HIGHLIGHT_CONNECTOR);
+ if (pid != null) {
+ highlightedConnector = uI.getConnectorTracker().getConnector(
+ pid);
+ highlightConnector(highlightedConnector);
+ }
+ }
+
+ final Writer outWriter = new BufferedWriter(new OutputStreamWriter(out,
+ "UTF-8"));
+
+ // The rest of the process is synchronized with the session
+ // in order to guarantee that no parallel variable handling is
+ // made
+ session.lock();
+ try {
+ rpcHandler.handleRpc(uI, request.getReader(), request);
+
+ if (repaintAll) {
+ session.getCommunicationManager().repaintAll(uI);
+ }
+
+ writeUidl(request, response, uI, outWriter, repaintAll,
+ analyzeLayouts);
+ postHandleRequest(uI);
+ } catch (JSONException e) {
+ getLogger().log(Level.SEVERE, "Error writing JSON to response", e);
+ // Refresh on client side
+ criticalNotifier.criticalNotification(request, response, null,
+ null, null, null);
+ } catch (InvalidUIDLSecurityKeyException e) {
+ getLogger().log(Level.WARNING,
+ "Invalid security key received from {}",
+ request.getRemoteHost());
+ // Refresh on client side
+ criticalNotifier.criticalNotification(request, response, null,
+ null, null, null);
+ } finally {
+ session.unlock();
+ outWriter.close();
+ requestThemeName = null;
+ }
+
+ // Ensure that the browser does not cache UIDL responses.
+ // iOS 6 Safari requires this (#9732)
+ response.setHeader("Cache-Control", "no-cache");
+
+ return true;
+ }
+
+ /**
+ * Checks that the version reported by the client (widgetset) matches that
+ * of the server.
+ *
+ * @param request
+ */
+ private void checkWidgetsetVersion(VaadinRequest request) {
+ String widgetsetVersion = request.getParameter("v-wsver");
+ if (widgetsetVersion == null) {
+ // Only check when the widgetset version is reported. It is reported
+ // in the first UIDL request (not the initial request as it is a
+ // plain GET /)
+ return;
+ }
+
+ if (!Version.getFullVersion().equals(widgetsetVersion)) {
+ getLogger().warning(
+ String.format(Constants.WIDGETSET_MISMATCH_INFO,
+ Version.getFullVersion(), widgetsetVersion));
+ }
+ }
+
+ private void writeUidl(VaadinRequest request, VaadinResponse response,
+ UI ui, Writer writer, boolean repaintAll, boolean analyzeLayouts)
+ throws IOException, JSONException {
+ openJsonMessage(writer, response);
+
+ // security key
+ Object writeSecurityTokenFlag = request
+ .getAttribute(AbstractCommunicationManager.WRITE_SECURITY_TOKEN_FLAG);
+
+ if (writeSecurityTokenFlag != null) {
+ writer.write(ui.getSession().getCommunicationManager()
+ .getSecurityKeyUIDL(request));
+ }
+
+ new UidlWriter().write(ui, writer, repaintAll, analyzeLayouts);
+
+ closeJsonMessage(writer);
+ }
+
+ /**
+ * Method called after the paint phase while still being synchronized on the
+ * session
+ *
+ * @param uI
+ *
+ */
+ protected void postHandleRequest(UI uI) {
+ // Remove connectors that have been detached from the session during
+ // handling of the request
+ uI.getConnectorTracker().cleanConnectorMap();
+ }
+
+ protected void closeJsonMessage(Writer outWriter) throws IOException {
+ outWriter.write("}]");
+ }
+
+ /**
+ * Writes the opening of JSON message to be sent to client.
+ *
+ * @param outWriter
+ * @param response
+ * @throws IOException
+ */
+ protected void openJsonMessage(Writer outWriter, VaadinResponse response)
+ throws IOException {
+ // Sets the response type
+ response.setContentType("application/json; charset=UTF-8");
+ // some dirt to prevent cross site scripting
+ outWriter.write("for(;;);[{");
+ }
+
+ // TODO Does this belong here?
+ protected void highlightConnector(ClientConnector highlightedConnector) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("*** Debug details of a connector: *** \n");
+ sb.append("Type: ");
+ sb.append(highlightedConnector.getClass().getName());
+ sb.append("\nId:");
+ sb.append(highlightedConnector.getConnectorId());
+ if (highlightedConnector instanceof Component) {
+ Component component = (Component) highlightedConnector;
+ if (component.getCaption() != null) {
+ sb.append("\nCaption:");
+ sb.append(component.getCaption());
+ }
+ }
+ printHighlightedConnectorHierarchy(sb, highlightedConnector);
+ getLogger().info(sb.toString());
+ }
+
+ // TODO Does this belong here?
+ protected void printHighlightedConnectorHierarchy(StringBuilder sb,
+ ClientConnector connector) {
+ LinkedList<ClientConnector> h = new LinkedList<ClientConnector>();
+ h.add(connector);
+ ClientConnector parent = connector.getParent();
+ while (parent != null) {
+ h.addFirst(parent);
+ parent = parent.getParent();
+ }
+
+ sb.append("\nConnector hierarchy:\n");
+ VaadinSession session2 = connector.getUI().getSession();
+ sb.append(session2.getClass().getName());
+ sb.append("(");
+ sb.append(session2.getClass().getSimpleName());
+ sb.append(".java");
+ sb.append(":1)");
+ int l = 1;
+ for (ClientConnector connector2 : h) {
+ sb.append("\n");
+ for (int i = 0; i < l; i++) {
+ sb.append(" ");
+ }
+ l++;
+ Class<? extends ClientConnector> connectorClass = connector2
+ .getClass();
+ Class<?> topClass = connectorClass;
+ while (topClass.getEnclosingClass() != null) {
+ topClass = topClass.getEnclosingClass();
+ }
+ sb.append(connectorClass.getName());
+ sb.append("(");
+ sb.append(topClass.getSimpleName());
+ sb.append(".java:1)");
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UidlRequestHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java
new file mode 100644
index 0000000000..0086affe91
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UidlWriter.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.annotations.StyleSheet;
+import com.vaadin.server.AbstractCommunicationManager;
+import com.vaadin.server.AbstractCommunicationManager.ClientCache;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.JsonPaintTarget;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes pending server-side changes to UI state to JSON. This includes
+ * shared state, client RPC invocations, connector hierarchy changes, connector
+ * type information among others.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UidlWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing all pending changes to the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose changes to write
+ * @param writer
+ * The writer to use
+ * @param repaintAll
+ * Whether the client should re-render the whole UI.
+ * @param analyzeLayouts
+ * Whether detected layout problems should be logged.
+ * @throws IOException
+ * If the writing fails.
+ * @throws JSONException
+ * If the JSON serialization fails.
+ */
+ public void write(UI ui, Writer writer, boolean repaintAll,
+ boolean analyzeLayouts) throws IOException, JSONException {
+
+ ArrayList<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+ VaadinSession session = ui.getSession();
+ AbstractCommunicationManager manager = session
+ .getCommunicationManager();
+ // Paints components
+ ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
+ getLogger().log(Level.FINE, "* Creating response to client");
+
+ getLogger().log(
+ Level.FINE,
+ "Found " + dirtyVisibleConnectors.size()
+ + " dirty connectors to paint");
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ boolean initialized = uiConnectorTracker
+ .isClientSideInitialized(connector);
+ connector.beforeClientResponse(!initialized);
+ }
+
+ uiConnectorTracker.setWritingResponse(true);
+ try {
+ writer.write("\"changes\" : ");
+
+ JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
+ !repaintAll);
+
+ new LegacyUidlWriter().write(ui, writer, paintTarget);
+
+ paintTarget.close();
+ writer.write(", "); // close changes
+
+ // send shared state to client
+
+ // for now, send the complete state of all modified and new
+ // components
+
+ // Ideally, all this would be sent before "changes", but that causes
+ // complications with legacy components that create sub-components
+ // in their paint phase. Nevertheless, this will be processed on the
+ // client after component creation but before legacy UIDL
+ // processing.
+
+ writer.write("\"state\":");
+ new SharedStateWriter().write(ui, writer);
+ writer.write(", "); // close states
+
+ // TODO This should be optimized. The type only needs to be
+ // sent once for each connector id + on refresh. Use the same cache
+ // as
+ // widget mapping
+
+ writer.write("\"types\":");
+ new ConnectorTypeWriter().write(ui, writer, paintTarget);
+ writer.write(", "); // close states
+
+ // Send update hierarchy information to the client.
+
+ // This could be optimized aswell to send only info if hierarchy has
+ // actually changed. Much like with the shared state. Note though
+ // that an empty hierarchy is information aswell (e.g. change from 1
+ // child to 0 children)
+
+ writer.write("\"hierarchy\":");
+ new ConnectorHierarchyWriter().write(ui, writer);
+ writer.write(", "); // close hierarchy
+
+ uiConnectorTracker.markAllConnectorsClean();
+
+ // send server to client RPC calls for components in the UI, in call
+ // order
+
+ // collect RPC calls from components in the UI in the order in
+ // which they were performed, remove the calls from components
+
+ writer.write("\"rpc\" : ");
+ new ClientRpcWriter().write(ui, writer);
+ writer.write(", "); // close rpc
+
+ writer.write("\"meta\" : ");
+
+ SystemMessages messages = ui.getSession().getService()
+ .getSystemMessages(ui.getLocale(), null);
+ // TODO hilightedConnector
+ new MetadataWriter().write(ui, writer, repaintAll, analyzeLayouts,
+ null, messages);
+ writer.write(", ");
+
+ writer.write("\"resources\" : ");
+ new ResourceWriter().write(ui, writer, paintTarget);
+
+ Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
+ .getUsedClientConnectors();
+ boolean typeMappingsOpen = false;
+ ClientCache clientCache = manager.getClientCache(ui);
+
+ List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
+
+ for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
+ if (clientCache.cache(class1)) {
+ // client does not know the mapping key for this type, send
+ // mapping to client
+ newConnectorTypes.add(class1);
+
+ if (!typeMappingsOpen) {
+ typeMappingsOpen = true;
+ writer.write(", \"typeMappings\" : { ");
+ } else {
+ writer.write(" , ");
+ }
+ String canonicalName = class1.getCanonicalName();
+ writer.write("\"");
+ writer.write(canonicalName);
+ writer.write("\" : ");
+ writer.write(manager.getTagForType(class1));
+ }
+ }
+ if (typeMappingsOpen) {
+ writer.write(" }");
+ }
+
+ // TODO PUSH Refactor to TypeInheritanceWriter or something
+ boolean typeInheritanceMapOpen = false;
+ if (typeMappingsOpen) {
+ // send the whole type inheritance map if any new mappings
+ for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
+ if (!ClientConnector.class.isAssignableFrom(class1
+ .getSuperclass())) {
+ continue;
+ }
+ if (!typeInheritanceMapOpen) {
+ typeInheritanceMapOpen = true;
+ writer.write(", \"typeInheritanceMap\" : { ");
+ } else {
+ writer.write(" , ");
+ }
+ writer.write("\"");
+ writer.write(manager.getTagForType(class1));
+ writer.write("\" : ");
+ writer.write(manager
+ .getTagForType((Class<? extends ClientConnector>) class1
+ .getSuperclass()));
+ }
+ if (typeInheritanceMapOpen) {
+ writer.write(" }");
+ }
+ }
+
+ // TODO Refactor to DependencyWriter or something
+ /*
+ * Ensure super classes come before sub classes to get script
+ * dependency order right. Sub class @JavaScript might assume that
+ *
+ * @JavaScript defined by super class is already loaded.
+ */
+ Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
+ @Override
+ public int compare(Class<?> o1, Class<?> o2) {
+ // TODO optimize using Class.isAssignableFrom?
+ return hierarchyDepth(o1) - hierarchyDepth(o2);
+ }
+
+ private int hierarchyDepth(Class<?> type) {
+ if (type == Object.class) {
+ return 0;
+ } else {
+ return hierarchyDepth(type.getSuperclass()) + 1;
+ }
+ }
+ });
+
+ List<String> scriptDependencies = new ArrayList<String>();
+ List<String> styleDependencies = new ArrayList<String>();
+
+ for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
+ JavaScript jsAnnotation = class1
+ .getAnnotation(JavaScript.class);
+ if (jsAnnotation != null) {
+ for (String uri : jsAnnotation.value()) {
+ scriptDependencies.add(manager.registerDependency(uri,
+ class1));
+ }
+ }
+
+ StyleSheet styleAnnotation = class1
+ .getAnnotation(StyleSheet.class);
+ if (styleAnnotation != null) {
+ for (String uri : styleAnnotation.value()) {
+ styleDependencies.add(manager.registerDependency(uri,
+ class1));
+ }
+ }
+ }
+
+ // Include script dependencies in output if there are any
+ if (!scriptDependencies.isEmpty()) {
+ writer.write(", \"scriptDependencies\": "
+ + new JSONArray(scriptDependencies).toString());
+ }
+
+ // Include style dependencies in output if there are any
+ if (!styleDependencies.isEmpty()) {
+ writer.write(", \"styleDependencies\": "
+ + new JSONArray(styleDependencies).toString());
+ }
+
+ // add any pending locale definitions requested by the client
+ writer.write(", \"locales\": ");
+ manager.printLocaleDeclarations(writer);
+
+ if (manager.getDragAndDropService() != null) {
+ manager.getDragAndDropService().printJSONResponse(writer);
+ }
+
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ uiConnectorTracker.markClientSideInitialized(connector);
+ }
+
+ assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
+
+ writePerformanceData(ui, writer);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ uiConnectorTracker.setWritingResponse(false);
+ }
+ }
+
+ /**
+ * Adds the performance timing data (used by TestBench 3) to the UIDL
+ * response.
+ *
+ * @throws IOException
+ */
+ private void writePerformanceData(UI ui, Writer writer) throws IOException {
+ writer.write(String.format(", \"timings\":[%d, %d]", ui.getSession()
+ .getCumulativeRequestDuration(), ui.getSession()
+ .getLastRequestDuration()));
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UidlWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java
index a229003224..7d741e1b28 100644
--- a/server/src/com/vaadin/ui/ConnectorTracker.java
+++ b/server/src/com/vaadin/ui/ConnectorTracker.java
@@ -17,6 +17,7 @@ package com.vaadin.ui;
import java.io.IOException;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -463,6 +464,22 @@ public class ConnectorTracker implements Serializable {
return dirtyConnectors;
}
+ /**
+ * Returns a collection of those {@link #getDirtyConnectors() dirty
+ * connectors} that are actually visible to the client.
+ *
+ * @return A list of dirty and visible connectors.
+ */
+ public ArrayList<ClientConnector> getDirtyVisibleConnectors() {
+ ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>();
+ for (ClientConnector c : getDirtyConnectors()) {
+ if (AbstractCommunicationManager.isConnectorVisibleToClient(c)) {
+ dirtyConnectors.add(c);
+ }
+ }
+ return dirtyConnectors;
+ }
+
public JSONObject getDiffState(ClientConnector connector) {
assert getConnector(connector.getConnectorId()) == connector;
return diffStates.get(connector);
diff --git a/server/tests/src/com/vaadin/tests/server/TestSimpleMultiPartInputStream.java b/server/tests/src/com/vaadin/tests/server/TestSimpleMultiPartInputStream.java
index 84247c81c1..6907594b5e 100644
--- a/server/tests/src/com/vaadin/tests/server/TestSimpleMultiPartInputStream.java
+++ b/server/tests/src/com/vaadin/tests/server/TestSimpleMultiPartInputStream.java
@@ -7,7 +7,7 @@ import java.util.Arrays;
import junit.framework.TestCase;
-import com.vaadin.server.AbstractCommunicationManager.SimpleMultiPartInputStream;
+import com.vaadin.server.communication.FileUploadHandler.SimpleMultiPartInputStream;
public class TestSimpleMultiPartInputStream extends TestCase {
diff --git a/server/tests/src/com/vaadin/tests/server/TestStreamVariableMapping.java b/server/tests/src/com/vaadin/tests/server/TestStreamVariableMapping.java
index 467a76dfa6..0245ec5d5e 100644
--- a/server/tests/src/com/vaadin/tests/server/TestStreamVariableMapping.java
+++ b/server/tests/src/com/vaadin/tests/server/TestStreamVariableMapping.java
@@ -67,7 +67,7 @@ public class TestStreamVariableMapping extends TestCase {
assertNotNull(tracker.getStreamVariable(owner.getConnectorId(),
variableName));
- cm.cleanStreamVariable(owner, variableName);
+ tracker.cleanStreamVariable(owner.getConnectorId(), variableName);
assertNull(tracker.getStreamVariable(owner.getConnectorId(),
variableName));
}