aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java')
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java1423
1 files changed, 1423 insertions, 0 deletions
diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java
new file mode 100644
index 0000000000..62c561b480
--- /dev/null
+++ b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java
@@ -0,0 +1,1423 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Interfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license.pdf. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+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.net.MalformedURLException;
+import java.net.URL;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.xml.sax.SAXException;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.service.FileTypeResolver;
+import com.itmill.toolkit.service.License;
+import com.itmill.toolkit.service.License.InvalidLicenseFile;
+import com.itmill.toolkit.service.License.LicenseFileHasNotBeenRead;
+import com.itmill.toolkit.service.License.LicenseSignatureIsInvalid;
+import com.itmill.toolkit.service.License.LicenseViolation;
+import com.itmill.toolkit.terminal.DownloadStream;
+import com.itmill.toolkit.terminal.Paintable;
+import com.itmill.toolkit.terminal.ParameterHandler;
+import com.itmill.toolkit.terminal.ThemeResource;
+import com.itmill.toolkit.terminal.URIHandler;
+import com.itmill.toolkit.ui.Window;
+
+/**
+ * This servlet connects IT Mill Toolkit Application to Web. This servlet
+ * replaces both WebAdapterServlet and AjaxAdapterServlet.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 4.0
+ */
+
+public class ApplicationServlet extends HttpServlet {
+
+ private static final long serialVersionUID = -4937882979845826574L;
+
+ /**
+ * Version number of this release. For example "4.0.0".
+ */
+ public static final String VERSION;
+
+ /**
+ * Major version number. For example 4 in 4.1.0.
+ */
+ public static final int VERSION_MAJOR;
+
+ /**
+ * Minor version number. For example 1 in 4.1.0.
+ */
+ public static final int VERSION_MINOR;
+
+ /**
+ * Builds number. For example 0-beta1 in 4.0.0-beta1.
+ */
+ public static final String VERSION_BUILD;
+
+ /* Initialize version numbers from string replaced by build-script. */
+ static {
+ if ("@VERSION@".equals("@" + "VERSION" + "@"))
+ VERSION = "4.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD";
+ else
+ VERSION = "@VERSION@";
+ String[] digits = VERSION.split("\\.");
+ VERSION_MAJOR = Integer.parseInt(digits[0]);
+ VERSION_MINOR = Integer.parseInt(digits[1]);
+ VERSION_BUILD = digits[2];
+ }
+
+ // Configurable parameter names
+ private static final String PARAMETER_DEBUG = "Debug";
+
+ private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
+
+ private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+ // TODO: these should be moved to session object and stored directly into
+ // session
+ private static final String SESSION_ATTR_VARMAP = "itmill-toolkit-varmap";
+
+ private static final String SESSION_ATTR_CONTEXT = "itmill-toolkit-context";
+
+ protected static final String SESSION_ATTR_APPS = "itmill-toolkit-apps";
+
+ private static final String SESSION_BINDING_LISTENER = "itmill-toolkit-bindinglistener";
+
+ private static HashMap applicationToLastRequestDate = new HashMap();
+
+ private static HashMap applicationToAjaxAppMgrMap = new HashMap();
+
+ // License for ApplicationServlets
+ private static HashMap licenseForApplicationClass = new HashMap();
+
+ private static HashMap licensePrintedForApplicationClass = new HashMap();
+
+ // TODO Should default or base theme be the default?
+ protected static final String DEFAULT_THEME = "base";
+
+ private static final String RESOURCE_URI = "/RES/";
+
+ private static final String AJAX_UIDL_URI = "/UIDL/";
+
+ static final String THEME_DIRECTORY_PATH = "/theme/";
+
+ // Maximum delay between request for an user to be considered active (in ms)
+ private static final long ACTIVE_USER_REQUEST_INTERVAL = 1000 * 45;
+
+ private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
+ // Private fields
+ private Class applicationClass;
+
+ private Properties applicationProperties;
+
+ private String resourcePath = null;
+
+ private String debugMode = "";
+
+ /**
+ * 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.
+ */
+ public void init(javax.servlet.ServletConfig servletConfig)
+ throws javax.servlet.ServletException {
+ super.init(servletConfig);
+
+ // Gets the application class name
+ String applicationClassName = servletConfig
+ .getInitParameter("application");
+ if (applicationClassName == null) {
+ Log.error("Application not specified in servlet parameters");
+ }
+
+ // Stores the application parameters into Properties object
+ this.applicationProperties = new Properties();
+ for (Enumeration e = servletConfig.getInitParameterNames(); e
+ .hasMoreElements();) {
+ String name = (String) e.nextElement();
+ this.applicationProperties.setProperty(name, servletConfig
+ .getInitParameter(name));
+ }
+
+ // Overrides with server.xml parameters
+ ServletContext context = servletConfig.getServletContext();
+ for (Enumeration e = context.getInitParameterNames(); e
+ .hasMoreElements();) {
+ String name = (String) e.nextElement();
+ this.applicationProperties.setProperty(name, context
+ .getInitParameter(name));
+ }
+
+ // Gets the debug window parameter
+ String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG, "")
+ .toLowerCase();
+
+ // Enables application specific debug
+ if (!"".equals(debug) && !"true".equals(debug)
+ && !"false".equals(debug))
+ throw new ServletException(
+ "If debug parameter is given for an application, it must be 'true' or 'false'");
+ this.debugMode = debug;
+
+ // Loads the application class using the same class loader
+ // as the servlet itself
+ ClassLoader loader = this.getClass().getClassLoader();
+ try {
+ this.applicationClass = loader.loadClass(applicationClassName);
+ } catch (ClassNotFoundException e) {
+ throw new ServletException("Failed to load application class: "
+ + applicationClassName);
+ }
+ }
+
+ /**
+ * 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 = this.applicationProperties.getProperty(parameterName);
+ if (val != null) {
+ return val;
+ }
+
+ // Try lowercased application properties for backward compability with
+ // 3.0.2 and earlier
+ val = this.applicationProperties.getProperty(parameterName
+ .toLowerCase());
+ if (val != null) {
+ return val;
+ }
+
+ // Try system properties
+ String pkgName;
+ Package pkg = this.getClass().getPackage();
+ if (pkg != null) {
+ pkgName = pkg.getName();
+ } else {
+ String className = this.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.
+ */
+ protected void service(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+
+ // Transformer and output stream for the result
+ HttpVariableMap variableMap = null;
+ OutputStream out = response.getOutputStream();
+ Application application = null;
+ try {
+
+ // Gets the application
+ application = getApplication(request);
+
+ // Creates application if it doesn't exist
+ if (application == null)
+ application = createApplication(request);
+
+ // Sets the last application request date
+ synchronized (applicationToLastRequestDate) {
+ applicationToLastRequestDate.put(application, new Date());
+ }
+
+ // Invokes context transaction listeners
+ ((WebApplicationContext) application.getContext())
+ .startTransaction(application, request);
+
+ // Is this a download request from application
+ DownloadStream download = null;
+
+ // The rest of the process is synchronized with the application
+ // in order to guarantee that no parallel variable handling is
+ // made
+ synchronized (application) {
+
+ // Handles AJAX UIDL requests
+ String resourceId = request.getPathInfo();
+ if (resourceId != null && resourceId.startsWith(AJAX_UIDL_URI)) {
+ getApplicationManager(application).handleUidlRequest(
+ request, response);
+ return;
+ }
+
+ // Gets the variable map
+ variableMap = getVariableMap(application, request);
+ if (variableMap == null)
+ return;
+
+ // Change all variables based on request parameters
+ Map unhandledParameters = variableMap.handleVariables(request,
+ application);
+
+ // Check/handle client side feature checks
+ WebBrowserProbe
+ .handleProbeRequest(request, unhandledParameters);
+
+ // If rendering mode is not defined or detecting requested
+ // try to detect it
+ WebBrowser wb = WebBrowserProbe.getTerminalType(request
+ .getSession());
+
+ boolean detect = false;
+ if (unhandledParameters.get("renderingMode") != null) {
+ detect = ((String) ((Object[]) unhandledParameters
+ .get("renderingMode"))[0]).equals("detect");
+ }
+ if (detect) {
+ String themeName = application.getTheme();
+ if (themeName == null)
+ themeName = DEFAULT_THEME;
+ if (unhandledParameters.get("theme") != null) {
+ themeName = (String) ((Object[]) unhandledParameters
+ .get("theme"))[0];
+ }
+ }
+
+ // Handles the URI if the application is still running
+ if (application.isRunning())
+ download = handleURI(application, request, response);
+
+ // If this is not a download request
+ if (download == null) {
+
+ // Window renders are not cacheable
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+
+ // Finds the window within the application
+ Window window = null;
+ if (application.isRunning())
+ window = getApplicationWindow(request, application,
+ unhandledParameters);
+
+ // Handles the unhandled parameters if the application is
+ // still running
+ if (window != null && unhandledParameters != null
+ && !unhandledParameters.isEmpty()) {
+ try {
+ window.handleParameters(unhandledParameters);
+ } catch (Throwable t) {
+ application
+ .terminalError(new ParameterHandlerErrorImpl(
+ window, t));
+ }
+ }
+
+ // Removes application if it has stopped
+ if (!application.isRunning()) {
+ endApplication(request, response, application);
+ return;
+ }
+
+ // Returns blank page, if no window found
+ if (window == null) {
+ response.setContentType("text/html");
+ BufferedWriter page = new BufferedWriter(
+ new OutputStreamWriter(out));
+ page.write("<html><head><script>");
+ // WAS GENERATE WINDOW SCRIPT
+ page.write("</script></head><body>");
+ page
+ .write("The requested window has been removed from application.");
+ page.write("</body></html>");
+ page.close();
+
+ return;
+ }
+
+ // Sets terminal type for the window, if not already set
+ if (window.getTerminal() == null) {
+ window.setTerminal(wb);
+ }
+
+ // Finds theme
+ String themeName = window.getTheme() != null ? window
+ .getTheme() : DEFAULT_THEME;
+ if (unhandledParameters.get("theme") != null) {
+ themeName = (String) ((Object[]) unhandledParameters
+ .get("theme"))[0];
+ }
+
+ // Handles resource requests
+ if (handleResourceRequest(request, response, themeName))
+ return;
+
+
+ writeAjaxPage(request, response, out,
+ unhandledParameters, window, wb, themeName);
+ }
+ }
+
+ // For normal requests, transform the window
+ if (download != null)
+
+ handleDownload(download, request, response);
+
+
+
+ } catch (Throwable e) {
+ // Print stacktrace
+ e.printStackTrace();
+ // Re-throw other exceptions
+ throw new ServletException(e);
+ } finally {
+
+ // Notifies transaction end
+ if (application != null)
+ ((WebApplicationContext) application.getContext())
+ .endTransaction(application, request);
+ }
+ }
+
+ /**
+ *
+ * @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, OutputStream out,
+ Map unhandledParameters, Window window, WebBrowser terminalType, String themeName) throws IOException, MalformedURLException {
+ response.setContentType("text/html");
+ BufferedWriter page = new BufferedWriter(new OutputStreamWriter(out));
+
+ page
+ .write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
+ + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+
+
+
+ page.write("<html>\n<head>\n<title>IT Mill Toolkit 5</title>\n" +
+ "<meta name='gwt:module' content='../com.itmill.toolkit.terminal.gwt.Client=com.itmill.toolkit.terminal.gwt.Client'>\n" +
+ "<script type=\"text/javascript\">\n" +
+ " var itmtk = {\n" +
+ " appUri:'");
+
+ String[] urlParts = getApplicationUrl(request).toString().split("\\/");
+ String appUrl = "";
+ // don't use server and port in uri. It may cause problems with some
+ // virtual server configurations which lose the server name
+ for (int i = 3; i < urlParts.length; i++)
+ appUrl += "/" + urlParts[i];
+ if (appUrl.endsWith("/"))
+ appUrl = appUrl.substring(0, appUrl.length() - 1);
+
+ page.write(appUrl);
+
+ page.write("'\n};\n" +
+ "</script>\n" +
+ "<link REL=\"stylesheet\" TYPE=\"text/css\" HREF=\""+request.getContextPath() + THEME_DIRECTORY_PATH+themeName+"/style.css\">" +
+ "</head>\n<body>\n<script language=\"javascript\" src=\"/tk/com.itmill.toolkit.terminal.gwt.Client/gwt.js\"></script>\n" +
+ " <iframe id=\"__gwt_historyFrame\" style=\"width:0;height:0;border:0\"></iframe>\n" +
+ " <div id=\"itmtk-ajax-window\"></div>" +
+ " <div id=\"itmtk-loki\" style=\"width: 100%; position: absolute; left: 0px; bottom: 0; height: 0px; border-top: 1px solid gray; background-color: #f6f6f6; overflow: scroll; font-size: x-small;color:red !important;\"" +
+ "></div>\n" +
+ "<div id='itm-loki-exp' style='right: 0; bottom: 0px; position: absolute; padding-left: 5px; padding-right: 5px; border-left: 1px solid gray; border-top: 1px solid gray; background-color: #f6f6f6;' onclick='itm_loki_exp()'>console</div><script language='JavaScript'>itm_loki_exp = function() {var l=document.getElementById('itmtk-loki'); var e=document.getElementById('itm-loki-exp'); if (e.style.bottom=='400px') {e.style.bottom='0px'; l.style.height='0px'; e.innerHTML='console';} else {e.style.bottom='400px'; l.style.height='400px'; e.innerHTML='-';}}</script>"+
+ " <div style=\"position: absolute; right: 5px; top: 5px; color: gray;\"><strong>IT Mill Toolkit 5 Prototype</strong></div>\n" +
+ " </body>\n" +
+ "</html>\n");
+
+
+
+// Theme t = theme;
+// Vector themes = new Vector();
+// themes.add(t);
+// while (t.getParent() != null) {
+// String parentName = t.getParent();
+// t = themeSource.getThemeByName(parentName);
+// themes.add(t);
+// }
+// for (int k = themes.size() - 1; k >= 0; k--) {
+// t = (Theme) themes.get(k);
+// Collection files = t.getFileNames(terminalType, Theme.MODE_AJAX);
+// for (Iterator i = files.iterator(); i.hasNext();) {
+// String file = (String) i.next();
+// if (file.endsWith(".css"))
+// page.write("<link rel=\"stylesheet\" href=\""
+// + getResourceLocation(t.getName(),
+// new ThemeResource(file))
+// + "\" type=\"text/css\" />\n");
+// else if (file.endsWith(".js")) {
+// page.write("<script src=\"");
+//
+// // TODO remove this and implement behaviour in themes
+// // description.xml files
+// if (file.endsWith("firebug.js")
+// && !isDebugMode(unhandledParameters)) {
+// file = file.replaceFirst("bug.js", "bugx.js");
+// }
+// page.write(getResourceLocation(t.getName(),
+// new ThemeResource(file)));
+// page.write("\" type=\"text/javascript\"></script>\n");
+// }
+// }
+//
+// }
+
+
+// page.write("itmill.tmp = new itmill.Client("
+// + "document.getElementById('ajax-window')," + "\"" + appUrl
+// + "/UIDL/" + "\",\"" + resourcePath
+// + ((Theme) themes.get(themes.size() - 1)).getName() + "/"
+//
+// + "client/\",document.getElementById('ajax-wait'));\n");
+
+ // TODO Only current theme is registered to the client
+ // for (int k = themes.size() - 1; k >= 0; k--) {
+ // t = (Theme) themes.get(k);
+// t = theme;
+// String themeObjName = "itmill.themes."
+// + t.getName().substring(0, 1).toUpperCase()
+// + t.getName().substring(1);
+// page.write(" (new " + themeObjName + "(\"" + resourcePath + t.getName()
+// + "/\")).registerTo(itmill.tmp);\n");
+ // }
+
+ page.close();
+ }
+
+ /**
+ * Handles the requested URI. An application can add handlers to do special
+ * processing, when a certain URI is requested. The handlers are invoked
+ * before any windows URIs are processed and if a DownloadStream is returned
+ * it is sent to the client.
+ *
+ * @param application
+ * the Application owning the URI.
+ * @param request
+ * the HTTP request instance.
+ * @param response
+ * the HTTP response to write to.
+ * @return boolean <code>true</code> if the request was handled and
+ * further processing should be suppressed, <code>false</code>
+ * otherwise.
+ * @see com.itmill.toolkit.terminal.URIHandler
+ */
+ private DownloadStream handleURI(Application application,
+ HttpServletRequest request, HttpServletResponse response) {
+
+ String uri = request.getPathInfo();
+
+ // If no URI is available
+ if (uri == null || uri.length() == 0 || uri.equals("/"))
+ return null;
+
+ // Removes the leading /
+ while (uri.startsWith("/") && uri.length() > 0)
+ uri = uri.substring(1);
+
+ // Handles the uri
+ DownloadStream stream = null;
+ try {
+ stream = application.handleURI(application.getURL(), uri);
+ } catch (Throwable t) {
+ application.terminalError(new URIHandlerErrorImpl(application, t));
+ }
+
+ return stream;
+ }
+
+ /**
+ * 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.
+ *
+ * @see com.itmill.toolkit.terminal.URIHandler
+ */
+ private void handleDownload(DownloadStream stream,
+ HttpServletRequest request, HttpServletResponse response) {
+
+ // Download from given stream
+ InputStream data = stream.getStream();
+ if (data != null) {
+
+ // Sets content type
+ response.setContentType(stream.getContentType());
+
+ // Sets cache headers
+ 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.
+ Iterator i = stream.getParameterNames();
+ if (i != null) {
+ while (i.hasNext()) {
+ String param = (String) i.next();
+ response.setHeader((String) param, stream
+ .getParameter(param));
+ }
+ }
+
+ int bufferSize = stream.getBufferSize();
+ if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE)
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead = 0;
+
+ try {
+ OutputStream out = response.getOutputStream();
+
+ while ((bytesRead = data.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ out.flush();
+ }
+ out.close();
+ } catch (IOException ignored) {
+ }
+
+ }
+
+ }
+
+ /**
+ * 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 <code>true</code> if the request was handled and
+ * further processing should be suppressed, <code>false</code>
+ * 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 (Exception e) {
+ Log.info(e.getMessage());
+ 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
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead = 0;
+ 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 (java.io.IOException e) {
+ Log.info("Resource transfer failed: " + request.getRequestURI()
+ + ". (" + e.getMessage() + ")");
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the variable map for the session.
+ *
+ * @param application
+ * @param request
+ * the HTTP request.
+ * @return the variable map.
+ *
+ */
+ private static synchronized HttpVariableMap getVariableMap(
+ Application application, HttpServletRequest request) {
+
+ HttpSession session = request.getSession();
+
+ // Gets the application to variablemap map
+ Map varMapMap = (Map) session.getAttribute(SESSION_ATTR_VARMAP);
+ if (varMapMap == null) {
+ varMapMap = new WeakHashMap();
+ session.setAttribute(SESSION_ATTR_VARMAP, varMapMap);
+ }
+
+ // Creates a variable map, if it does not exists.
+ HttpVariableMap variableMap = (HttpVariableMap) varMapMap
+ .get(application);
+ if (variableMap == null) {
+ variableMap = new HttpVariableMap();
+ varMapMap.put(application, variableMap);
+ }
+
+ return variableMap;
+ }
+
+ /**
+ * 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;
+ try {
+ 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 = request.getContextPath()
+ + request.getServletPath();
+ if (servletPath.length() == 0
+ || servletPath.charAt(servletPath.length() - 1) != '/')
+ servletPath = servletPath + "/";
+ applicationUrl = new URL(reqURL, servletPath);
+ } catch (MalformedURLException e) {
+ Log.error("Error constructing application url "
+ + request.getRequestURI() + " (" + e + ")");
+ throw e;
+ }
+
+ return applicationUrl;
+ }
+
+ /**
+ * 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.
+ */
+ private Application getApplication(HttpServletRequest request)
+ throws MalformedURLException {
+
+ // Ensures that the session is still valid
+ HttpSession session = request.getSession(false);
+ if (session == null)
+ return null;
+
+ // Gets application list for the session.
+ LinkedList applications = (LinkedList) session
+ .getAttribute(SESSION_ATTR_APPS);
+ if (applications == null)
+ return null;
+
+ // Search for the application (using the application URI) from the list
+ Application application = null;
+ for (Iterator i = applications.iterator(); i.hasNext()
+ && application == null;) {
+ Application a = (Application) i.next();
+ String aPath = a.getURL().getPath();
+ String servletPath = request.getContextPath()
+ + request.getServletPath();
+ if (servletPath.length() < aPath.length())
+ servletPath += "/";
+ if (servletPath.equals(aPath))
+ application = a;
+ }
+
+ // Removes stopped applications from the list
+ if (application != null && !application.isRunning()) {
+ applications.remove(application);
+ application = null;
+ }
+
+ return application;
+ }
+
+ /**
+ * Creates a new application.
+ *
+ * @param request
+ * the HTTP request.
+ * @return the New application instance.
+ * @throws MalformedURLException
+ * if the application is denied access to the persistent data
+ * store represented by the given URL.
+ * @throws InstantiationException
+ * if a new instance of the class cannot be instantiated.
+ * @throws IllegalAccessException
+ * if it does not have access to the property accessor method.
+ * @throws LicenseFileHasNotBeenRead
+ * if the license file has not been read.
+ * @throws LicenseSignatureIsInvalid
+ * if the license file has been changed or signature is
+ * otherwise invalid.
+ * @throws InvalidLicenseFile
+ * if the license file is not of correct XML format.
+ * @throws LicenseViolation
+ *
+ * @throws SAXException
+ * the Error parsing the license file.
+ */
+ private Application createApplication(HttpServletRequest request)
+ throws MalformedURLException, InstantiationException,
+ IllegalAccessException, LicenseFileHasNotBeenRead,
+ LicenseSignatureIsInvalid, InvalidLicenseFile, LicenseViolation,
+ SAXException {
+
+ Application application = null;
+
+ // Gets the application url
+ URL applicationUrl = getApplicationUrl(request);
+
+ // Gets application list.
+ HttpSession session = request.getSession();
+ if (session == null)
+ return null;
+ LinkedList applications = (LinkedList) session
+ .getAttribute(SESSION_ATTR_APPS);
+ if (applications == null) {
+ applications = new LinkedList();
+ session.setAttribute(SESSION_ATTR_APPS, applications);
+ HttpSessionBindingListener sessionBindingListener = new SessionBindingListener(
+ applications);
+ session.setAttribute(SESSION_BINDING_LISTENER,
+ sessionBindingListener);
+ }
+
+ // Creates new application and start it
+ try {
+ application = (Application) this.applicationClass.newInstance();
+ applications.add(application);
+
+ // Sets locale
+ application.setLocale(request.getLocale());
+
+ // Gets application context for this session
+ WebApplicationContext context = (WebApplicationContext) session
+ .getAttribute(SESSION_ATTR_CONTEXT);
+ if (context == null) {
+ context = new WebApplicationContext(session);
+ session.setAttribute(SESSION_ATTR_CONTEXT, context);
+ }
+
+ // Starts application and check license
+ initializeLicense(application);
+ application.start(applicationUrl, this.applicationProperties,
+ context);
+ checkLicense(application);
+
+ } catch (IllegalAccessException e) {
+ Log.error("Illegal access to application class "
+ + this.applicationClass.getName());
+ throw e;
+ } catch (InstantiationException e) {
+ Log.error("Failed to instantiate application class: "
+ + this.applicationClass.getName());
+ throw e;
+ }
+
+ return application;
+ }
+
+ /**
+ *
+ * @param application
+ */
+ private void initializeLicense(Application application) {
+ License license;
+ synchronized (licenseForApplicationClass) {
+ license = (License) licenseForApplicationClass.get(application
+ .getClass());
+ if (license == null) {
+ license = new License();
+ licenseForApplicationClass.put(application.getClass(), license);
+ }
+ }
+ application.setToolkitLicense(license);
+ }
+
+ /**
+ *
+ * @param application
+ * @throws LicenseFileHasNotBeenRead
+ * if the license file has not been read.
+ * @throws LicenseSignatureIsInvalid
+ * if the license file has been changed or signature is
+ * otherwise invalid.
+ * @throws InvalidLicenseFile
+ * if the license file is not of correct XML format.
+ * @throws LicenseViolation
+ *
+ * @throws SAXException
+ * the Error parsing the license file.
+ */
+ private void checkLicense(Application application)
+ throws LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid,
+ InvalidLicenseFile, LicenseViolation, SAXException {
+ License license = application.getToolkitLicense();
+
+ if (!license.hasBeenRead())
+ // Lock threads that have not yet read license
+ synchronized (license) {
+ if (!license.hasBeenRead()) {
+ InputStream lis;
+ try {
+ URL url = getServletContext().getResource(
+ "/WEB-INF/itmill-toolkit-license.xml");
+ if (url == null) {
+ throw new RuntimeException(
+ "License file could not be read. "
+ + "You can install it to "
+ + "WEB-INF/itmill-toolkit-license.xml.");
+ }
+ lis = url.openStream();
+ license.readLicenseFile(lis);
+ } catch (MalformedURLException e) {
+ // This should not happen
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ // This should not happen
+ throw new RuntimeException(e);
+ }
+
+ // For each application class, print license description -
+ // once
+ if (!licensePrintedForApplicationClass
+ .containsKey(applicationClass)) {
+ licensePrintedForApplicationClass.put(applicationClass,
+ Boolean.TRUE);
+ if (license.shouldLimitsBePrintedOnInit()) {
+ System.out.println(license
+ .getDescription(application.getClass()
+ .toString()));
+ }
+ }
+
+ // Checks license validity
+ try {
+ license.check(applicationClass, VERSION_MAJOR,
+ VERSION_MINOR, "IT Mill Toolkit", null);
+ } catch (LicenseFileHasNotBeenRead e) {
+ application.close();
+ throw e;
+ } catch (LicenseSignatureIsInvalid e) {
+ application.close();
+ throw e;
+ } catch (InvalidLicenseFile e) {
+ application.close();
+ throw e;
+ } catch (LicenseViolation e) {
+ application.close();
+ throw e;
+ }
+ }
+ }
+
+ // Checks concurrent user limit
+ try {
+ license.checkConcurrentUsers(getNumberOfActiveUsers() + 1);
+ } catch (LicenseViolation e) {
+ application.close();
+ throw e;
+ }
+ }
+
+ /**
+ * Gets the number of active application-user pairs.
+ *
+ * This returns total number of all applications in the server that are
+ * considered to be active. For an application to be active, it must have
+ * been accessed less than ACTIVE_USER_REQUEST_INTERVAL ms.
+ *
+ * @return the Number of active application instances in the server.
+ */
+ private int getNumberOfActiveUsers() {
+ int active = 0;
+
+ synchronized (applicationToLastRequestDate) {
+ Set apps = applicationToLastRequestDate.keySet();
+ long now = System.currentTimeMillis();
+ for (Iterator i = apps.iterator(); i.hasNext();) {
+ Date lastReq = (Date) applicationToLastRequestDate
+ .get(i.next());
+ if (now - lastReq.getTime() < ACTIVE_USER_REQUEST_INTERVAL)
+ active++;
+ }
+ }
+
+ return active;
+ }
+
+ /**
+ * 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();
+
+ HttpSession session = request.getSession();
+ if (session != null) {
+ LinkedList applications = (LinkedList) session
+ .getAttribute(SESSION_ATTR_APPS);
+ if (applications != null)
+ applications.remove(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 mathing the given URI or null if not found.
+ * @throws ServletException
+ * if an exception has occurred that interferes with the
+ * servlet's normal operation.
+ */
+ private Window getApplicationWindow(HttpServletRequest request,
+ Application application, Map params) throws ServletException {
+
+ Window window = null;
+
+ // Finds the window where the request is handled
+ String path = request.getPathInfo();
+
+ // Main window as the URI is empty
+ if (path == null || path.length() == 0 || path.equals("/"))
+ window = application.getMainWindow();
+
+ // Try to search by window name
+ else {
+ String windowName = null;
+ if (path.charAt(0) == '/')
+ path = path.substring(1);
+ 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();
+ return resourcePath + theme + "/" + resource.getResourceId();
+ }
+
+ /**
+ * Checks if web adapter is in debug mode. Extra output is generated to log
+ * when debug mode is enabled.
+ *
+ * @param parameters
+ * @return <code>true</code> if the web adapter is in debug mode.
+ * otherwise <code>false</code>.
+ */
+ public boolean isDebugMode(Map parameters) {
+ if (parameters != null) {
+ Object[] debug = (Object[]) parameters.get("debug");
+ if (debug != null && !"false".equals(debug[0].toString())
+ && !"false".equals(debugMode))
+ return true;
+ }
+ return "true".equals(debugMode);
+ }
+
+ /**
+ *
+ * SessionBindingListener performs Application cleanups after sessions are
+ * expired. For each session exists one SessionBindingListener. It contains
+ * references to all applications related to single session.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 4.0
+ */
+
+ private class SessionBindingListener implements HttpSessionBindingListener {
+ private LinkedList applications;
+
+ /**
+ *
+ * @param applications
+ */
+ protected SessionBindingListener(LinkedList applications) {
+ this.applications = applications;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
+ */
+ public void valueBound(HttpSessionBindingEvent arg0) {
+ // We are not interested in bindings
+ }
+
+ /**
+ * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
+ */
+ public void valueUnbound(HttpSessionBindingEvent event) {
+ // If the binding listener is unbound from the session, the
+ // session must be closing
+ if (event.getName().equals(SESSION_BINDING_LISTENER)) {
+ // Close all applications related to given session
+ Object[] apps = applications.toArray();
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i] != null) {
+ // Close application
+ ((Application) apps[i]).close();
+
+ synchronized (applicationToLastRequestDate) {
+ applicationToLastRequestDate.remove(apps[i]);
+ }
+ synchronized (applicationToAjaxAppMgrMap) {
+ applicationToAjaxAppMgrMap.remove(apps[i]);
+ }
+ // Remove application from applications list
+ applications.remove(apps[i]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implementation of ParameterHandler.ErrorEvent interface.
+ */
+ public class ParameterHandlerErrorImpl implements
+ ParameterHandler.ErrorEvent {
+
+ private ParameterHandler owner;
+
+ private Throwable throwable;
+
+ /**
+ *
+ * @param owner
+ * @param throwable
+ */
+ private ParameterHandlerErrorImpl(ParameterHandler 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 this.throwable;
+ }
+
+ /**
+ * Gets the source ParameterHandler.
+ *
+ * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
+ */
+ public ParameterHandler getParameterHandler() {
+ return this.owner;
+ }
+
+ }
+
+ /**
+ * Implementation of URIHandler.ErrorEvent interface.
+ */
+ public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
+
+ private URIHandler owner;
+
+ private 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 this.throwable;
+ }
+
+ /**
+ * Gets the source URIHandler.
+ *
+ * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
+ */
+ public URIHandler getURIHandler() {
+ return this.owner;
+ }
+ }
+
+ /**
+ * Gets AJAX application manager for an application.
+ *
+ * If this application has not been running in ajax mode before, new manager
+ * is created and web adapter stops listening to changes.
+ *
+ * @param application
+ * @return AJAX Application Manager
+ */
+ private ApplicationManager getApplicationManager(Application application) {
+ ApplicationManager mgr = (ApplicationManager) applicationToAjaxAppMgrMap
+ .get(application);
+
+ // This application is going from Web to AJAX mode, create new manager
+ if (mgr == null) {
+ // Creates new manager
+ mgr = new ApplicationManager(application, this);
+ applicationToAjaxAppMgrMap.put(application, mgr);
+
+ // Manager takes control over the application
+ mgr.takeControl();
+ }
+
+ return mgr;
+ }
+
+ /**
+ * Gets resource path using different implementations. Required fo
+ * 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 {
+ URL url = servletContext.getResource(path);
+ resultPath = url.getFile();
+ } catch (Exception e) {
+ // ignored
+ }
+ }
+ return resultPath;
+ }
+
+} \ No newline at end of file