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