diff options
Diffstat (limited to 'src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java')
-rw-r--r-- | src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java | 1797 |
1 files changed, 1797 insertions, 0 deletions
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java new file mode 100644 index 0000000000..87f966a65b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -0,0 +1,1797 @@ +package com.vaadin.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.external.org.apache.commons.fileupload.servlet.ServletFileUpload; +import com.vaadin.Application; +import com.vaadin.Application.SystemMessages; +import com.vaadin.service.FileTypeResolver; +import com.vaadin.terminal.DownloadStream; +import com.vaadin.terminal.ParameterHandler; +import com.vaadin.terminal.Terminal; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.URIHandler; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.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.vaadin.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.vaadin.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("<!DOCTYPE html PUBLIC \"-//W3C//DTD " + + "XHTML 1.0 Transitional//EN\" " + + "\"http://www.w3.org/TR/xhtml1/" + + "DTD/xhtml1-transitional.dtd\">\n"); + + page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\"" + + ">\n<head>\n"); + page + .write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"); + page + .write("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=7\" />\n"); + page.write("<style type=\"text/css\">" + + "html, body {height:100%;}</style>"); + + // Add favicon links + page + .write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\"" + + themeUri + "/favicon.ico\" />"); + page + .write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\"" + + themeUri + "/favicon.ico\" />"); + + page.write("<title>" + title + "</title>"); + + page + .write("\n</head>\n<body scroll=\"auto\" class=\"i-generated-body\">\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("<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" " + + "style=\"width:0;height:0;border:0;overflow:" + + "hidden\" src=\"javascript:false\"></iframe>\n"); + page.write("<script language='javascript' src='" + staticFilePath + + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + "/" + + widgetset + ".nocache.js?" + new Date().getTime() + + "'></script>\n"); + page.write("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("if(!itmill || !itmill.toolkitConfigurations) {\n " + + "if(!itmill) { var itmill = {}} \n" + + "itmill.toolkitConfigurations = {};\n" + + "itmill.themesLoaded = {}};\n"); + + if (!isProductionMode()) { + page.write("itmill.debug = true;\n"); + } + + page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {"); + page.write("appUri:'" + appUrl + "', "); + page.write("pathInfo: '" + pathInfo + "', "); + if (window != application.getMainWindow()) { + page.write("windowName: '" + window.getName() + "', "); + } + page.write("themeUri:"); + page.write(themeUri != null ? "'" + themeUri + "'" : "null"); + page.write(", versionInfo : {toolkitVersion:\""); + page.write(VERSION); + page.write("\",applicationVersion:\""); + page.write(application.getVersion()); + page.write("\"},"); + if (systemMessages != null) { + // Write the CommunicationError -message to client + String caption = systemMessages.getCommunicationErrorCaption(); + if (caption != null) { + caption = "\"" + caption + "\""; + } + String message = systemMessages.getCommunicationErrorMessage(); + if (message != null) { + message = "\"" + message + "\""; + } + String url = systemMessages.getCommunicationErrorURL(); + if (url != null) { + url = "\"" + url + "\""; + } + page.write("\"comErrMsg\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}"); + } + page.write("};\n//]]>\n</script>\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("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n"); + page + .write("var stylesheet = document.createElement('link');\n"); + page.write("stylesheet.setAttribute('rel', 'stylesheet');\n"); + page.write("stylesheet.setAttribute('type', 'text/css');\n"); + page.write("stylesheet.setAttribute('href', '" + themeUri + + "/styles.css');\n"); + page + .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n"); + page.write("itmill.themesLoaded['" + themeName + + "'] = true;\n}\n"); + page.write("//]]>\n</script>\n"); + } + + } else { + page.write("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("if(!itmill || !itmill.toolkitConfigurations) {\n " + + "if(!itmill) { var itmill = {}} \n" + + "itmill.toolkitConfigurations = {};\n" + + "itmill.themesLoaded = {};\n"); + if (!isProductionMode()) { + page.write("itmill.debug = true;\n"); + } + page + .write("document.write('<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" " + + "style=\"width:0;height:0;border:0;overflow:" + + "hidden\" src=\"javascript:false\"></iframe>');\n"); + page.write("document.write(\"<script language='javascript' src='" + + staticFilePath + "/" + WIDGETSET_DIRECTORY_PATH + + widgetset + "/" + widgetset + ".nocache.js?" + + new Date().getTime() + "'><\\/script>\");\n}\n"); + + page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {"); + page.write("appUri:'" + appUrl + "', "); + page.write("pathInfo: '" + pathInfo + "', "); + if (window != application.getMainWindow()) { + page.write("windowName: '" + window.getName() + "', "); + } + page.write("themeUri:"); + page.write(themeUri != null ? "'" + themeUri + "'" : "null"); + page.write(", versionInfo : {toolkitVersion:\""); + page.write(VERSION); + page.write("\",applicationVersion:\""); + page.write(application.getVersion()); + page.write("\"},"); + if (systemMessages != null) { + // Write the CommunicationError -message to client + String caption = systemMessages.getCommunicationErrorCaption(); + if (caption != null) { + caption = "\"" + caption + "\""; + } + String message = systemMessages.getCommunicationErrorMessage(); + if (message != null) { + message = "\"" + message + "\""; + } + String url = systemMessages.getCommunicationErrorURL(); + if (url != null) { + url = "\"" + url + "\""; + } + + page.write("\"comErrMsg\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}"); + } + page.write("};\n//]]>\n</script>\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("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n"); + page + .write("var stylesheet = document.createElement('link');\n"); + page.write("stylesheet.setAttribute('rel', 'stylesheet');\n"); + page.write("stylesheet.setAttribute('type', 'text/css');\n"); + page.write("stylesheet.setAttribute('href', '" + themeUri + + "/styles.css');\n"); + page + .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n"); + page.write("itmill.themesLoaded['" + themeName + + "'] = true;\n}\n"); + page.write("//]]>\n</script>\n"); + } + } + + // Warn if the widgetset has not been loaded after 15 seconds on + // inactivity + page.write("<script type=\"text/javascript\">\n"); + page.write("//<![CDATA[\n"); + page.write("setTimeout('if (typeof " + widgetset.replace('.', '_') + + " == \"undefined\") {alert(\"Failed to load the widgetset: " + + staticFilePath + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + + "/" + widgetset + ".nocache.js\")};',15000);\n" + + "//]]>\n</script>\n"); + + String style = null; + reqParam = request.getAttribute(REQUEST_APPSTYLE); + if (reqParam != null) { + style = "style=\"" + reqParam + "\""; + } + /*- Add classnames; + * .i-app + * .i-app-loading + * .i-app-<simpleName for app class> + * .i-theme-<themeName, remove non-alphanum> + */ + 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("<div id=\"" + appId + "\" class=\"i-app i-app-loading " + + themeClass + " " + appClass + "\" " + + (style != null ? style : "") + "></div>\n"); + + if (!fragment) { + page.write("<noscript>" + getNoScriptMessage() + "</noscript>"); + page.write("</body>\n</html>\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 <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 (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.vaadin.terminal.Terminal.ErrorEvent#getThrowable() + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Gets the source ParameterHandler. + * + * @see com.vaadin.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.vaadin.terminal.Terminal.ErrorEvent#getThrowable() + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Gets the source URIHandler. + * + * @see com.vaadin.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; + } + + } +} |