From d31cebd06e5e9c8fccaa437b9514a489bb286054 Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Wed, 6 May 2009 11:14:08 +0000 Subject: [PATCH] Merges from 6.0 branch. svn changeset:7637/svn branch:theme_2009_03 --- .../toolkit/launcher/WidgetsetCompiler.java | 55 + .../terminal/gwt/client/BrowserInfo.java | 32 +- .../toolkit/terminal/gwt/client/ICaption.java | 6 +- .../gwt/client/ui/IAbsoluteLayout.java | 49 +- .../terminal/gwt/client/ui/ITabsheet.java | 23 + .../server/AbstractApplicationServlet.java | 1797 +++++++++++++++++ .../gwt/server/ApplicationRunnerServlet.java | 173 ++ .../gwt/server/ApplicationServlet.java | 1676 +-------------- .../gwt/server/CommunicationManager.java | 53 +- .../gwt/server/WebApplicationContext.java | 18 +- 10 files changed, 2168 insertions(+), 1714 deletions(-) create mode 100644 src/com/itmill/toolkit/launcher/WidgetsetCompiler.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/server/AbstractApplicationServlet.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/server/ApplicationRunnerServlet.java diff --git a/src/com/itmill/toolkit/launcher/WidgetsetCompiler.java b/src/com/itmill/toolkit/launcher/WidgetsetCompiler.java new file mode 100644 index 0000000000..389aacaee3 --- /dev/null +++ b/src/com/itmill/toolkit/launcher/WidgetsetCompiler.java @@ -0,0 +1,55 @@ +package com.itmill.toolkit.launcher; + +import com.google.gwt.dev.GWTCompiler; + +/** + * A wrapper for the GWT 1.6 compiler that runs the compiler in a new thread. + * + * This allows circumventing a J2SE 5.0 bug (6316197) that prevents setting the + * stack size for the main thread. Thus, larger widgetsets can be compiled. + * + * This class takes the same command line arguments as the + * com.google.gwt.dev.GWTCompiler class. The old and deprecated compiler is used + * for compatibility with GWT 1.5. + * + * A typical invocation would use e.g. the following arguments + * + * "-out WebContent/ITMILL/widgetsets com.itmill.toolkit.terminal.gwt.DefaultWidgetSet" + * + * In addition, larger memory usage settings for the VM should be used, e.g. + * + * "-Xms256M -Xmx512M -Xss8M" + * + * The source directory containing widgetset and related classes must be + * included in the classpath, as well as the gwt-dev-[platform].jar and other + * relevant JARs. + */ +public class WidgetsetCompiler { + + /** + * @param args + * same arguments as for com.google.gwt.dev.Compiler + */ + public static void main(final String[] args) { + try { + // run the compiler in a different thread to enable using the + // user-set stack size + + // on Windows, the default stack size is too small for the main + // thread and cannot be changed in JRE 1.5 (see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6316197) + + Runnable runCompiler = new Runnable() { + public void run() { + GWTCompiler.main(args); + } + }; + Thread runThread = new Thread(runCompiler); + runThread.start(); + runThread.join(); + System.out.println("Widgetset compilation finished"); + } catch (Throwable thr) { + thr.printStackTrace(); + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/BrowserInfo.java b/src/com/itmill/toolkit/terminal/gwt/client/BrowserInfo.java index 42e44d0374..1604ac65d1 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/BrowserInfo.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/BrowserInfo.java @@ -74,6 +74,11 @@ public class BrowserInfo { ieVersionString = ieVersionString.substring(0, ieVersionString .indexOf(";")); version = Float.parseFloat(ieVersionString); + + if (version == 8 && isIE8InIE7CompatibilityMode()) { + version = 7; + } + } } catch (Exception e) { ClientExceptionHandler.displayError(e); @@ -100,7 +105,6 @@ public class BrowserInfo { */ public String getCSSClass() { String prefix = "i-"; - boolean ie8 = false; if (cssClass == null) { String bs = getBrowserString().toLowerCase(); @@ -122,29 +126,32 @@ public class BrowserInfo { b = "ie"; int i = bs.indexOf(" msie ") + 6; String ieVersion = bs.substring(i, i + 1); - v = b + ieVersion; - // This adds .i-ie7 for ie8 also. - // TODO Remove this when IE8 is no longer run in compatibility - // mode - if (ieVersion != null && ieVersion.equals("8")) { - ie8 = true; + if (ieVersion != null && ieVersion.equals("8") + && isIE8InIE7CompatibilityMode()) { + ieVersion = "7"; } + v = b + ieVersion; } else if (bs.indexOf("opera/") != -1) { b = "op"; int i = bs.indexOf("opera/") + 6; v = b + bs.substring(i, i + 3).replace(".", ""); } cssClass = prefix + b + " " + prefix + v; - if (ie8) { - cssClass += " " + prefix + "ie7"; - } } return cssClass; } + private native boolean isIE8InIE7CompatibilityMode() + /*-{ + var mode = $wnd.document.documentMode; + if (!mode) + return false; + return (mode == 7); + }-*/; + public boolean isIE() { return isIE; } @@ -161,6 +168,10 @@ public class BrowserInfo { return isIE && version == 7; } + public boolean isIE8() { + return isIE && version == 8; + } + public boolean isGecko() { return isGecko; } @@ -201,6 +212,7 @@ public class BrowserInfo { c.log("isIE() " + get().isIE()); c.log("isIE6() " + get().isIE6()); c.log("isIE7() " + get().isIE7()); + c.log("isIE8() " + get().isIE8()); c.log("isFF2() " + get().isFF2()); c.log("isSafari() " + get().isSafari()); c.log("getGeckoVersion() " + get().getGeckoVersion()); diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java b/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java index d3e12769ad..7487ecc434 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java @@ -436,4 +436,8 @@ public class ICaption extends HTML { } } -} + protected Element getTextElement() { + return captionText; + } + +} \ No newline at end of file diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IAbsoluteLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IAbsoluteLayout.java index 8b10854959..a64a41fa7f 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IAbsoluteLayout.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IAbsoluteLayout.java @@ -18,6 +18,7 @@ import com.google.gwt.user.client.ui.Widget; import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ICaption; import com.itmill.toolkit.terminal.gwt.client.Paintable; import com.itmill.toolkit.terminal.gwt.client.RenderSpace; import com.itmill.toolkit.terminal.gwt.client.UIDL; @@ -113,8 +114,9 @@ public class IAbsoluteLayout extends ComplexPanel implements Container { } public void updateCaption(Paintable component, UIDL uidl) { - // TODO Auto-generated method stub - + AbsoluteWrapper parent2 = (AbsoluteWrapper) ((Widget) component) + .getParent(); + parent2.updateCaption(uidl); } public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { @@ -189,9 +191,10 @@ public class IAbsoluteLayout extends ComplexPanel implements Container { private void relayoutRelativeChildren() { for (Widget widget : getChildren()) { - if (widget instanceof Paintable) { - Paintable new_name = (Paintable) widget; - client.handleComponentRelativeSize(widget); + if (widget instanceof AbsoluteWrapper) { + AbsoluteWrapper w = (AbsoluteWrapper) widget; + client.handleComponentRelativeSize(w.getWidget()); + w.updateCaptionPosition(); } } } @@ -213,7 +216,9 @@ public class IAbsoluteLayout extends ComplexPanel implements Container { private void relayoutWrappersForIe6() { for (Widget wrapper : getChildren()) { - ((AbsoluteWrapper) wrapper).ie6Layout(); + if (wrapper instanceof AbsoluteWrapper) { + ((AbsoluteWrapper) wrapper).ie6Layout(); + } } } @@ -226,13 +231,35 @@ public class IAbsoluteLayout extends ComplexPanel implements Container { private String zIndex; private Paintable paintable; + private ICaption caption; public AbsoluteWrapper(Paintable paintable) { this.paintable = paintable; setStyleName(CLASSNAME + "-wrapper"); } + public void updateCaption(UIDL uidl) { + + boolean captionIsNeeded = ICaption.isNeeded(uidl); + if (captionIsNeeded) { + if (caption == null) { + caption = new ICaption(paintable, client); + IAbsoluteLayout.this.add(caption); + } + caption.updateCaption(uidl); + updateCaptionPosition(); + } else { + if (caption != null) { + caption.removeFromParent(); + caption = null; + } + } + } + public void destroy() { + if (caption != null) { + caption.removeFromParent(); + } client.unregisterPaintable(paintable); removeFromParent(); } @@ -285,7 +312,17 @@ public class IAbsoluteLayout extends ComplexPanel implements Container { ie6Layout(); } } + updateCaptionPosition(); + } + private void updateCaptionPosition() { + if (caption != null) { + Style style = caption.getElement().getStyle(); + style.setProperty("position", "absolute"); + style.setPropertyPx("left", getElement().getOffsetLeft()); + style.setPropertyPx("top", getElement().getOffsetTop() + - caption.getHeight()); + } } private void ie6Layout() { diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITabsheet.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITabsheet.java index 519ec6034a..e40b6ac0a8 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITabsheet.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITabsheet.java @@ -45,6 +45,29 @@ public class ITabsheet extends ITabsheetBase { } } + @Override + public void setWidth(String width) { + super.setWidth(width); + if (BrowserInfo.get().isIE7()) { + /* + * IE7 apparently has problems with calculating width for + * floated elements inside a DIV with padding. Set the width + * explicitly for the caption. + */ + fixTextWidth(); + } + } + + private void fixTextWidth() { + Element captionText = getTextElement(); + int captionWidth = Util.getRequiredWidth(captionText); + int scrollWidth = captionText.getScrollWidth(); + if (scrollWidth > captionWidth) { + captionWidth = scrollWidth; + } + captionText.getStyle().setPropertyPx("width", captionWidth); + } + } class TabBar extends ComplexPanel implements ClickListener { diff --git a/src/com/itmill/toolkit/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/AbstractApplicationServlet.java new file mode 100644 index 0000000000..963550d08c --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/server/AbstractApplicationServlet.java @@ -0,0 +1,1797 @@ +package com.itmill.toolkit.terminal.gwt.server; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.xml.sax.SAXException; + +import com.itmill.toolkit.Application; +import com.itmill.toolkit.Application.SystemMessages; +import com.itmill.toolkit.external.org.apache.commons.fileupload.servlet.ServletFileUpload; +import com.itmill.toolkit.service.FileTypeResolver; +import com.itmill.toolkit.terminal.DownloadStream; +import com.itmill.toolkit.terminal.ParameterHandler; +import com.itmill.toolkit.terminal.Terminal; +import com.itmill.toolkit.terminal.ThemeResource; +import com.itmill.toolkit.terminal.URIHandler; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.ui.Window; + +/** + * Abstract implementation of the ApplicationServlet which handles all + * communication between the client and the server. + * + * It is possible to extend this class to provide own functionality but in most + * cases this is unnecessary. + * + * + * @author IT Mill Ltd. + * @version + * @VERSION@ + * @since 6.0 + */ + +@SuppressWarnings("serial") +public abstract class AbstractApplicationServlet extends HttpServlet { + /** + * Version number of this release. For example "5.0.0". + */ + public static final String VERSION; + /** + * Major version number. For example 5 in 5.1.0. + */ + public static final int VERSION_MAJOR; + + /** + * Minor version number. For example 1 in 5.1.0. + */ + public static final int VERSION_MINOR; + + /** + * Builds number. For example 0-custom_tag in 5.0.0-custom_tag. + */ + public static final String VERSION_BUILD; + + /* Initialize version numbers from string replaced by build-script. */ + static { + if ("@VERSION@".equals("@" + "VERSION" + "@")) { + VERSION = "5.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD"; + } else { + VERSION = "@VERSION@"; + } + final String[] digits = VERSION.split("\\."); + VERSION_MAJOR = Integer.parseInt(digits[0]); + VERSION_MINOR = Integer.parseInt(digits[1]); + VERSION_BUILD = digits[2]; + } + + /** + * If the attribute is present in the request, a html fragment will be + * written instead of a whole page. + */ + public static final String REQUEST_FRAGMENT = ApplicationServlet.class + .getName() + + ".fragment"; + /** + * This request attribute forces widgetset used; e.g for portlets that can + * not have different widgetsets. + */ + public static final String REQUEST_WIDGETSET = ApplicationServlet.class + .getName() + + ".widgetset"; + /** + * This request attribute is used to add styles to the main element. E.g + * "height:500px" generates a style="height:500px" to the main element, + * useful from some embedding situations (e.g portlet include.) + */ + public static final String REQUEST_APPSTYLE = ApplicationServlet.class + .getName() + + ".style"; + + private Properties applicationProperties; + + private static final String NOT_PRODUCTION_MODE_INFO = "=================================================================\nIT Mill Toolkit is running in DEBUG MODE.\nAdd productionMode=true to web.xml to disable debug features.\nTo show debug window, add ?debug to your application URL.\n================================================================="; + + private boolean productionMode = false; + + private static final String URL_PARAMETER_RESTART_APPLICATION = "restartApplication"; + private static final String URL_PARAMETER_CLOSE_APPLICATION = "closeApplication"; + private static final String URL_PARAMETER_REPAINT_ALL = "repaintAll"; + protected static final String URL_PARAMETER_THEME = "theme"; + + private static final String SERVLET_PARAMETER_DEBUG = "Debug"; + private static final String SERVLET_PARAMETER_PRODUCTION_MODE = "productionMode"; + + // Configurable parameter names + private static final String PARAMETER_ITMILL_RESOURCES = "Resources"; + + private static final int DEFAULT_BUFFER_SIZE = 32 * 1024; + + private static final int MAX_BUFFER_SIZE = 64 * 1024; + + private static final String RESOURCE_URI = "/RES/"; + + private static final String AJAX_UIDL_URI = "/UIDL"; + + static final String THEME_DIRECTORY_PATH = "ITMILL/themes/"; + + private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24; + + static final String WIDGETSET_DIRECTORY_PATH = "ITMILL/widgetsets/"; + + // Name of the default widget set, used if not specified in web.xml + private static final String DEFAULT_WIDGETSET = "com.itmill.toolkit.terminal.gwt.DefaultWidgetSet"; + + // Widget set parameter name + private static final String PARAMETER_WIDGETSET = "widgetset"; + + private static final String ERROR_NO_WINDOW_FOUND = "Application did not give any window, did you remember to setMainWindow()?"; + + private String resourcePath = null; + + /** + * Called by the servlet container to indicate to a servlet that the servlet + * is being placed into service. + * + * @param servletConfig + * the object containing the servlet's configuration and + * initialization parameters + * @throws javax.servlet.ServletException + * if an exception has occurred that interferes with the + * servlet's normal operation. + */ + @Override + public void init(javax.servlet.ServletConfig servletConfig) + throws javax.servlet.ServletException { + super.init(servletConfig); + + // Stores the application parameters into Properties object + applicationProperties = new Properties(); + for (final Enumeration e = servletConfig.getInitParameterNames(); e + .hasMoreElements();) { + final String name = (String) e.nextElement(); + applicationProperties.setProperty(name, servletConfig + .getInitParameter(name)); + } + + // Overrides with server.xml parameters + final ServletContext context = servletConfig.getServletContext(); + for (final Enumeration e = context.getInitParameterNames(); e + .hasMoreElements();) { + final String name = (String) e.nextElement(); + applicationProperties.setProperty(name, context + .getInitParameter(name)); + } + + checkProductionMode(); + } + + private void checkProductionMode() { + // Check if the application is in production mode. + // We are in production mode if Debug=false or productionMode=true + if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true") + .equals("false")) { + // "Debug=true" is the old way and should no longer be used + productionMode = true; + } else if (getApplicationOrSystemProperty( + SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) { + // "productionMode=true" is the real way to do it + productionMode = true; + } + + if (!productionMode) { + /* Print an information/warning message about running in debug mode */ + System.err.println(NOT_PRODUCTION_MODE_INFO); + } + + } + + /** + * Gets an application property value. + * + * @param parameterName + * the Name or the parameter. + * @return String value or null if not found + */ + protected String getApplicationProperty(String parameterName) { + + String val = applicationProperties.getProperty(parameterName); + if (val != null) { + return val; + } + + // Try lower case application properties for backward compatibility with + // 3.0.2 and earlier + val = applicationProperties.getProperty(parameterName.toLowerCase()); + + return val; + } + + /** + * Gets an system property value. + * + * @param parameterName + * the Name or the parameter. + * @return String value or null if not found + */ + protected String getSystemProperty(String parameterName) { + String val = null; + + String pkgName; + final Package pkg = getClass().getPackage(); + if (pkg != null) { + pkgName = pkg.getName(); + } else { + final String className = getClass().getName(); + pkgName = new String(className.toCharArray(), 0, className + .lastIndexOf('.')); + } + val = System.getProperty(pkgName + "." + parameterName); + if (val != null) { + return val; + } + + // Try lowercased system properties + val = System.getProperty(pkgName + "." + parameterName.toLowerCase()); + return val; + } + + /** + * Gets an application or system property value. + * + * @param parameterName + * the Name or the parameter. + * @param defaultValue + * the Default to be used. + * @return String value or default if not found + */ + private String getApplicationOrSystemProperty(String parameterName, + String defaultValue) { + + String val = null; + + // Try application properties + val = getApplicationProperty(parameterName); + if (val != null) { + return val; + } + + // Try system properties + val = getSystemProperty(parameterName); + if (val != null) { + return val; + } + + return defaultValue; + } + + /** + * Returns true if the servlet is running in production mode. Production + * mode disables all debug facilities. + * + * @return true if in production mode, false if in debug mode + */ + public boolean isProductionMode() { + return productionMode; + } + + /** + * Receives standard HTTP requests from the public service method and + * dispatches them. + * + * @param request + * the object that contains the request the client made of the + * servlet. + * @param response + * the object that contains the response the servlet returns to + * the client. + * @throws ServletException + * if an input or output error occurs while the servlet is + * handling the TRACE request. + * @throws IOException + * if the request for the TRACE cannot be handled. + */ + @Override + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + + // check if we should serve static files (widgetsets, themes) + if (serveStaticResources(request, response)) { + return; + } + + Application application = null; + RequestType requestType = getRequestType(request); + + try { + // Find out which application this request is related to + application = findApplicationInstance(request, requestType); + if (application == null) { + return; + } + + /* + * Get or create a WebApplicationContext and an ApplicationManager + * for the session + */ + WebApplicationContext webApplicationContext = WebApplicationContext + .getApplicationContext(request.getSession()); + CommunicationManager applicationManager = webApplicationContext + .getApplicationManager(application, this); + + /* Update browser information from the request */ + webApplicationContext.getBrowser().updateBrowserProperties(request); + + /* + * Transaction starts. Call transaction listeners. Transaction end + * is called in the finally block below. + */ + webApplicationContext.startTransaction(application, request); + + // TODO Add screen height and width to the GWT client + + /* Handle the request */ + if (requestType == RequestType.FILE_UPLOAD) { + applicationManager.handleFileUpload(request, response); + return; + } else if (requestType == RequestType.UIDL) { + // Handles AJAX UIDL requests + applicationManager.handleUidlRequest(request, response, this); + return; + } + + // Removes application if it has stopped + if (!application.isRunning()) { + // FIXME How can this be reached? + endApplication(request, response, application); + return; + } + + // Finds the window within the application + Window window = getApplicationWindow(request, application); + if (window == null) { + throw new ServletException(ERROR_NO_WINDOW_FOUND); + } + + // Sets terminal type for the window, if not already set + if (window.getTerminal() == null) { + window.setTerminal(webApplicationContext.getBrowser()); + } + + // Handle parameters + final Map parameters = request.getParameterMap(); + if (window != null && parameters != null) { + window.handleParameters(parameters); + } + + /* + * Call the URI handlers and if this turns out to be a download + * request, send the file to the client + */ + if (handleURI(applicationManager, window, request, response)) { + return; + } + + String themeName = getThemeForWindow(request, window); + + // Handles theme resource requests + if (handleResourceRequest(request, response, themeName)) { + return; + } + + // Send initial AJAX page that kickstarts Toolkit application + writeAjaxPage(request, response, window, themeName, application); + + } catch (final SessionExpired e) { + // Session has expired, notify user + handleServiceSessionExpired(request, response); + } catch (final GeneralSecurityException e) { + handleServiceSecurityException(request, response); + } catch (final Throwable e) { + handleServiceException(request, response, application, e); + } finally { + // Notifies transaction end + if (application != null) { + ((WebApplicationContext) application.getContext()) + .endTransaction(application, request); + } + + // Work-around for GAE session problem. Explicitly touch session so + // it is re-serialized. + HttpSession session = request.getSession(false); + if (session != null) { + session.setAttribute("sessionUpdated", new Date().getTime()); + } + } + } + + protected ClassLoader getClassLoader() throws ServletException { + // Gets custom class loader + final String classLoaderName = getApplicationOrSystemProperty( + "ClassLoader", null); + ClassLoader classLoader; + if (classLoaderName == null) { + classLoader = getClass().getClassLoader(); + } else { + try { + final Class classLoaderClass = getClass().getClassLoader() + .loadClass(classLoaderName); + final Constructor c = classLoaderClass + .getConstructor(new Class[] { ClassLoader.class }); + classLoader = (ClassLoader) c + .newInstance(new Object[] { getClass().getClassLoader() }); + } catch (final Exception e) { + throw new ServletException( + "Could not find specified class loader: " + + classLoaderName, e); + } + } + return classLoader; + } + + /** + * Send notification to client's application. Used to notify client of + * critical errors and session expiration due to long inactivity. Server has + * no knowledge of what application client refers to. + * + * @param request + * the HTTP request instance. + * @param response + * the HTTP response to write to. + * @param caption + * for the notification + * @param message + * for the notification + * @param url + * url to load after message, null for current page + * @throws IOException + * if the writing failed due to input/output error. + */ + void criticalNotification(HttpServletRequest request, + HttpServletResponse response, String caption, String message, + String url) throws IOException { + + // clients JS app is still running, but server application either + // no longer exists or it might fail to perform reasonably. + // send a notification to client's application and link how + // to "restart" application. + + if (caption != null) { + caption = "\"" + caption + "\""; + } + if (message != null) { + message = "\"" + message + "\""; + } + if (url != null) { + url = "\"" + url + "\""; + } + + // Set the response type + response.setContentType("application/json; charset=UTF-8"); + final ServletOutputStream out = response.getOutputStream(); + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {" + + "\"appError\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}}, \"resources\": {}, \"locales\":[]}]"); + outWriter.flush(); + outWriter.close(); + out.flush(); + } + + /** + * Returns the application instance to be used for the request. If an + * existing instance is not found a new one is created or null is returned + * to indicate that the application is not available. + * + * @param request + * @param requestType + * @return + * @throws MalformedURLException + * @throws SAXException + * @throws IllegalAccessException + * @throws InstantiationException + * @throws ServletException + * @throws SessionExpired + */ + private Application findApplicationInstance(HttpServletRequest request, + RequestType requestType) throws MalformedURLException, + SAXException, IllegalAccessException, InstantiationException, + ServletException, SessionExpired { + + final boolean restartApplication = (request + .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); + final boolean closeApplication = (request + .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); + + Application application = getExistingApplication(request); + if (application != null) { + /* + * There is an existing application. We can use this as long as the + * user not specifically requested to close or restart it. + */ + if (restartApplication) { + closeApplication(application, request.getSession(false)); + return createApplication(request); + } else if (closeApplication) { + closeApplication(application, request.getSession(false)); + return null; + } else { + return application; + } + } + + // No existing application was found + if (requestType == RequestType.UIDL && isRepaintAll(request)) { + /* + * UIDL request contains valid repaintAll=1 event, the user probably + * wants to initiate a new application through a custom index.html + * without using writeAjaxPage. + */ + return createApplication(request); + + } else if (requestType == RequestType.OTHER) { + /* + * TODO Should *any* request really create a new application + * instance if none was found? + */ + return createApplication(request); + + } else { + /* The application was not found. Assume the session has expired. */ + throw new SessionExpired(); + } + + } + + /** + * Gets resource path using different implementations. Required to + * supporting different servlet container implementations (application + * servers). + * + * @param servletContext + * @param path + * the resource path. + * @return the resource path. + */ + protected static String getResourcePath(ServletContext servletContext, + String path) { + String resultPath = null; + resultPath = servletContext.getRealPath(path); + if (resultPath != null) { + return resultPath; + } else { + try { + final URL url = servletContext.getResource(path); + resultPath = url.getFile(); + } catch (final Exception e) { + // FIXME: Handle exception + e.printStackTrace(); + } + } + return resultPath; + } + + /** + * 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 stream + * the download stream. + * + * @param request + * the HTTP request instance. + * @param response + * the HTTP response to write to. + * @throws IOException + * + * @see com.itmill.toolkit.terminal.URIHandler + */ + private void handleDownload(DownloadStream stream, + HttpServletRequest request, HttpServletResponse response) + throws IOException { + + if (stream.getParameter("Location") != null) { + response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + response.addHeader("Location", stream.getParameter("Location")); + return; + } + + // Download from given stream + final InputStream data = stream.getStream(); + if (data != null) { + + // Sets content type + response.setContentType(stream.getContentType()); + + // Sets cache headers + final long cacheTime = stream.getCacheTime(); + if (cacheTime <= 0) { + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + } else { + response.setHeader("Cache-Control", "max-age=" + cacheTime + / 1000); + response.setDateHeader("Expires", System.currentTimeMillis() + + cacheTime); + response.setHeader("Pragma", "cache"); // Required to apply + // caching in some + // Tomcats + } + + // Copy download stream parameters directly + // to HTTP headers. + final Iterator i = stream.getParameterNames(); + if (i != null) { + while (i.hasNext()) { + final String param = (String) i.next(); + response.setHeader(param, stream.getParameter(param)); + } + } + + // suggest local filename from DownloadStream if Content-Disposition + // not explicitly set + String contentDispositionValue = stream + .getParameter("Content-Disposition"); + if (contentDispositionValue == null) { + contentDispositionValue = "filename=\"" + stream.getFileName() + + "\""; + response.setHeader("Content-Disposition", + contentDispositionValue); + } + + int bufferSize = stream.getBufferSize(); + if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) { + bufferSize = DEFAULT_BUFFER_SIZE; + } + final byte[] buffer = new byte[bufferSize]; + int bytesRead = 0; + + final OutputStream out = response.getOutputStream(); + + while ((bytesRead = data.read(buffer)) > 0) { + out.write(buffer, 0, bytesRead); + out.flush(); + } + out.close(); + + } + + } + + /** + * Creates and starts a new application. This is not meant to be overridden. + * Override getNewApplication to create the application instance in a custom + * way. + * + * @param request + * @return + * @throws ServletException + * @throws MalformedURLException + */ + private Application createApplication(HttpServletRequest request) + throws ServletException, MalformedURLException { + Application newApplication = getNewApplication(request); + + // Create application + final URL applicationUrl = getApplicationUrl(request); + + // Initial locale comes from the request + Locale locale = request.getLocale(); + HttpSession session = request.getSession(); + + // Start the newly created application + startApplication(session, newApplication, applicationUrl, locale); + + return newApplication; + } + + private void handleServiceException(HttpServletRequest request, + HttpServletResponse response, Application application, Throwable e) + throws IOException, ServletException { + // if this was an UIDL request, response UIDL back to client + if (getRequestType(request) == RequestType.UIDL) { + Application.SystemMessages ci = getSystemMessages(); + criticalNotification(request, response, ci + .getInternalErrorCaption(), ci.getInternalErrorMessage(), + ci.getInternalErrorURL()); + if (application != null) { + application.getErrorHandler() + .terminalError(new RequestError(e)); + } else { + throw new ServletException(e); + } + } else { + // Re-throw other exceptions + throw new ServletException(e); + } + + } + + private String getThemeForWindow(HttpServletRequest request, Window window) { + // Finds theme name + String themeName = window.getTheme(); + if (request.getParameter(URL_PARAMETER_THEME) != null) { + themeName = request.getParameter(URL_PARAMETER_THEME); + } + + if (themeName == null) { + themeName = "default"; + } + + return themeName; + } + + /** + * Calls URI handlers for the request. If an URI handler returns a + * DownloadStream the stream is passed to the client for downloading. + * + * @param applicationManager + * @param window + * @param request + * @param response + * @return true if an DownloadStream was sent to the client, false otherwise + * @throws IOException + */ + private boolean handleURI(CommunicationManager applicationManager, + Window window, HttpServletRequest request, + HttpServletResponse response) throws IOException { + // Handles the URI + DownloadStream download = applicationManager.handleURI(window, request, + response); + + // A download request + if (download != null) { + // Client downloads an resource + handleDownload(download, request, response); + return true; + } + + return false; + } + + private void handleServiceSessionExpired(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + if (isOnUnloadRequest(request)) { + /* + * Request was an unload request (e.g. window close event) and the + * client expects no response if it fails. + */ + return; + } + + try { + Application.SystemMessages ci = getSystemMessages(); + if (getRequestType(request) != RequestType.UIDL) { + // 'plain' http req - e.g. browser reload; + // just go ahead redirect the browser + response.sendRedirect(ci.getSessionExpiredURL()); + } else { + // send uidl redirect + criticalNotification(request, response, ci + .getSessionExpiredCaption(), ci + .getSessionExpiredMessage(), ci.getSessionExpiredURL()); + /* + * Invalidate session (weird to have session if we're saying + * that it's expired, and worse: portal integration will fail + * since the session is not created by the portal. + */ + request.getSession().invalidate(); + } + } catch (SystemMessageException ee) { + throw new ServletException(ee); + } + + } + + private void handleServiceSecurityException(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + if (isOnUnloadRequest(request)) { + /* + * Request was an unload request (e.g. window close event) and the + * client expects no response if it fails. + */ + return; + } + + // TODO handle differently? + // Invalid security key, show session expired message for now. + handleServiceSessionExpired(request, response); + } + + /** + * Creates a new application for the given request. + * + * @param request + * the HTTP request. + * @return A new Application instance. + * @throws ServletException + */ + protected abstract Application getNewApplication(HttpServletRequest request) + throws ServletException; + + /** + * Starts the application if it is not already running. Ensures the + * application is added to the WebApplicationContext. + * + * @param session + * @param application + * @param applicationUrl + * @param locale + * @throws ServletException + */ + private void startApplication(HttpSession session, Application application, + URL applicationUrl, Locale locale) throws ServletException { + if (application == null) { + throw new ServletException( + "Application is null and can't be started"); + } + + if (!application.isRunning()) { + final WebApplicationContext context = WebApplicationContext + .getApplicationContext(session); + // final URL applicationUrl = getApplicationUrl(request); + context.addApplication(application); + application.setLocale(locale); + application.start(applicationUrl, applicationProperties, context); + } + } + + /** + * Check if this is a request for a static resource and, if it is, serve the + * resource to the client. Returns true if a file was served and the request + * has been handled, false otherwise. + * + * @param request + * @param response + * @return + * @throws IOException + * @throws ServletException + */ + private boolean serveStaticResources(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + // FIXME What does 10 refer to? + String pathInfo = request.getPathInfo(); + if (pathInfo == null || pathInfo.length() <= 10) { + return false; + } + + if ((request.getContextPath() != null) + && (request.getRequestURI().startsWith("/ITMILL/"))) { + serveStaticResourcesInITMILL(request.getRequestURI(), response); + return true; + } else if (request.getRequestURI().startsWith( + request.getContextPath() + "/ITMILL/")) { + serveStaticResourcesInITMILL(request.getRequestURI().substring( + request.getContextPath().length()), response); + return true; + } + + return false; + } + + /** + * Serve resources from ITMILL directory. + * + * @param request + * @param response + * @throws IOException + * @throws ServletException + */ + private void serveStaticResourcesInITMILL(String filename, + HttpServletResponse response) throws IOException, ServletException { + + final ServletContext sc = getServletContext(); + InputStream is = sc.getResourceAsStream(filename); + if (is == null) { + // try if requested file is found from classloader + + // strip leading "/" otherwise stream from JAR wont work + filename = filename.substring(1); + is = getClassLoader().getResourceAsStream(filename); + + if (is == null) { + // cannot serve requested file + System.err + .println("Requested resource [" + + filename + + "] not found from filesystem or through class loader." + + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/ITMILL folder."); + response.setStatus(404); + return; + } + } + final String mimetype = sc.getMimeType(filename); + if (mimetype != null) { + response.setContentType(mimetype); + } + final OutputStream os = response.getOutputStream(); + final byte buffer[] = new byte[20000]; + int bytes; + while ((bytes = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytes); + } + } + + private enum RequestType { + FILE_UPLOAD, UIDL, OTHER; + } + + private RequestType getRequestType(HttpServletRequest request) { + if (isFileUploadRequest(request)) { + return RequestType.FILE_UPLOAD; + } else if (isUIDLRequest(request)) { + return RequestType.UIDL; + } + + return RequestType.OTHER; + } + + private boolean isUIDLRequest(HttpServletRequest request) { + String pathInfo = getRequestPathInfo(request); + + if (pathInfo == null) { + return false; + } + + String compare = AJAX_UIDL_URI; + + if (pathInfo.startsWith(compare + "/") || pathInfo.endsWith(compare)) { + return true; + } + + return false; + } + + private boolean isFileUploadRequest(HttpServletRequest request) { + return ServletFileUpload.isMultipartContent(request); + } + + private boolean isOnUnloadRequest(HttpServletRequest request) { + return request.getParameter(ApplicationConnection.PARAM_UNLOADBURST) != null; + } + + /** + * Get system messages from the current application class + * + * @return + */ + protected SystemMessages getSystemMessages() { + try { + Class appCls = getApplicationClass(); + Method m = appCls.getMethod("getSystemMessages", (Class[]) null); + return (Application.SystemMessages) m.invoke(null, (Object[]) null); + } catch (ClassNotFoundException e) { + // This should never happen + throw new SystemMessageException(e); + } catch (SecurityException e) { + throw new SystemMessageException( + "Application.getSystemMessage() should be static public", e); + } catch (NoSuchMethodException e) { + // This is completely ok and should be silently ignored + } catch (IllegalArgumentException e) { + // This should never happen + throw new SystemMessageException(e); + } catch (IllegalAccessException e) { + throw new SystemMessageException( + "Application.getSystemMessage() should be static public", e); + } catch (InvocationTargetException e) { + // This should never happen + throw new SystemMessageException(e); + } + return Application.getSystemMessages(); + } + + protected abstract Class getApplicationClass() + throws ClassNotFoundException; + + /** + * Resolve widgetset URL. Widgetset is not application specific. + * + * @param request + * @return + * @throws MalformedURLException + */ + String getWidgetsetLocation(HttpServletRequest request) + throws MalformedURLException { + URL applicationURL = getApplicationUrl(request); + + String applicationPath = applicationURL.getPath(); + String pathParts[] = applicationPath.split("\\/"); + + // if context is specified add it to widgetsetUrl + String ctxPath = request.getContextPath(); + if (ctxPath.length() == 0 + && request.getAttribute("javax.servlet.include.context_path") != null) { + // include request (e.g portlet), get context path from + // attribute + ctxPath = (String) request + .getAttribute("javax.servlet.include.context_path"); + } + + String widgetsetPath = ""; + if (pathParts.length > 1 + && pathParts[1].equals(ctxPath.replaceAll("\\/", ""))) { + widgetsetPath = "/" + pathParts[1]; + } + + return widgetsetPath; + + } + + /** + * + * @param request + * the HTTP request. + * @param response + * the HTTP response to write to. + * @param out + * @param unhandledParameters + * @param window + * @param terminalType + * @param theme + * @throws IOException + * if the writing failed due to input/output error. + * @throws MalformedURLException + * if the application is denied access the persistent data store + * represented by the given URL. + */ + private void writeAjaxPage(HttpServletRequest request, + HttpServletResponse response, Window window, String themeName, + Application application) throws IOException, MalformedURLException, + ServletException { + + // e.g portlets only want a html fragment + boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null); + if (fragment) { + request.setAttribute(Application.class.getName(), application); + } + + final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( + response.getOutputStream())); + String pathInfo = getRequestPathInfo(request); + if (pathInfo == null) { + pathInfo = "/"; + } + + String title = ((window.getCaption() == null) ? "IT Mill Toolkit 5" + : window.getCaption()); + + String widgetset = null; + // request widgetset takes precedence (e.g portlet include) + Object reqParam = request.getAttribute(REQUEST_WIDGETSET); + try { + widgetset = (String) reqParam; + } catch (Exception e) { + // FIXME: Handle exception + System.err.println("Warning: request param '" + REQUEST_WIDGETSET + + "' could not be used (is not a String)" + e); + } + + // TODO: Any reason this could not use + // getApplicationOrSystemProperty with DEFAULT_WIDGETSET as default + // value ? + if (widgetset == null) { + widgetset = getApplicationProperty(PARAMETER_WIDGETSET); + } + if (widgetset == null) { + widgetset = DEFAULT_WIDGETSET; + } + + /* Fetch relative url to application */ + // don't use server and port in uri. It may cause problems with some + // virtual server configurations which lose the server name + String appUrl = getApplicationUrl(request).getPath(); + if (appUrl.endsWith("/")) { + appUrl = appUrl.substring(0, appUrl.length() - 1); + } + + final String widgetsetPath = getWidgetsetLocation(request); + + final String staticFilePath = getApplicationOrSystemProperty( + PARAMETER_ITMILL_RESOURCES, widgetsetPath); + + // Default theme does not use theme URI + String themeUri = null; + if (themeName != null) { + // Using custom theme + themeUri = staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName; + } + + if (!fragment) { + // Window renders are not cacheable + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setContentType("text/html; charset=UTF-8"); + + // write html header + page.write("\n"); + + page.write("\n\n"); + page + .write("\n"); + page + .write("\n"); + page.write(""); + + // Add favicon links + page + .write(""); + page + .write(""); + + page.write("" + title + ""); + + page + .write("\n\n\n"); + } + + String appId = appUrl; + if ("".equals(appUrl)) { + appId = "ROOT"; + } + appId = appId.replaceAll("[^a-zA-Z0-9]", ""); + // Add hashCode to the end, so that it is still (sort of) predictable, + // but indicates that it should not be used in CSS and such: + int hashCode = appId.hashCode(); + if (hashCode < 0) { + hashCode = -hashCode; + } + appId = appId + "-" + hashCode; + + // Get system messages + Application.SystemMessages systemMessages = null; + try { + systemMessages = getSystemMessages(); + } catch (SystemMessageException e) { + // failing to get the system messages is always a problem + throw new ServletException("CommunicationError!", e); + } + + if (isGecko17(request)) { + // special start page for gecko 1.7 versions. Firefox 1.0 is not + // supported, but the hack is make it possible to use linux and + // hosted mode browser for debugging. Note that due this hack, + // debugging gwt code in portals with linux will be problematic if + // there are multiple toolkit portlets visible at the same time. + // TODO remove this when hosted mode on linux gets newer gecko + + page.write("\n"); + page.write("\n"); + page.write("\n"); + + if (themeName != null) { + // Custom theme's stylesheet, load only once, in different + // script + // tag to be dominate styles injected by widget + // set + page.write("\n"); + } + + } else { + page.write("\n"); + + if (themeName != null) { + // Custom theme's stylesheet, load only once, in different + // script + // tag to be dominate styles injected by widget + // set + page.write("\n"); + } + } + + // Warn if the widgetset has not been loaded after 15 seconds on + // inactivity + page.write("\n"); + + String style = null; + reqParam = request.getAttribute(REQUEST_APPSTYLE); + if (reqParam != null) { + style = "style=\"" + reqParam + "\""; + } + /*- Add classnames; + * .i-app + * .i-app-loading + * .i-app- + * .i-theme- + */ + String appClass = "i-app-"; + try { + appClass += getApplicationClass().getSimpleName(); + } catch (ClassNotFoundException e) { + appClass += "unknown"; + + e.printStackTrace(); + } + String themeClass = ""; + if (themeName != null) { + themeClass = "i-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", ""); + } + + page.write("
\n"); + + if (!fragment) { + page.write(""); + page.write("\n\n"); + } + page.close(); + + } + + /** + * Returns a message printed for browsers without scripting support or if + * browsers scripting support is disabled. + */ + protected String getNoScriptMessage() { + return "You have to enable javascript in your browser to use an application built with IT Mill Toolkit."; + } + + private boolean isGecko17(HttpServletRequest request) { + final WebBrowser browser = WebApplicationContext.getApplicationContext( + request.getSession()).getBrowser(); + if (browser != null && browser.getBrowserApplication() != null) { + if (browser.getBrowserApplication().indexOf("rv:1.7.") > 0 + && browser.getBrowserApplication().indexOf("Gecko") > 0) { + return true; + } + } + return false; + } + + /** + * Handles theme resource file requests. Resources supplied with the themes + * are provided by the WebAdapterServlet. + * + * @param request + * the HTTP request. + * @param response + * the HTTP response. + * @return boolean true if the request was handled and further + * processing should be suppressed, false otherwise. + * @throws ServletException + * if an exception has occurred that interferes with the + * servlet's normal operation. + */ + private boolean handleResourceRequest(HttpServletRequest request, + HttpServletResponse response, String themeName) + throws ServletException { + + // If the resource path is unassigned, initialize it + if (resourcePath == null) { + resourcePath = request.getContextPath() + request.getServletPath() + + RESOURCE_URI; + // WebSphere Application Server related fix + resourcePath = resourcePath.replaceAll("//", "/"); + } + + String resourceId = request.getPathInfo(); + + // Checks if this really is a resource request + if (resourceId == null || !resourceId.startsWith(RESOURCE_URI)) { + return false; + } + + // Checks the resource type + resourceId = resourceId.substring(RESOURCE_URI.length()); + InputStream data = null; + + // Gets theme resources + try { + data = getServletContext().getResourceAsStream( + THEME_DIRECTORY_PATH + themeName + "/" + resourceId); + } catch (final Exception e) { + // FIXME: Handle exception + e.printStackTrace(); + data = null; + } + + // Writes the response + try { + if (data != null) { + response.setContentType(FileTypeResolver + .getMIMEType(resourceId)); + + // Use default cache time for theme resources + response.setHeader("Cache-Control", "max-age=" + + DEFAULT_THEME_CACHETIME / 1000); + response.setDateHeader("Expires", System.currentTimeMillis() + + DEFAULT_THEME_CACHETIME); + response.setHeader("Pragma", "cache"); // Required to apply + // caching in some + // Tomcats + + // Writes the data to client + final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int bytesRead = 0; + final OutputStream out = response.getOutputStream(); + while ((bytesRead = data.read(buffer)) > 0) { + out.write(buffer, 0, bytesRead); + } + out.close(); + data.close(); + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + } catch (final java.io.IOException e) { + // FIXME: Handle exception + System.err.println("Resource transfer failed: " + + request.getRequestURI() + ". (" + e.getMessage() + ")"); + } + + return true; + } + + /** + * Gets the current application URL from request. + * + * @param request + * the HTTP request. + * @throws MalformedURLException + * if the application is denied access to the persistent data + * store represented by the given URL. + */ + URL getApplicationUrl(HttpServletRequest request) + throws MalformedURLException { + final URL reqURL = new URL( + (request.isSecure() ? "https://" : "http://") + + request.getServerName() + + ((request.isSecure() && request.getServerPort() == 443) + || (!request.isSecure() && request + .getServerPort() == 80) ? "" : ":" + + request.getServerPort()) + + request.getRequestURI()); + String servletPath = ""; + if (request.getAttribute("javax.servlet.include.servlet_path") != null) { + // this is an include request + servletPath = request.getAttribute( + "javax.servlet.include.context_path").toString() + + request + .getAttribute("javax.servlet.include.servlet_path"); + + } else { + servletPath = request.getContextPath() + request.getServletPath(); + } + + if (servletPath.length() == 0 + || servletPath.charAt(servletPath.length() - 1) != '/') { + servletPath = servletPath + "/"; + } + + URL u = new URL(reqURL, servletPath); + return u; + } + + /** + * Gets the existing application for given request. Looks for application + * instance for given request based on the requested URL. + * + * @param request + * the HTTP request. + * @return Application instance, or null if the URL does not map to valid + * application. + * @throws MalformedURLException + * if the application is denied access to the persistent data + * store represented by the given URL. + * @throws SAXException + * @throws IllegalAccessException + * @throws InstantiationException + */ + private Application getExistingApplication(HttpServletRequest request) + throws MalformedURLException, SAXException, IllegalAccessException, + InstantiationException { + + // Ensures that the session is still valid + final HttpSession session = request.getSession(true); + + WebApplicationContext context = WebApplicationContext + .getApplicationContext(session); + + // Gets application list for the session. + final Collection applications = context.getApplications(); + + // Search for the application (using the application URI) from the list + for (final Iterator i = applications.iterator(); i.hasNext();) { + final Application sessionApplication = (Application) i.next(); + final String sessionApplicationPath = sessionApplication.getURL() + .getPath(); + String requestApplicationPath = getApplicationUrl(request) + .getPath(); + + if (requestApplicationPath.equals(sessionApplicationPath)) { + // Found a running application + if (sessionApplication.isRunning()) { + return sessionApplication; + } + // Application has stopped, so remove it before creating a new + // application + WebApplicationContext.getApplicationContext(session) + .removeApplication(sessionApplication); + break; + } + } + + // Existing application not found + return null; + } + + /** + * Ends the application. + * + * @param request + * the HTTP request. + * @param response + * the HTTP response to write to. + * @param application + * the application to end. + * @throws IOException + * if the writing failed due to input/output error. + */ + private void endApplication(HttpServletRequest request, + HttpServletResponse response, Application application) + throws IOException { + + String logoutUrl = application.getLogoutURL(); + if (logoutUrl == null) { + logoutUrl = application.getURL().toString(); + } + + final HttpSession session = request.getSession(); + if (session != null) { + WebApplicationContext.getApplicationContext(session) + .removeApplication(application); + } + + response.sendRedirect(response.encodeRedirectURL(logoutUrl)); + } + + /** + * 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. + * @return Window matching the given URI or null if not found. + * @throws ServletException + * if an exception has occurred that interferes with the + * servlet's normal operation. + */ + private Window getApplicationWindow(HttpServletRequest request, + Application application) throws ServletException { + + Window window = null; + + // Finds the window where the request is handled + String path = getRequestPathInfo(request); + + // Main window as the URI is empty + if (path == null || path.length() == 0 || path.equals("/") + || path.startsWith("/APP/")) { + window = application.getMainWindow(); + } else { + String windowName = null; + if (path.charAt(0) == '/') { + path = path.substring(1); + } + final int index = path.indexOf('/'); + if (index < 0) { + windowName = path; + path = ""; + } else { + windowName = path.substring(0, index); + path = path.substring(index + 1); + } + window = application.getWindow(windowName); + + if (window == null) { + // By default, we use main window + window = application.getMainWindow(); + } else if (!window.isVisible()) { + // Implicitly painting without actually invoking paint() + window.requestRepaintRequests(); + + // If the window is invisible send a blank page + return null; + } + } + + return window; + } + + String getRequestPathInfo(HttpServletRequest request) { + return request.getPathInfo(); + } + + /** + * Gets relative location of a theme resource. + * + * @param theme + * the Theme name. + * @param resource + * the Theme resource. + * @return External URI specifying the resource + */ + public String getResourceLocation(String theme, ThemeResource resource) { + + if (resourcePath == null) { + return resource.getResourceId(); + } + return resourcePath + theme + "/" + resource.getResourceId(); + } + + private boolean isRepaintAll(HttpServletRequest request) { + return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) + && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); + } + + private void closeApplication(Application application, HttpSession session) { + if (application == null) { + return; + } + + application.close(); + if (session != null) { + WebApplicationContext context = WebApplicationContext + .getApplicationContext(session); + context.applicationToAjaxAppMgrMap.remove(application); + // FIXME: Move to WebApplicationContext + context.removeApplication(application); + } + } + + /** + * Implementation of ParameterHandler.ErrorEvent interface. + */ + public class ParameterHandlerErrorImpl implements + ParameterHandler.ErrorEvent, Serializable { + + private ParameterHandler owner; + + private Throwable throwable; + + /** + * Gets the contained throwable. + * + * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable() + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Gets the source ParameterHandler. + * + * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler() + */ + public ParameterHandler getParameterHandler() { + return owner; + } + + } + + /** + * 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; + } + + /** + * Gets the contained throwable. + * + * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable() + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Gets the source URIHandler. + * + * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler() + */ + public URIHandler getURIHandler() { + return owner; + } + } + + public class RequestError implements Terminal.ErrorEvent, Serializable { + + private final Throwable throwable; + + public RequestError(Throwable throwable) { + this.throwable = throwable; + } + + public Throwable getThrowable() { + return throwable; + } + + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationRunnerServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationRunnerServlet.java new file mode 100644 index 0000000000..1932e0adc9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationRunnerServlet.java @@ -0,0 +1,173 @@ +package com.itmill.toolkit.terminal.gwt.server; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.itmill.toolkit.Application; + +@SuppressWarnings("serial") +public class ApplicationRunnerServlet extends AbstractApplicationServlet { + + /** + * The name of the application class currently used. Only valid within one + * request. + */ + String applicationClassName = ""; + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + super.init(servletConfig); + } + + @Override + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + applicationClassName = getApplicationRunnerApplicationClassName(request); + super.service(request, response); + applicationClassName = ""; + + } + + @Override + URL getApplicationUrl(HttpServletRequest request) + throws MalformedURLException { + URL url = super.getApplicationUrl(request); + + String path = url.toString(); + path += applicationClassName; + path += "/"; + + return new URL(path); + } + + @Override + protected Application getNewApplication(HttpServletRequest request) + throws ServletException { + + // Creates a new application instance + try { + Class applicationClass = getClassLoader().loadClass( + applicationClassName); + final Application application = (Application) applicationClass + .newInstance(); + + return application; + } catch (final IllegalAccessException e) { + throw new ServletException(e); + } catch (final InstantiationException e) { + throw new ServletException(e); + } catch (final ClassNotFoundException e) { + throw new ServletException( + new InstantiationException( + "Failed to load application class: " + + applicationClassName)); + } + + } + + private String getApplicationRunnerApplicationClassName( + HttpServletRequest request) { + return getApplicationRunnerURIs(request).applicationClassname; + } + + private static class URIS { + String widgetsetPath; + String applicationURI; + String context; + String runner; + String applicationClassname; + + } + + /** + * Parses application runner URIs. + * + * If request URL is e.g. + * http://localhost:8080/itmill/run/com.itmill.toolkit.demo.Calc then + *
    + *
  • context=itmill
  • + *
  • Runner servlet=run
  • + *
  • Toolkit application=com.itmill.toolkit.demo.Calc
  • + *
+ * + * @param request + * @return string array containing widgetset URI, application URI and + * context, runner, application classname + */ + private static URIS getApplicationRunnerURIs(HttpServletRequest request) { + final String[] urlParts = request.getRequestURI().toString().split( + "\\/"); + String context = null; + String runner = null; + URIS uris = new URIS(); + String applicationClassname = null; + String contextPath = request.getContextPath(); + if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) { + // class name comes after web context and runner application + context = urlParts[1]; + runner = urlParts[2]; + if (urlParts.length == 3) { + throw new IllegalArgumentException("No application specified"); + } + applicationClassname = urlParts[3]; + + uris.widgetsetPath = "/" + context; + uris.applicationURI = "/" + context + "/" + runner + "/" + + applicationClassname; + uris.context = context; + uris.runner = runner; + uris.applicationClassname = applicationClassname; + } else { + // no context + context = ""; + runner = urlParts[1]; + if (urlParts.length == 2) { + throw new IllegalArgumentException("No application specified"); + } + applicationClassname = urlParts[2]; + + uris.widgetsetPath = "/"; + uris.applicationURI = "/" + runner + "/" + applicationClassname; + uris.context = context; + uris.runner = runner; + uris.applicationClassname = applicationClassname; + } + return uris; + } + + // @Override + @Override + protected Class getApplicationClass() throws ClassNotFoundException { + // TODO use getClassLoader() ? + return getClass().getClassLoader().loadClass(applicationClassName); + } + + @Override + String getRequestPathInfo(HttpServletRequest request) { + String path = request.getPathInfo(); + if (path == null) { + return null; + } + + path = path.substring(1 + applicationClassName.length()); + return path; + } + + @Override + String getWidgetsetLocation(HttpServletRequest request) { + URIS uris = getApplicationRunnerURIs(request); + String widgetsetPath = uris.widgetsetPath; + if (widgetsetPath.equals("/")) { + widgetsetPath = ""; + } + + return widgetsetPath; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java index 17b6c66c45..bf26df9c5c 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java @@ -4,47 +4,10 @@ package com.itmill.toolkit.terminal.gwt.server; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.util.Collection; -import java.util.Date; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; - -import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.xml.sax.SAXException; import com.itmill.toolkit.Application; -import com.itmill.toolkit.Application.SystemMessages; -import com.itmill.toolkit.external.org.apache.commons.fileupload.servlet.ServletFileUpload; -import com.itmill.toolkit.service.FileTypeResolver; -import com.itmill.toolkit.terminal.DownloadStream; -import com.itmill.toolkit.terminal.ParameterHandler; -import com.itmill.toolkit.terminal.Terminal; -import com.itmill.toolkit.terminal.ThemeResource; -import com.itmill.toolkit.terminal.URIHandler; -import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; -import com.itmill.toolkit.ui.Window; /** * This servlet connects IT Mill Toolkit Application to Web. @@ -56,106 +19,10 @@ import com.itmill.toolkit.ui.Window; */ @SuppressWarnings("serial") -public class ApplicationServlet extends HttpServlet { - - /** - * Version number of this release. For example "5.0.0". - */ - public static final String VERSION; - - /** - * Major version number. For example 5 in 5.1.0. - */ - public static final int VERSION_MAJOR; - - /** - * Minor version number. For example 1 in 5.1.0. - */ - public static final int VERSION_MINOR; - - /** - * Builds number. For example 0-custom_tag in 5.0.0-custom_tag. - */ - public static final String VERSION_BUILD; - - /* Initialize version numbers from string replaced by build-script. */ - static { - if ("@VERSION@".equals("@" + "VERSION" + "@")) { - VERSION = "5.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD"; - } else { - VERSION = "@VERSION@"; - } - final String[] digits = VERSION.split("\\."); - VERSION_MAJOR = Integer.parseInt(digits[0]); - VERSION_MINOR = Integer.parseInt(digits[1]); - VERSION_BUILD = digits[2]; - } - - /** - * If the attribute is present in the request, a html fragment will be - * written instead of a whole page. - */ - public static final String REQUEST_FRAGMENT = ApplicationServlet.class - .getName() - + ".fragment"; - /** - * This request attribute forces widgetset used; e.g for portlets that can - * not have different widgetsets. - */ - public static final String REQUEST_WIDGETSET = ApplicationServlet.class - .getName() - + ".widgetset"; - /** - * This request attribute is used to add styles to the main element. E.g - * "height:500px" generates a style="height:500px" to the main element, - * useful from some embedding situations (e.g portlet include.) - */ - public static final String REQUEST_APPSTYLE = ApplicationServlet.class - .getName() - + ".style"; - - // Configurable parameter names - private static final String PARAMETER_DEBUG = "Debug"; - - private static final String PARAMETER_PRODUCTION_MODE = "productionMode"; - private static final String NOT_PRODUCTION_MODE_INFO = "=================================================================\nIT Mill Toolkit is running in DEBUG MODE.\nAdd productionMode=true to web.xml to disable debug features.\nTo show debug window, add ?debug to your application URL.\n================================================================="; - - private static final String PARAMETER_ITMILL_RESOURCES = "Resources"; - - private static final int DEFAULT_BUFFER_SIZE = 32 * 1024; - - private static final int MAX_BUFFER_SIZE = 64 * 1024; - - private static final String RESOURCE_URI = "/RES/"; - - private static final String AJAX_UIDL_URI = "/UIDL"; - - static final String THEME_DIRECTORY_PATH = "ITMILL/themes/"; - - private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24; - - static final String WIDGETSET_DIRECTORY_PATH = "ITMILL/widgetsets/"; - - // Name of the default widget set, used if not specified in web.xml - private static final String DEFAULT_WIDGETSET = "com.itmill.toolkit.terminal.gwt.DefaultWidgetSet"; - - // Widget set parameter name - private static final String PARAMETER_WIDGETSET = "widgetset"; +public class ApplicationServlet extends AbstractApplicationServlet { // Private fields - private Class applicationClass; - - private Properties applicationProperties; - - private String resourcePath = null; - - private boolean productionMode = false; - - // Is this servlet application runner - boolean isApplicationRunnerServlet = false; - - // If servlet is application runner, store request's classname - String applicationRunnerClassname = null; + private Class applicationClass; /** * Called by the servlet container to indicate to a servlet that the servlet @@ -168,1547 +35,50 @@ public class ApplicationServlet extends HttpServlet { * if an exception has occurred that interferes with the * servlet's normal operation. */ + @SuppressWarnings("unchecked") @Override public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { super.init(servletConfig); - // Get applicationRunner - final String applicationRunner = servletConfig - .getInitParameter("applicationRunner"); - if (applicationRunner != null) { - if ("true".equals(applicationRunner)) { - isApplicationRunnerServlet = true; - } else if ("false".equals(applicationRunner)) { - isApplicationRunnerServlet = false; - } else { - throw new ServletException( - "If applicationRunner parameter is given for an application, it must be 'true' or 'false'"); - } - } - - // Stores the application parameters into Properties object - applicationProperties = new Properties(); - for (final Enumeration e = servletConfig.getInitParameterNames(); e - .hasMoreElements();) { - final String name = (String) e.nextElement(); - applicationProperties.setProperty(name, servletConfig - .getInitParameter(name)); - } - - // Overrides with server.xml parameters - final ServletContext context = servletConfig.getServletContext(); - for (final Enumeration e = context.getInitParameterNames(); e - .hasMoreElements();) { - final String name = (String) e.nextElement(); - applicationProperties.setProperty(name, context - .getInitParameter(name)); - } - - // Check if the application is in production mode. - // We are in production mode if Debug=false or productionMode=true - if (getApplicationOrSystemProperty(PARAMETER_DEBUG, "true").equals( - "false")) { - // "Debug=true" is the old way and should no longer be used - productionMode = true; - } else if (getApplicationOrSystemProperty(PARAMETER_PRODUCTION_MODE, - "false").equals("true")) { - // "productionMode=true" is the real way to do it - productionMode = true; - } - - if (!productionMode) { - /* Print an information/warning message about running in debug mode */ - System.err.println(NOT_PRODUCTION_MODE_INFO); - } - // Loads the application class using the same class loader // as the servlet itself - if (!isApplicationRunnerServlet) { - // Gets the application class name - final String applicationClassName = servletConfig - .getInitParameter("application"); - if (applicationClassName == null) { - throw new ServletException( - "Application not specified in servlet parameters"); - } - try { - applicationClass = getClassLoader().loadClass( - applicationClassName); - } catch (final ClassNotFoundException e) { - throw new ServletException("Failed to load application class: " - + applicationClassName); - } - } else { - // This servlet is in application runner mode, it uses classloader - // later to create Applications based on URL - } - - } - - private ClassLoader getClassLoader() throws ServletException { - // Gets custom class loader - final String classLoaderName = getApplicationOrSystemProperty( - "ClassLoader", null); - ClassLoader classLoader; - if (classLoaderName == null) { - classLoader = getClass().getClassLoader(); - } else { - try { - final Class classLoaderClass = getClass().getClassLoader() - .loadClass(classLoaderName); - final Constructor c = classLoaderClass - .getConstructor(new Class[] { ClassLoader.class }); - classLoader = (ClassLoader) c - .newInstance(new Object[] { getClass().getClassLoader() }); - } catch (final Exception e) { - throw new ServletException( - "Could not find specified class loader: " - + classLoaderName, e); - } - } - return classLoader; - } - - /** - * Gets an application or system property value. - * - * @param parameterName - * the Name or the parameter. - * @param defaultValue - * the Default to be used. - * @return String value or default if not found - */ - private String getApplicationOrSystemProperty(String parameterName, - String defaultValue) { - - // Try application properties - String val = applicationProperties.getProperty(parameterName); - if (val != null) { - return val; - } - - // Try lowercased application properties for backward compability with - // 3.0.2 and earlier - val = applicationProperties.getProperty(parameterName.toLowerCase()); - if (val != null) { - return val; - } - - // Try system properties - String pkgName; - final Package pkg = getClass().getPackage(); - if (pkg != null) { - pkgName = pkg.getName(); - } else { - final String className = getClass().getName(); - pkgName = new String(className.toCharArray(), 0, className - .lastIndexOf('.')); - } - val = System.getProperty(pkgName + "." + parameterName); - if (val != null) { - return val; - } - - // Try lowercased system properties - val = System.getProperty(pkgName + "." + parameterName.toLowerCase()); - if (val != null) { - return val; - } - - return defaultValue; - } - - /** - * Receives standard HTTP requests from the public service method and - * dispatches them. - * - * @param request - * the object that contains the request the client made of the - * servlet. - * @param response - * the object that contains the response the servlet returns to - * the client. - * @throws ServletException - * if an input or output error occurs while the servlet is - * handling the TRACE request. - * @throws IOException - * if the request for the TRACE cannot be handled. - */ - @Override - protected void service(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - - // check if we should serve static files (widgetsets, themes) - if ((request.getPathInfo() != null) - && (request.getPathInfo().length() > 10)) { - if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/ITMILL/"))) { - serveStaticResourcesInITMILL(request.getRequestURI(), response); - return; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/ITMILL/")) { - serveStaticResourcesInITMILL(request.getRequestURI().substring( - request.getContextPath().length()), response); - return; - } - } - - Application application = null; - boolean UIDLrequest = false; - try { - // handle file upload if multipart request - if (ServletFileUpload.isMultipartContent(request)) { - application = getExistingApplication(request, response); - if (application == null) { - throw new SessionExpired(); - } - // Invokes context transaction listeners - // note: endTransaction is called on finalize below - ((WebApplicationContext) application.getContext()) - .startTransaction(application, request); - ((WebApplicationContext) application.getContext()) - .getApplicationManager(application, this) - .handleFileUpload(request, response); - return; - } - - // Update browser details - final WebBrowser browser = WebApplicationContext - .getApplicationContext(request.getSession()).getBrowser(); - browser.updateBrowserProperties(request); - // TODO Add screen height and width to the GWT client - - // Handles AJAX UIDL requests - if (request.getPathInfo() != null) { - - String compare = AJAX_UIDL_URI; - if (isApplicationRunnerServlet) { - final String[] URIparts = getApplicationRunnerURIs(request); - applicationRunnerClassname = URIparts[4]; - compare = "/" + applicationRunnerClassname + AJAX_UIDL_URI; - } - - if (request.getPathInfo().startsWith(compare + "/") - || request.getPathInfo().endsWith(compare)) { - UIDLrequest = true; - application = getExistingApplication(request, response); - if (application == null) { - // No existing applications found - final String repaintAll = request - .getParameter("repaintAll"); - if ((repaintAll != null) && (repaintAll.equals("1"))) { - // UIDL request contains valid repaintAll=1 event, - // probably user wants to initiate new application - // through custom index.html without writeAjaxPage - application = getNewApplication(request, response); - } else { - // UIDL request refers to non-existing application - throw new SessionExpired(); - } - } - - // Invokes context transaction listeners - // note: endTransaction is called on finalize below - ((WebApplicationContext) application.getContext()) - .startTransaction(application, request); - // Handle UIDL request - ((WebApplicationContext) application.getContext()) - .getApplicationManager(application, this) - .handleUidlRequest(request, response, this); - return; - } - } - - // Get existing application - application = getExistingApplication(request, response); - if (application == null - || request.getParameter("restartApplication") != null - || request.getParameter("closeApplication") != null) { - if (application != null) { - application.close(); - final HttpSession session = request.getSession(false); - if (session != null) { - WebApplicationContext.getApplicationContext(session).applicationToAjaxAppMgrMap - .remove(application); - WebApplicationContext.getApplicationContext(session) - .removeApplication(application); - } - } - if (request.getParameter("closeApplication") != null) { - return; - } - // Not found, creating new application - application = getNewApplication(request, response); - } - - // Invokes context transaction listeners - // note: endTransaction is called on finalize below - ((WebApplicationContext) application.getContext()) - .startTransaction(application, request); - - // Removes application if it has stopped - if (!application.isRunning()) { - endApplication(request, response, application); - return; - } - - // Finds the window within the application - Window window = null; - window = getApplicationWindow(request, application); - if (window == null) { - throw new ServletException( - "Application did not give any window, did you remember to setMainWindow()?"); - } - - // Handle parameters - final Map parameters = request.getParameterMap(); - if (window != null && parameters != null) { - window.handleParameters(parameters); - } - - // Is this a download request from application - DownloadStream download = null; - - // Handles the URI if the application is still running - download = ((WebApplicationContext) application.getContext()) - .getApplicationManager(application, this).handleURI(window, - request, response); - - // If this is not a download request - if (download == null) { - - // Sets terminal type for the window, if not already set - if (window.getTerminal() == null) { - window.setTerminal(browser); - } - - // Finds theme name - String themeName = window.getTheme(); - if (request.getParameter("theme") != null) { - themeName = request.getParameter("theme"); - } - - if (themeName == null) { - themeName = "default"; - } - - // Handles resource requests - if (handleResourceRequest(request, response, themeName)) { - return; - } - - // Send initial AJAX page that kickstarts Toolkit application - writeAjaxPage(request, response, window, themeName, application); - - } else { - // Client downloads an resource - handleDownload(download, request, response); - } - - } catch (final SessionExpired e) { - // Session has expired, notify user - try { - if (!isOnUnloadRequest(request)) { - Application.SystemMessages ci = getSystemMessages(); - if (!UIDLrequest) { - // 'plain' http req - e.g. browser reload; - // just go ahead redirect the browser - response.sendRedirect(ci.getSessionExpiredURL()); - } else { - // send uidl redirect - criticalNotification(request, response, ci - .getSessionExpiredCaption(), ci - .getSessionExpiredMessage(), ci - .getSessionExpiredURL()); - // Invalidate session (weird to have session if we're - // saying - // that it's expired, and worse: portal integration will - // fail since the session is not created by the portal. - request.getSession().invalidate(); - } - } - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - - } catch (final GeneralSecurityException e) { - if (!isOnUnloadRequest(request)) { - // TODO handle differently? - // Invalid security key, show session expired message for now. - try { - Application.SystemMessages ci = getSystemMessages(); - if (!UIDLrequest) { - // 'plain' http req - e.g. browser reload; - // just go ahead redirect the browser - response.sendRedirect(ci.getSessionExpiredURL()); - } else { - // send uidl redirect - criticalNotification(request, response, ci - .getSessionExpiredCaption(), ci - .getSessionExpiredMessage(), ci - .getSessionExpiredURL()); - } - request.getSession().invalidate(); - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - } - } catch (final Throwable e) { - // if this was an UIDL request, response UIDL back to client - if (UIDLrequest) { - Application.SystemMessages ci = getSystemMessages(); - criticalNotification(request, response, ci - .getInternalErrorCaption(), ci - .getInternalErrorMessage(), ci.getInternalErrorURL()); - if (application != null) { - application.getErrorHandler().terminalError( - new RequestError(e)); - } else { - throw new ServletException(e); - } - } else { - // Re-throw other exceptions - throw new ServletException(e); - } - } finally { - // Notifies transaction end - if (application != null) { - ((WebApplicationContext) application.getContext()) - .endTransaction(application, request); - } - - // Work-around for GAE session problem. Explicitly touch session so - // it is re-serialized. - request.getSession().setAttribute("sessionUpdated", - new Date().getTime()); + // Gets the application class name + final String applicationClassName = servletConfig + .getInitParameter("application"); + if (applicationClassName == null) { + throw new ServletException( + "Application not specified in servlet parameters"); } - } - - private boolean isOnUnloadRequest(HttpServletRequest request) { - return request.getParameter(ApplicationConnection.PARAM_UNLOADBURST) != null; - } - /** Get system messages from the current application class */ - private SystemMessages getSystemMessages() { try { - Class appCls = applicationClass; - if (isApplicationRunnerServlet) { - appCls = getClass().getClassLoader().loadClass( - applicationRunnerClassname); - } - Method m = appCls.getMethod("getSystemMessages", (Class[]) null); - return (Application.SystemMessages) m.invoke(null, (Object[]) null); - } catch (ClassNotFoundException e) { - // This should never happen - throw new SystemMessageException(e); - } catch (SecurityException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (NoSuchMethodException e) { - // This is completely ok and should be silently ignored - } catch (IllegalArgumentException e) { - // This should never happen - throw new SystemMessageException(e); - } catch (IllegalAccessException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (InvocationTargetException e) { - // This should never happen - throw new SystemMessageException(e); - } - return Application.getSystemMessages(); - } - - /** - * Serve resources in ITMILL directory if requested. - * - * @param request - * @param response - * @throws IOException - * @throws ServletException - */ - private void serveStaticResourcesInITMILL(String filename, - HttpServletResponse response) throws IOException, ServletException { - - final ServletContext sc = getServletContext(); - InputStream is = sc.getResourceAsStream(filename); - if (is == null) { - // try if requested file is found from classloader - - // strip leading "/" otherwise stream from JAR wont work - filename = filename.substring(1); - is = getClassLoader().getResourceAsStream(filename); - - if (is == null) { - // cannot serve requested file - System.err - .println("Requested resource [" - + filename - + "] not found from filesystem or through class loader." - + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/ITMILL folder."); - response.setStatus(404); - return; - } - } - final String mimetype = sc.getMimeType(filename); - if (mimetype != null) { - response.setContentType(mimetype); - } - final OutputStream os = response.getOutputStream(); - final byte buffer[] = new byte[20000]; - int bytes; - while ((bytes = is.read(buffer)) >= 0) { - os.write(buffer, 0, bytes); - } - } - - /** - * Send notification to client's application. Used to notify client of - * critical errors and session expiration due to long inactivity. Server has - * no knowledge of what application client refers to. - * - * @param request - * the HTTP request instance. - * @param response - * the HTTP response to write to. - * @param caption - * for the notification - * @param message - * for the notification - * @param url - * url to load after message, null for current page - * @throws IOException - * if the writing failed due to input/output error. - */ - void criticalNotification(HttpServletRequest request, - HttpServletResponse response, String caption, String message, - String url) throws IOException { - - // clients JS app is still running, but server application either - // no longer exists or it might fail to perform reasonably. - // send a notification to client's application and link how - // to "restart" application. - - if (caption != null) { - caption = "\"" + caption + "\""; - } - if (message != null) { - message = "\"" + message + "\""; - } - if (url != null) { - url = "\"" + url + "\""; - } - - // Set the response type - response.setContentType("application/json; charset=UTF-8"); - final ServletOutputStream out = response.getOutputStream(); - final PrintWriter outWriter = new PrintWriter(new BufferedWriter( - new OutputStreamWriter(out, "UTF-8"))); - outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {" - + "\"appError\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}}, \"resources\": {}, \"locales\":[]}]"); - outWriter.flush(); - outWriter.close(); - out.flush(); - } - - /** - * Resolve application URL and widgetset URL. Widgetset is not application - * specific. - * - * @param request - * @return string array consisting of application url first and then - * widgetset url. - * @throws MalformedURLException - */ - private String[] getAppAndWidgetUrl(HttpServletRequest request) - throws MalformedURLException { - // don't use server and port in uri. It may cause problems with some - // virtual server configurations which lose the server name - String appUrl = null; - String widgetsetUrl = null; - if (isApplicationRunnerServlet) { - final String[] URIparts = getApplicationRunnerURIs(request); - widgetsetUrl = URIparts[0]; - if (widgetsetUrl.equals("/")) { - widgetsetUrl = ""; - } - appUrl = URIparts[1]; - } else { - String[] urlParts; - urlParts = getApplicationUrl(request).toString().split("\\/"); - appUrl = ""; - widgetsetUrl = ""; - // if context is specified add it to widgetsetUrl - String ctxPath = request.getContextPath(); - if (ctxPath.length() == 0 - && request - .getAttribute("javax.servlet.include.context_path") != null) { - // include request (e.g portlet), get contex path from - // attribute - ctxPath = (String) request - .getAttribute("javax.servlet.include.context_path"); - } - if (urlParts.length > 3 - && urlParts[3].equals(ctxPath.replaceAll("\\/", ""))) { - widgetsetUrl += "/" + urlParts[3]; - } - for (int i = 3; i < urlParts.length; i++) { - appUrl += "/" + urlParts[i]; - } - if (appUrl.endsWith("/")) { - appUrl = appUrl.substring(0, appUrl.length() - 1); - } - + applicationClass = (Class) getClassLoader() + .loadClass(applicationClassName); + } catch (final ClassNotFoundException e) { + throw new ServletException("Failed to load application class: " + + applicationClassName); } - return new String[] { appUrl, widgetsetUrl }; } - /** - * - * @param request - * the HTTP request. - * @param response - * the HTTP response to write to. - * @param out - * @param unhandledParameters - * @param window - * @param terminalType - * @param theme - * @throws IOException - * if the writing failed due to input/output error. - * @throws MalformedURLException - * if the application is denied access the persistent data store - * represented by the given URL. - */ - private void writeAjaxPage(HttpServletRequest request, - HttpServletResponse response, Window window, String themeName, - Application application) throws IOException, MalformedURLException, - ServletException { - - // e.g portlets only want a html fragment - boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null); - if (fragment) { - request.setAttribute(Application.class.getName(), application); - } - - final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( - response.getOutputStream())); - String pathInfo = request.getPathInfo() == null ? "/" : request - .getPathInfo(); - if (isApplicationRunnerServlet) { - pathInfo = pathInfo - .substring(applicationRunnerClassname.length() + 1); - } - String title = ((window == null || window.getCaption() == null) ? "IT Mill Toolkit 5" - : window.getCaption()); - - String widgetset = null; - // request widgetset takes precedence (e.g portlet include) - Object reqParam = request.getAttribute(REQUEST_WIDGETSET); - try { - widgetset = (String) reqParam; - } catch (Exception e) { - // FIXME: Handle exception - System.err.println("Warning: request param '" + REQUEST_WIDGETSET - + "' could not be used (is not a String)" + e); - } - if (widgetset == null) { - widgetset = applicationProperties.getProperty(PARAMETER_WIDGETSET); - } - if (widgetset == null) { - widgetset = DEFAULT_WIDGETSET; - } - final String[] urls = getAppAndWidgetUrl(request); - final String appUrl = urls[0]; - final String widgetsetUrl = urls[1]; - - final String staticFilePath = getApplicationOrSystemProperty( - PARAMETER_ITMILL_RESOURCES, widgetsetUrl); - - // Default theme does not use theme URI - String themeUri = null; - if (themeName != null) { - // Using custom theme - themeUri = staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName; - } - - if (!fragment) { - // Window renders are not cacheable - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - response.setContentType("text/html; charset=UTF-8"); - - // write html header - page.write("\n"); - - page.write("\n\n"); - page - .write("\n"); - page - .write("\n"); - page.write(""); - - // Add favicon links - page - .write(""); - page - .write(""); - - page.write("" + title + ""); - - page - .write("\n\n\n"); - } - - String appId = appUrl; - if ("".equals(appUrl)) { - appId = "ROOT"; - } - appId = appId.replaceAll("[^a-zA-Z0-9]", ""); - // Add hashCode to the end, so that it is still (sort of) predictable, - // but indicates that it should not be used in CSS and such: - int hashCode = appId.hashCode(); - if (hashCode < 0) { - hashCode = -hashCode; - } - appId = appId + "-" + hashCode; - - // Get system messages - Application.SystemMessages systemMessages = null; - try { - systemMessages = getSystemMessages(); - } catch (SystemMessageException e) { - // failing to get the system messages is always a problem - throw new ServletException("CommunicationError!", e); - } - - if (isGecko17(request)) { - // special start page for gecko 1.7 versions. Firefox 1.0 is not - // supported, but the hack is make it possible to use linux and - // hosted mode browser for debugging. Note that due this hack, - // debugging gwt code in portals with linux will be problematic if - // there are multiple toolkit portlets visible at the same time. - // TODO remove this when hosted mode on linux gets newer gecko - - page.write("\n"); - page.write("\n"); - page.write("\n"); - - if (themeName != null) { - // Custom theme's stylesheet, load only once, in different - // script - // tag to be dominate styles injected by widget - // set - page.write("\n"); - } - - } else { - page.write("\n"); - - if (themeName != null) { - // Custom theme's stylesheet, load only once, in different - // script - // tag to be dominate styles injected by widget - // set - page.write("\n"); - } - } - - // Warn if the widgetset has not been loaded after 15 seconds on - // inactivity - page.write("\n"); - - String style = null; - reqParam = request.getAttribute(REQUEST_APPSTYLE); - if (reqParam != null) { - style = "style=\"" + reqParam + "\""; - } - /*- Add classnames; - * .i-app - * .i-app-loading - * .i-app- - * .i-theme- - */ - // FIXME Can themeName be null? This possibility is taken into account - // in other places - page.write("
\n"); - - if (!fragment) { - page.write(""); - page.write("\n\n"); - } - page.close(); - - } - - /** - * Returns a message printed for browsers without scripting support or if - * browsers scripting support is disabled. - */ - protected String getNoScriptMessage() { - return "You have to enable javascript in your browser to use an application built with IT Mill Toolkit."; - } - - private boolean isGecko17(HttpServletRequest request) { - final WebBrowser browser = WebApplicationContext.getApplicationContext( - request.getSession()).getBrowser(); - if (browser != null && browser.getBrowserApplication() != null) { - if (browser.getBrowserApplication().indexOf("rv:1.7.") > 0 - && browser.getBrowserApplication().indexOf("Gecko") > 0) { - return true; - } - } - return false; - } - - /** - * 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 stream - * the download stream. - * - * @param request - * the HTTP request instance. - * @param response - * the HTTP response to write to. - * @throws IOException - * - * @see com.itmill.toolkit.terminal.URIHandler - */ - private void handleDownload(DownloadStream stream, - HttpServletRequest request, HttpServletResponse response) - throws IOException { - - if (stream.getParameter("Location") != null) { - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.addHeader("Location", stream.getParameter("Location")); - return; - } - - // Download from given stream - final InputStream data = stream.getStream(); - if (data != null) { - - // Sets content type - response.setContentType(stream.getContentType()); - - // Sets cache headers - final long cacheTime = stream.getCacheTime(); - if (cacheTime <= 0) { - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - } else { - response.setHeader("Cache-Control", "max-age=" + cacheTime - / 1000); - response.setDateHeader("Expires", System.currentTimeMillis() - + cacheTime); - response.setHeader("Pragma", "cache"); // Required to apply - // caching in some - // Tomcats - } - - // Copy download stream parameters directly - // to HTTP headers. - final Iterator i = stream.getParameterNames(); - if (i != null) { - while (i.hasNext()) { - final String param = (String) i.next(); - response.setHeader(param, stream.getParameter(param)); - } - } - - // suggest local filename from DownloadStream if Content-Disposition - // not explicitly set - String contentDispositionValue = stream - .getParameter("Content-Disposition"); - if (contentDispositionValue == null) { - contentDispositionValue = "filename=\"" + stream.getFileName() - + "\""; - response.setHeader("Content-Disposition", - contentDispositionValue); - } - - int bufferSize = stream.getBufferSize(); - if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) { - bufferSize = DEFAULT_BUFFER_SIZE; - } - final byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - - final OutputStream out = response.getOutputStream(); - - while ((bytesRead = data.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - out.flush(); - } - out.close(); - - } - - } - - /** - * Handles theme resource file requests. Resources supplied with the themes - * are provided by the WebAdapterServlet. - * - * @param request - * the HTTP request. - * @param response - * the HTTP response. - * @return boolean true if the request was handled and further - * processing should be suppressed, false otherwise. - * @throws ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - private boolean handleResourceRequest(HttpServletRequest request, - HttpServletResponse response, String themeName) + @Override + protected Application getNewApplication(HttpServletRequest request) throws ServletException { - // If the resource path is unassigned, initialize it - if (resourcePath == null) { - resourcePath = request.getContextPath() + request.getServletPath() - + RESOURCE_URI; - // WebSphere Application Server related fix - resourcePath = resourcePath.replaceAll("//", "/"); - } - - String resourceId = request.getPathInfo(); - - // Checks if this really is a resource request - if (resourceId == null || !resourceId.startsWith(RESOURCE_URI)) { - return false; - } - - // Checks the resource type - resourceId = resourceId.substring(RESOURCE_URI.length()); - InputStream data = null; - - // Gets theme resources + // Creates a new application instance try { - data = getServletContext().getResourceAsStream( - THEME_DIRECTORY_PATH + themeName + "/" + resourceId); - } catch (final Exception e) { - // FIXME: Handle exception - e.printStackTrace(); - data = null; - } - - // Writes the response - try { - if (data != null) { - response.setContentType(FileTypeResolver - .getMIMEType(resourceId)); - - // Use default cache time for theme resources - response.setHeader("Cache-Control", "max-age=" - + DEFAULT_THEME_CACHETIME / 1000); - response.setDateHeader("Expires", System.currentTimeMillis() - + DEFAULT_THEME_CACHETIME); - response.setHeader("Pragma", "cache"); // Required to apply - // caching in some - // Tomcats - - // Writes the data to client - final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - int bytesRead = 0; - final OutputStream out = response.getOutputStream(); - while ((bytesRead = data.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - } - out.close(); - data.close(); - } else { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } - - } catch (final java.io.IOException e) { - // FIXME: Handle exception - System.err.println("Resource transfer failed: " - + request.getRequestURI() + ". (" + e.getMessage() + ")"); - } - - return true; - } - - /** - * Gets the current application URL from request. - * - * @param request - * the HTTP request. - * @throws MalformedURLException - * if the application is denied access to the persistent data - * store represented by the given URL. - */ - private URL getApplicationUrl(HttpServletRequest request) - throws MalformedURLException { - - URL applicationUrl; - - final URL reqURL = new URL( - (request.isSecure() ? "https://" : "http://") - + request.getServerName() - + ((request.isSecure() && request.getServerPort() == 443) - || (!request.isSecure() && request - .getServerPort() == 80) ? "" : ":" - + request.getServerPort()) - + request.getRequestURI()); - String servletPath = ""; - if (request.getAttribute("javax.servlet.include.servlet_path") != null) { - // this is an include request - servletPath = request.getAttribute( - "javax.servlet.include.context_path").toString() - + request - .getAttribute("javax.servlet.include.servlet_path"); - - } else { - servletPath = request.getContextPath() + request.getServletPath(); - } - if (servletPath.length() == 0 - || servletPath.charAt(servletPath.length() - 1) != '/') { - servletPath = servletPath + "/"; - } - applicationUrl = new URL(reqURL, servletPath); - - return applicationUrl; - } - - /** - * Parses application runner URIs. - * - * If request URL is e.g. - * http://localhost:8080/itmill/run/com.itmill.toolkit.demo.Calc then - *
    - *
  • context=itmill
  • - *
  • Runner servlet=run
  • - *
  • Toolkit application=com.itmill.toolkit.demo.Calc
  • - *
- * - * @param request - * @return string array containing widgetset URI, application URI and - * context, runner, application classname - */ - private String[] getApplicationRunnerURIs(HttpServletRequest request) { - final String[] urlParts = request.getRequestURI().toString().split( - "\\/"); - String context = null; - String runner = null; - String applicationClassname = null; - if (urlParts[1].equals(request.getContextPath().replaceAll("\\/", ""))) { - // class name comes after web context and runner application - context = urlParts[1]; - runner = urlParts[2]; - applicationClassname = urlParts[3]; - return new String[] { "/" + context, - "/" + context + "/" + runner + "/" + applicationClassname, - context, runner, applicationClassname }; - } else { - // no context - context = ""; - runner = urlParts[1]; - applicationClassname = urlParts[2]; - return new String[] { "/", - "/" + runner + "/" + applicationClassname, context, runner, - applicationClassname }; - } - } - - /** - * Gets the existing application for given request. Looks for application - * instance for given request based on the requested URL. - * - * @param request - * the HTTP request. - * @param response - * @return Application instance, or null if the URL does not map to valid - * application. - * @throws MalformedURLException - * if the application is denied access to the persistent data - * store represented by the given URL. - * @throws SAXException - * @throws IllegalAccessException - * @throws InstantiationException - */ - private Application getExistingApplication(HttpServletRequest request, - HttpServletResponse response) throws MalformedURLException, - SAXException, IllegalAccessException, InstantiationException { - - // Ensures that the session is still valid - final HttpSession session = request.getSession(true); - - // Gets application list for the session. - final Collection applications = WebApplicationContext - .getApplicationContext(session).getApplications(); - - // Search for the application (using the application URI) from the list - for (final Iterator i = applications.iterator(); i.hasNext();) { - final Application a = (Application) i.next(); - final String aPath = a.getURL().getPath(); - String servletPath = ""; - if (isApplicationRunnerServlet) { - final String[] URIparts = getApplicationRunnerURIs(request); - servletPath = URIparts[1] + "/"; - } else { - servletPath = request.getContextPath() - + request.getServletPath(); - if (servletPath.length() < aPath.length()) { - servletPath += "/"; - } - } - if (servletPath.equals(aPath)) { - // Found a running application - if (a.isRunning()) { - return a; - } - // Application has stopped, so remove it before creating a new - // application - WebApplicationContext.getApplicationContext(session) - .removeApplication(a); - break; - } - } - - // Existing application not found - return null; - } - - /** - * Creates new application for given request. - * - * @param request - * the HTTP request. - * @param response - * @return Application instance, or null if the URL does not map to valid - * application. - * @throws MalformedURLException - * if the application is denied access to the persistent data - * store represented by the given URL. - * @throws SAXException - * @throws IllegalAccessException - * @throws InstantiationException - * @throws ServletException - */ - private Application getNewApplication(HttpServletRequest request, - HttpServletResponse response) throws MalformedURLException, - SAXException, IllegalAccessException, InstantiationException, - ServletException { - - // Create application - final WebApplicationContext context = WebApplicationContext - .getApplicationContext(request.getSession()); - final URL applicationUrl; - - if (isApplicationRunnerServlet) { - final String[] URIparts = getApplicationRunnerURIs(request); - final String applicationClassname = URIparts[4]; - applicationUrl = new URL(getApplicationUrl(request).toString() - + applicationClassname + "/"); - try { - applicationClass = getClassLoader().loadClass( - applicationClassname); - } catch (final ClassNotFoundException e) { - throw new InstantiationException( - "Failed to load application class: " - + applicationClassname); - } - } else { - applicationUrl = getApplicationUrl(request); - } - - // Creates new application and start it - try { - final Application application = (Application) applicationClass - .newInstance(); - context.addApplication(application); - - // Sets initial locale from the request - application.setLocale(request.getLocale()); - - // Starts application - application.start(applicationUrl, applicationProperties, context); + final Application application = getApplicationClass().newInstance(); return application; - } catch (final IllegalAccessException e) { - throw e; + throw new ServletException("getNewApplication failed", e); } catch (final InstantiationException e) { - throw e; - } - } - - /** - * Ends the application. - * - * @param request - * the HTTP request. - * @param response - * the HTTP response to write to. - * @param application - * the application to end. - * @throws IOException - * if the writing failed due to input/output error. - */ - private void endApplication(HttpServletRequest request, - HttpServletResponse response, Application application) - throws IOException { - - String logoutUrl = application.getLogoutURL(); - if (logoutUrl == null) { - logoutUrl = application.getURL().toString(); - } - - final HttpSession session = request.getSession(); - if (session != null) { - WebApplicationContext.getApplicationContext(session) - .removeApplication(application); - } - - response.sendRedirect(response.encodeRedirectURL(logoutUrl)); - } - - /** - * 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. - * @return Window matching the given URI or null if not found. - * @throws ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - private Window getApplicationWindow(HttpServletRequest request, - Application application) throws ServletException { - - Window window = null; - - // Finds the window where the request is handled - String path = request.getPathInfo(); - if (isApplicationRunnerServlet) { - path = path.substring(1 + applicationRunnerClassname.length()); - } - - // Main window as the URI is empty - if (path == null || path.length() == 0 || path.equals("/") - || path.startsWith("/APP/")) { - window = application.getMainWindow(); - } else { - String windowName = null; - if (path.charAt(0) == '/') { - path = path.substring(1); - } - final int index = path.indexOf('/'); - if (index < 0) { - windowName = path; - path = ""; - } else { - windowName = path.substring(0, index); - path = path.substring(index + 1); - } - window = application.getWindow(windowName); - - if (window == null) { - // By default, we use main window - window = application.getMainWindow(); - } else if (!window.isVisible()) { - // Implicitly painting without actually invoking paint() - window.requestRepaintRequests(); - - // If the window is invisible send a blank page - return null; - } - } - - return window; - } - - /** - * Gets relative location of a theme resource. - * - * @param theme - * the Theme name. - * @param resource - * the Theme resource. - * @return External URI specifying the resource - */ - public String getResourceLocation(String theme, ThemeResource resource) { - - if (resourcePath == null) { - return resource.getResourceId(); + throw new ServletException("getNewApplication failed", e); } - return resourcePath + theme + "/" + resource.getResourceId(); } - /** - * Implementation of ParameterHandler.ErrorEvent interface. - */ - public class ParameterHandlerErrorImpl implements - ParameterHandler.ErrorEvent, Serializable { - - private ParameterHandler owner; - - private Throwable throwable; - - /** - * Gets the contained throwable. - * - * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable() - */ - public Throwable getThrowable() { - return throwable; - } - - /** - * Gets the source ParameterHandler. - * - * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler() - */ - public ParameterHandler getParameterHandler() { - return owner; - } - - } - - /** - * 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; - } - - /** - * Gets the contained throwable. - * - * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable() - */ - public Throwable getThrowable() { - return throwable; - } - - /** - * Gets the source URIHandler. - * - * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler() - */ - public URIHandler getURIHandler() { - return owner; - } - } - - /** - * Gets resource path using different implementations. Required to - * supporting different servlet container implementations (application - * servers). - * - * @param servletContext - * @param path - * the resource path. - * @return the resource path. - */ - protected static String getResourcePath(ServletContext servletContext, - String path) { - String resultPath = null; - resultPath = servletContext.getRealPath(path); - if (resultPath != null) { - return resultPath; - } else { - try { - final URL url = servletContext.getResource(path); - resultPath = url.getFile(); - } catch (final Exception e) { - // FIXME: Handle exception - e.printStackTrace(); - } - } - return resultPath; - } - - public class RequestError implements Terminal.ErrorEvent, Serializable { - - private final Throwable throwable; - - public RequestError(Throwable throwable) { - this.throwable = throwable; - } - - public Throwable getThrowable() { - return throwable; - } - - } - - /** - * Returns true if the servlet is running in production mode. Production - * mode disables all debug facilities. - * - * @return true if in production mode, false if in debug mode - */ - public boolean isProductionMode() { - return productionMode; + @Override + protected Class getApplicationClass() { + return applicationClass; } - } \ No newline at end of file diff --git a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java index 033284259d..ced5ba1198 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java @@ -92,13 +92,13 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c"; - private final HashSet currentlyOpenWindowsInClient = new HashSet(); + private final HashSet currentlyOpenWindowsInClient = new HashSet(); 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 ArrayList dirtyPaintabletSet = new ArrayList(); private final HashMap paintableIdMap = new HashMap(); @@ -106,7 +106,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, private int idSequence = 0; - private final ApplicationServlet applicationServlet; + private final AbstractApplicationServlet applicationServlet; private final Application application; @@ -114,14 +114,14 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, // thus should be thread-safe. private String closingWindowName = null; - private List locales; + private List locales; private int pendingLocalesIndex; private int timeoutInterval = -1; public CommunicationManager(Application application, - ApplicationServlet applicationServlet) { + AbstractApplicationServlet applicationServlet) { this.application = application; requireLocale(application.getLocale().toString()); this.applicationServlet = applicationServlet; @@ -228,9 +228,9 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, * @throws ServletException */ public void handleUidlRequest(HttpServletRequest request, - HttpServletResponse response, ApplicationServlet applicationServlet) - throws IOException, ServletException, - InvalidUIDLSecurityKeyException { + HttpServletResponse response, + AbstractApplicationServlet applicationServlet) throws IOException, + ServletException, InvalidUIDLSecurityKeyException { // repaint requested or session has timed out and new one is created boolean repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null) @@ -317,7 +317,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, private void paintAfterVariablechanges(HttpServletRequest request, HttpServletResponse response, - ApplicationServlet applicationServlet, boolean repaintAll, + AbstractApplicationServlet applicationServlet, boolean repaintAll, final PrintWriter outWriter, Window window, boolean analyzeLayouts) throws IOException, ServletException, PaintException { @@ -394,8 +394,8 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, // 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(Object o1, Object o2) { + Collections.sort(paintables, new Comparator() { + public int compare(Paintable o1, Paintable o2) { Component c1 = (Component) o1; Component c2 = (Component) o2; int d1 = 0; @@ -885,8 +885,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, outWriter.print(", \"locales\":["); for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) { - final Locale l = generateLocale((String) locales - .get(pendingLocalesIndex)); + final Locale l = generateLocale(locales.get(pendingLocalesIndex)); // Locale name outWriter.print("{\"name\":\"" + l.toString() + "\","); @@ -1034,12 +1033,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, if (window == null) { // Get the path from URL - String path = request.getPathInfo(); - if (applicationServlet.isApplicationRunnerServlet) { - path = path - .substring(1 + applicationServlet.applicationRunnerClassname - .length()); - } + String path = applicationServlet.getRequestPathInfo(request); path = path.substring("/UIDL".length()); // If the path is specified, create name from it @@ -1175,8 +1169,9 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, * root window for which dirty components is to be fetched * @return */ - private ArrayList getDirtyVisibleComponents(Window w) { - final ArrayList resultset = new ArrayList(dirtyPaintabletSet); + 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 @@ -1363,7 +1358,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, public void requireLocale(String value) { if (locales == null) { - locales = new ArrayList(); + locales = new ArrayList(); locales.add(application.getLocale().toString()); pendingLocalesIndex = 0; } @@ -1452,7 +1447,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, DownloadStream handleURI(Window window, HttpServletRequest request, HttpServletResponse response) { - String uri = request.getPathInfo(); + String uri = applicationServlet.getRequestPathInfo(request); // If no URI is available if (uri == null) { @@ -1464,18 +1459,6 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, } } - // If using application runner, remove package and class name - if (applicationServlet.isApplicationRunnerServlet) { - if (uri.contains("/")) { - uri = uri - .replaceFirst( - applicationServlet.applicationRunnerClassname - + "/", ""); - } else { - uri = ""; - } - } - // Handles the uri try { URL context = application.getURL(); diff --git a/src/com/itmill/toolkit/terminal/gwt/server/WebApplicationContext.java b/src/com/itmill/toolkit/terminal/gwt/server/WebApplicationContext.java index 0193b4d3d7..4f2ca1817f 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/WebApplicationContext.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/WebApplicationContext.java @@ -36,11 +36,11 @@ import com.itmill.toolkit.service.ApplicationContext; public class WebApplicationContext implements ApplicationContext, HttpSessionBindingListener, Serializable { - protected List listeners; + protected List listeners; protected transient HttpSession session; - protected final HashSet applications = new HashSet(); + protected final HashSet applications = new HashSet(); protected WebBrowser browser = new WebBrowser(); @@ -131,7 +131,7 @@ public class WebApplicationContext implements ApplicationContext, */ public void addTransactionListener(TransactionListener listener) { if (listeners == null) { - listeners = new LinkedList(); + listeners = new LinkedList(); } listeners.add(listener); } @@ -179,14 +179,14 @@ public class WebApplicationContext implements ApplicationContext, return; } - LinkedList exceptions = null; + LinkedList exceptions = null; for (final Iterator i = listeners.iterator(); i.hasNext();) { try { ((ApplicationContext.TransactionListener) i.next()) .transactionEnd(application, request); } catch (final RuntimeException t) { if (exceptions == null) { - exceptions = new LinkedList(); + exceptions = new LinkedList(); } exceptions.add(t); } @@ -232,8 +232,7 @@ public class WebApplicationContext implements ApplicationContext, // closing try { while (!applications.isEmpty()) { - final Application app = (Application) applications.iterator() - .next(); + final Application app = applications.iterator().next(); app.close(); applicationToAjaxAppMgrMap.remove(app); removeApplication(app); @@ -266,13 +265,14 @@ public class WebApplicationContext implements ApplicationContext, /** * Gets communication manager for an application. * - * If this application has not been running before, new manager is created. + * If this application has not been running before, a new manager is + * created. * * @param application * @return CommunicationManager */ protected CommunicationManager getApplicationManager( - Application application, ApplicationServlet servlet) { + Application application, AbstractApplicationServlet servlet) { CommunicationManager mgr = applicationToAjaxAppMgrMap.get(application); if (mgr == null) { -- 2.39.5