From: Leif Åstrand Date: Mon, 3 Sep 2012 10:57:20 +0000 (+0300) Subject: Combine ApplicationServlet and AAS and rename to VaadinServlet (#9460) X-Git-Tag: 7.0.0.beta1~206^2~1 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6f276f4451cb185919084181b54fb84532a1d984;p=vaadin-framework.git Combine ApplicationServlet and AAS and rename to VaadinServlet (#9460) --- diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 58a125c10f..70c875b10c 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -25,10 +25,10 @@ com.vaadin.launcher.ApplicationRunnerServlet - + IntegrationTest - com.vaadin.server.ApplicationServlet + com.vaadin.server.VaadinServlet application com.vaadin.tests.integration.IntegrationTestApplication diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index 0bb9bf1989..f620a39a70 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -146,7 +146,7 @@ public class ApplicationConfiguration implements EntryPoint { * * @return a string with the version * - * @see com.vaadin.server.AbstractApplicationServlet#VERSION + * @see com.vaadin.server.VaadinServlet#VERSION */ private native String getVaadinVersion() /*-{ diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index 8a043154d7..2af3c6a59f 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -48,7 +48,6 @@ import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.event.EventRouter; -import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.AbstractErrorMessage; import com.vaadin.server.ApplicationContext; import com.vaadin.server.BootstrapFragmentResponse; @@ -64,6 +63,7 @@ import com.vaadin.server.RequestHandler; import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.VariableOwner; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; @@ -1728,7 +1728,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * Handles a request by passing it to each registered {@link RequestHandler} * in turn until one produces a response. This method is used for requests * that have not been handled by any specific functionality in the terminal - * implementation (e.g. {@link AbstractApplicationServlet}). + * implementation (e.g. {@link VaadinServlet}). *

* The request handlers are invoked in the revere order in which they were * added to the application until a response has been produced. This means diff --git a/server/src/com/vaadin/data/Validator.java b/server/src/com/vaadin/data/Validator.java index 1755a44920..421d88f574 100644 --- a/server/src/com/vaadin/data/Validator.java +++ b/server/src/com/vaadin/data/Validator.java @@ -18,7 +18,7 @@ package com.vaadin.data; import java.io.Serializable; -import com.vaadin.server.AbstractApplicationServlet; +import com.vaadin.server.VaadinServlet; /** * Interface that implements a method for validating if an {@link Object} is @@ -144,8 +144,7 @@ public interface Validator extends Serializable { * Note that this API may change in future versions. */ public String getHtmlMessage() { - return AbstractApplicationServlet - .safeEscapeForHtml(getLocalizedMessage()); + return VaadinServlet.safeEscapeForHtml(getLocalizedMessage()); } /** diff --git a/server/src/com/vaadin/server/AbstractApplicationServlet.java b/server/src/com/vaadin/server/AbstractApplicationServlet.java deleted file mode 100644 index 8b3103b794..0000000000 --- a/server/src/com/vaadin/server/AbstractApplicationServlet.java +++ /dev/null @@ -1,1557 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -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.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import com.vaadin.Application; -import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.server.AbstractCommunicationManager.Callback; -import com.vaadin.shared.ApplicationConstants; -import com.vaadin.ui.UI; - -/** - * 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 Vaadin Ltd. - * @since 6.0 - */ - -@SuppressWarnings("serial") -public abstract class AbstractApplicationServlet extends HttpServlet implements - Constants { - - private static class AbstractApplicationServletWrapper implements Callback { - - private final AbstractApplicationServlet servlet; - - public AbstractApplicationServletWrapper( - AbstractApplicationServlet servlet) { - this.servlet = servlet; - } - - @Override - public void criticalNotification(WrappedRequest request, - WrappedResponse response, String cap, String msg, - String details, String outOfSyncURL) throws IOException { - servlet.criticalNotification( - WrappedHttpServletRequest.cast(request), - ((WrappedHttpServletResponse) response), cap, msg, details, - outOfSyncURL); - } - } - - // TODO Move some (all?) of the constants to a separate interface (shared - // with portlet) - - private final String resourcePath = null; - - private DeploymentConfiguration deploymentConfiguration; - - private AddonContext addonContext; - - /** - * 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); - Properties applicationProperties = new Properties(); - - // Read default parameters from server.xml - final ServletContext context = servletConfig.getServletContext(); - for (final Enumeration e = context.getInitParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - context.getInitParameter(name)); - } - - // Override with application config from web.xml - for (final Enumeration e = servletConfig - .getInitParameterNames(); e.hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - servletConfig.getInitParameter(name)); - } - - deploymentConfiguration = new AbstractDeploymentConfiguration( - getClass(), applicationProperties) { - - @Override - public String getStaticFileLocation(WrappedRequest request) { - HttpServletRequest servletRequest = WrappedHttpServletRequest - .cast(request); - return AbstractApplicationServlet.this - .getStaticFilesLocation(servletRequest); - } - - @Override - public String getConfiguredWidgetset(WrappedRequest request) { - return getApplicationOrSystemProperty( - AbstractApplicationServlet.PARAMETER_WIDGETSET, - AbstractApplicationServlet.DEFAULT_WIDGETSET); - } - - @Override - public String getConfiguredTheme(WrappedRequest request) { - // Use the default - return AbstractApplicationServlet.getDefaultTheme(); - } - - @Override - public boolean isStandalone(WrappedRequest request) { - return true; - } - - @Override - public String getMimeType(String resourceName) { - return getServletContext().getMimeType(resourceName); - } - - @Override - public SystemMessages getSystemMessages() { - return AbstractApplicationServlet.this.getSystemMessages(); - } - }; - - addonContext = new AddonContext(deploymentConfiguration); - addonContext.init(); - } - - @Override - public void destroy() { - super.destroy(); - - addonContext.destroy(); - } - - /** - * 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 getDeploymentConfiguration().isProductionMode(); - } - - /** - * Returns the number of seconds the browser should cache a file. Default is - * 1 hour (3600 s). - * - * @return The number of seconds files are cached in the browser - */ - public int getResourceCacheTime() { - return getDeploymentConfiguration().getResourceCacheTime(); - } - - /** - * 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 { - service(createWrappedRequest(request), createWrappedResponse(response)); - } - - private void service(WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws ServletException, - IOException { - RequestTimer requestTimer = new RequestTimer(); - requestTimer.start(); - - AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( - this); - - RequestType requestType = getRequestType(request); - if (!ensureCookiesEnabled(requestType, request, response)) { - return; - } - - if (requestType == RequestType.STATIC_FILE) { - serveStaticResources(request, response); - return; - } - - Application application = null; - boolean transactionStarted = false; - boolean requestStarted = false; - boolean applicationRunning = false; - - try { - // If a duplicate "close application" URL is received for an - // application that is not open, redirect to the application's main - // page. - // This is needed as e.g. Spring Security remembers the last - // URL from the application, which is the logout URL, and repeats - // it. - // We can tell apart a real onunload request from a repeated one - // based on the real one having content (at least the UIDL security - // key). - if (requestType == RequestType.UIDL - && request.getParameterMap().containsKey( - ApplicationConstants.PARAM_UNLOADBURST) - && request.getContentLength() < 1 - && getExistingApplication(request, false) == null) { - redirectToApplication(request, response); - return; - } - - // Find out which application this request is related to - application = findApplicationInstance(request, requestType); - if (application == null) { - return; - } - Application.setCurrent(application); - - /* - * Get or create a WebApplicationContext and an ApplicationManager - * for the session - */ - ServletApplicationContext webApplicationContext = getApplicationContext(request - .getSession()); - CommunicationManager applicationManager = webApplicationContext - .getApplicationManager(application, this); - - if (requestType == RequestType.CONNECTOR_RESOURCE) { - applicationManager.serveConnectorResource(request, response); - return; - } else if (requestType == RequestType.HEARTBEAT) { - applicationManager.handleHeartbeatRequest(request, response, - application); - return; - } - - /* Update browser information from the request */ - webApplicationContext.getBrowser().updateRequestDetails(request); - - /* - * Call application requestStart before Application.init() is called - * (bypasses the limitation in TransactionListener) - */ - if (application instanceof HttpServletRequestListener) { - ((HttpServletRequestListener) application).onRequestStart( - request, response); - requestStarted = true; - } - - // Start the application if it's newly created - startApplication(request, application, webApplicationContext); - applicationRunning = true; - - /* - * Transaction starts. Call transaction listeners. Transaction end - * is called in the finally block below. - */ - webApplicationContext.startTransaction(application, request); - transactionStarted = true; - - /* Handle the request */ - if (requestType == RequestType.FILE_UPLOAD) { - // UI is resolved in communication manager - applicationManager.handleFileUpload(application, request, - response); - return; - } else if (requestType == RequestType.UIDL) { - UI uI = application.getUIForRequest(request); - if (uI == null) { - throw new ServletException(ERROR_NO_UI_FOUND); - } - // Handles AJAX UIDL requests - applicationManager.handleUidlRequest(request, response, - servletWrapper, uI); - return; - } else if (requestType == RequestType.BROWSER_DETAILS) { - // Browser details - not related to a specific UI - applicationManager.handleBrowserDetailsRequest(request, - response, application); - return; - } - - // Removes application if it has stopped (maybe by thread or - // transactionlistener) - if (!application.isRunning()) { - endApplication(request, response, application); - return; - } - - if (applicationManager.handleApplicationRequest(request, response)) { - return; - } - // TODO Should return 404 error here and not do anything more - - } catch (final SessionExpiredException e) { - // Session has expired, notify user - handleServiceSessionExpired(request, response); - } catch (final GeneralSecurityException e) { - handleServiceSecurityException(request, response); - } catch (final Throwable e) { - handleServiceException(request, response, application, e); - } finally { - - if (applicationRunning) { - application.closeInactiveUIs(); - } - - // Notifies transaction end - try { - if (transactionStarted) { - ((ServletApplicationContext) application.getContext()) - .endTransaction(application, request); - - } - - } finally { - try { - if (requestStarted) { - ((HttpServletRequestListener) application) - .onRequestEnd(request, response); - } - } finally { - UI.setCurrent(null); - Application.setCurrent(null); - - HttpSession session = request.getSession(false); - if (session != null) { - requestTimer.stop(getApplicationContext(session)); - } - } - } - - } - } - - private WrappedHttpServletResponse createWrappedResponse( - HttpServletResponse response) { - WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse( - response, getDeploymentConfiguration()); - return wrappedResponse; - } - - /** - * Create a wrapped request for a http servlet request. This method can be - * overridden if the wrapped request should have special properties. - * - * @param request - * the original http servlet request - * @return a wrapped request for the original request - */ - protected WrappedHttpServletRequest createWrappedRequest( - HttpServletRequest request) { - return new WrappedHttpServletRequest(request, - getDeploymentConfiguration()); - } - - /** - * Gets a the deployment configuration for this servlet. - * - * @return the deployment configuration - */ - protected DeploymentConfiguration getDeploymentConfiguration() { - return deploymentConfiguration; - } - - /** - * Check that cookie support is enabled in the browser. Only checks UIDL - * requests. - * - * @param requestType - * Type of the request as returned by - * {@link #getRequestType(HttpServletRequest)} - * @param request - * The request from the browser - * @param response - * The response to which an error can be written - * @return false if cookies are disabled, true otherwise - * @throws IOException - */ - private boolean ensureCookiesEnabled(RequestType requestType, - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - if (requestType == RequestType.UIDL && !isRepaintAll(request)) { - // In all other but the first UIDL request a cookie should be - // returned by the browser. - // This can be removed if cookieless mode (#3228) is supported - if (request.getRequestedSessionId() == null) { - // User has cookies disabled - criticalNotification(request, response, getSystemMessages() - .getCookiesDisabledCaption(), getSystemMessages() - .getCookiesDisabledMessage(), null, getSystemMessages() - .getCookiesDisabledURL()); - return false; - } - } - return true; - } - - /** - * Send a notification to client's application. Used to notify client of - * critical errors, session expiration and more. 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 - * the notification caption - * @param message - * to notification body - * @param details - * a detail message to show in addition to the message. Currently - * shown directly below the message but could be hidden behind a - * details drop down in the future. Mainly used to give - * additional information not necessarily useful to the end user. - * @param url - * url to load when the message is dismissed. Null will reload - * the current page. - * @throws IOException - * if the writing failed due to input/output error. - */ - protected void criticalNotification(WrappedHttpServletRequest request, - HttpServletResponse response, String caption, String message, - String details, String url) throws IOException { - - if (ServletPortletHelper.isUIDLRequest(request)) { - - if (caption != null) { - caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; - } - if (details != null) { - if (message == null) { - message = details; - } else { - message += "

" + details; - } - } - - if (message != null) { - message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; - } - if (url != null) { - url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; - } - - String output = "for(;;);[{\"changes\":[], \"meta\" : {" - + "\"appError\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}}, \"resources\": {}, \"locales\":[]}]"; - writeResponse(response, "application/json; charset=UTF-8", output); - } else { - // Create an HTML reponse with the error - String output = ""; - - if (url != null) { - output += ""; - } - if (caption != null) { - output += "" + caption + "
"; - } - if (message != null) { - output += message; - output += "

"; - } - - if (details != null) { - output += details; - output += "

"; - } - if (url != null) { - output += "
"; - } - writeResponse(response, "text/html; charset=UTF-8", output); - - } - - } - - /** - * Writes the response in {@code output} using the contentType given in - * {@code contentType} to the provided {@link HttpServletResponse} - * - * @param response - * @param contentType - * @param output - * Output to write (UTF-8 encoded) - * @throws IOException - */ - private void writeResponse(HttpServletResponse response, - String contentType, String output) throws IOException { - response.setContentType(contentType); - final ServletOutputStream out = response.getOutputStream(); - // Set the response type - final PrintWriter outWriter = new PrintWriter(new BufferedWriter( - new OutputStreamWriter(out, "UTF-8"))); - outWriter.print(output); - 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 IllegalAccessException - * @throws InstantiationException - * @throws ServletException - * @throws SessionExpiredException - */ - private Application findApplicationInstance(HttpServletRequest request, - RequestType requestType) throws MalformedURLException, - ServletException, SessionExpiredException { - - boolean requestCanCreateApplication = requestCanCreateApplication( - request, requestType); - - /* Find an existing application for this request. */ - Application application = getExistingApplication(request, - requestCanCreateApplication); - - 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. - */ - - final boolean restartApplication = (request - .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); - final boolean closeApplication = (request - .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); - - 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 (requestCanCreateApplication) { - /* - * If the request is such that it should create a new application if - * one as not found, we do that. - */ - return createApplication(request); - } else { - /* - * The application was not found and a new one should not be - * created. Assume the session has expired. - */ - throw new SessionExpiredException(); - } - - } - - /** - * Check if the request should create an application if an existing - * application is not found. - * - * @param request - * @param requestType - * @return true if an application should be created, false otherwise - */ - boolean requestCanCreateApplication(HttpServletRequest request, - RequestType requestType) { - 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 the bootstrap page. - */ - return true; - - } else if (requestType == RequestType.OTHER) { - /* - * I.e URIs that are not application resources or static (theme) - * files. - */ - return true; - - } - - return false; - } - - /** - * 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 - getLogger().log(Level.INFO, - "Could not find resource path " + path, e); - } - } - return resultPath; - } - - /** - * Creates a new application and registers it into WebApplicationContext - * (aka session). 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); - - final ServletApplicationContext context = getApplicationContext(request - .getSession()); - context.addApplication(newApplication); - - return newApplication; - } - - private void handleServiceException(WrappedHttpServletRequest request, - WrappedHttpServletResponse response, Application application, - Throwable e) throws IOException, ServletException { - // if this was an UIDL request, response UIDL back to client - if (getRequestType(request) == RequestType.UIDL) { - SystemMessages ci = getSystemMessages(); - criticalNotification(request, response, - ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), - null, 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); - } - - } - - /** - * A helper method to strip away characters that might somehow be used for - * XSS attacs. Leaves at least alphanumeric characters intact. Also removes - * eg. ( and ), so values should be safe in javascript too. - * - * @param themeName - * @return - */ - protected static String stripSpecialChars(String themeName) { - StringBuilder sb = new StringBuilder(); - char[] charArray = themeName.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (!CHAR_BLACKLIST.contains(c)) { - sb.append(c); - } - } - return sb.toString(); - } - - private static final Collection CHAR_BLACKLIST = new HashSet( - Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')', - ';' })); - - /** - * Returns the default theme. Must never return null. - * - * @return - */ - public static String getDefaultTheme() { - return DEFAULT_THEME_NAME; - } - - void handleServiceSessionExpired(WrappedHttpServletRequest request, - WrappedHttpServletResponse 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 { - 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 { - /* - * 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. - * - * Session must be invalidated before criticalNotification as it - * commits the response. - */ - request.getSession().invalidate(); - - // send uidl redirect - criticalNotification(request, response, - ci.getSessionExpiredCaption(), - ci.getSessionExpiredMessage(), null, - ci.getSessionExpiredURL()); - - } - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - - } - - private void handleServiceSecurityException( - WrappedHttpServletRequest request, - WrappedHttpServletResponse 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 { - SystemMessages ci = getSystemMessages(); - if (getRequestType(request) != RequestType.UIDL) { - // 'plain' http req - e.g. browser reload; - // just go ahead redirect the browser - response.sendRedirect(ci.getCommunicationErrorURL()); - } else { - // send uidl redirect - criticalNotification(request, response, - ci.getCommunicationErrorCaption(), - ci.getCommunicationErrorMessage(), - INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL()); - /* - * Invalidate session. Portal integration will fail otherwise - * since the session is not created by the portal. - */ - request.getSession().invalidate(); - } - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - - log("Invalid security key received from " + request.getRemoteHost()); - } - - /** - * 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. - * - * @param request - * @param application - * @param webApplicationContext - * @throws ServletException - * @throws MalformedURLException - */ - private void startApplication(HttpServletRequest request, - Application application, ServletApplicationContext webApplicationContext) - throws ServletException, MalformedURLException { - - if (!application.isRunning()) { - // Create application - final URL applicationUrl = getApplicationUrl(request); - - // Initial locale comes from the request - Locale locale = request.getLocale(); - application.setLocale(locale); - application.start(new ApplicationStartEvent(applicationUrl, - getDeploymentConfiguration(), webApplicationContext)); - addonContext.fireApplicationStarted(application); - } - } - - /** - * Check if this is a request for a static resource and, if it is, serve the - * resource to the client. - * - * @param request - * @param response - * @return true if a file was served and the request has been handled, false - * otherwise. - * @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("/VAADIN/"))) { - serveStaticResourcesInVAADIN(request.getRequestURI(), request, - response); - return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { - serveStaticResourcesInVAADIN( - request.getRequestURI().substring( - request.getContextPath().length()), request, - response); - return true; - } - - return false; - } - - /** - * Serve resources from VAADIN directory. - * - * @param filename - * The filename to serve. Should always start with /VAADIN/. - * @param request - * @param response - * @throws IOException - * @throws ServletException - */ - private void serveStaticResourcesInVAADIN(String filename, - HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - - final ServletContext sc = getServletContext(); - URL resourceUrl = sc.getResource(filename); - if (resourceUrl == null) { - // try if requested file is found from classloader - - // strip leading "/" otherwise stream from JAR wont work - filename = filename.substring(1); - resourceUrl = getDeploymentConfiguration().getClassLoader() - .getResource(filename); - - if (resourceUrl == null) { - // cannot serve requested file - getLogger() - .info("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/VAADIN folder."); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - // security check: do not permit navigation out of the VAADIN - // directory - if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { - getLogger() - .info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - return; - } - } - - // Find the modification timestamp - long lastModifiedTime = 0; - URLConnection connection = null; - try { - connection = resourceUrl.openConnection(); - lastModifiedTime = connection.getLastModified(); - // Remove milliseconds to avoid comparison problems (milliseconds - // are not returned by the browser in the "If-Modified-Since" - // header). - lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000; - - if (browserHasNewestVersion(request, lastModifiedTime)) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - return; - } - } catch (Exception e) { - // Failed to find out last modified timestamp. Continue without it. - getLogger() - .log(Level.FINEST, - "Failed to find out last modified timestamp. Continuing without it.", - e); - } finally { - if (connection instanceof URLConnection) { - try { - // Explicitly close the input stream to prevent it - // from remaining hanging - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 - InputStream is = connection.getInputStream(); - if (is != null) { - is.close(); - } - } catch (IOException e) { - getLogger().log(Level.INFO, - "Error closing URLConnection input stream", e); - } - } - } - - // Set type mime type if we can determine it based on the filename - final String mimetype = sc.getMimeType(filename); - if (mimetype != null) { - response.setContentType(mimetype); - } - - // Provide modification timestamp to the browser if it is known. - if (lastModifiedTime > 0) { - response.setDateHeader("Last-Modified", lastModifiedTime); - /* - * The browser is allowed to cache for 1 hour without checking if - * the file has changed. This forces browsers to fetch a new version - * when the Vaadin version is updated. This will cause more requests - * to the servlet than without this but for high volume sites the - * static files should never be served through the servlet. The - * cache timeout can be configured by setting the resourceCacheTime - * parameter in web.xml - */ - response.setHeader("Cache-Control", - "max-age= " + String.valueOf(getResourceCacheTime())); - } - - // Write the resource to the client. - final OutputStream os = response.getOutputStream(); - final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; - int bytes; - InputStream is = resourceUrl.openStream(); - while ((bytes = is.read(buffer)) >= 0) { - os.write(buffer, 0, bytes); - } - is.close(); - } - - /** - * Check whether a URL obtained from a classloader refers to a valid static - * resource in the directory VAADIN. - * - * Warning: Overriding of this method is not recommended, but is possible to - * support non-default classloaders or servers that may produce URLs - * different from the normal ones. The method prototype may change in the - * future. Care should be taken not to expose class files or other resources - * outside the VAADIN directory if the method is overridden. - * - * @param request - * @param resourceUrl - * @return - * - * @since 6.6.7 - */ - protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request, - URL resourceUrl) { - if ("jar".equals(resourceUrl.getProtocol())) { - // This branch is used for accessing resources directly from the - // Vaadin JAR in development environments and in similar cases. - - // Inside a JAR, a ".." would mean a real directory named ".." so - // using it in paths should just result in the file not being found. - // However, performing a check in case some servers or class loaders - // try to normalize the path by collapsing ".." before the class - // loader sees it. - - if (!resourceUrl.getPath().contains("!/VAADIN/")) { - getLogger().info( - "Blocked attempt to access a JAR entry not starting with /VAADIN/: " - + resourceUrl); - return false; - } - getLogger().fine( - "Accepted access to a JAR entry using a class loader: " - + resourceUrl); - return true; - } else { - // Some servers such as GlassFish extract files from JARs (file:) - // and e.g. JBoss 5+ use protocols vsf: and vfsfile: . - - // Check that the URL is in a VAADIN directory and does not contain - // "/../" - if (!resourceUrl.getPath().contains("/VAADIN/") - || resourceUrl.getPath().contains("/../")) { - getLogger().info( - "Blocked attempt to access file: " + resourceUrl); - return false; - } - getLogger().fine( - "Accepted access to a file using a class loader: " - + resourceUrl); - return true; - } - } - - /** - * Checks if the browser has an up to date cached version of requested - * resource. Currently the check is performed using the "If-Modified-Since" - * header. Could be expanded if needed. - * - * @param request - * The HttpServletRequest from the browser. - * @param resourceLastModifiedTimestamp - * The timestamp when the resource was last modified. 0 if the - * last modification time is unknown. - * @return true if the If-Modified-Since header tells the cached version in - * the browser is up to date, false otherwise - */ - private boolean browserHasNewestVersion(HttpServletRequest request, - long resourceLastModifiedTimestamp) { - if (resourceLastModifiedTimestamp < 1) { - // We do not know when it was modified so the browser cannot have an - // up-to-date version - return false; - } - /* - * The browser can request the resource conditionally using an - * If-Modified-Since header. Check this against the last modification - * time. - */ - try { - // If-Modified-Since represents the timestamp of the version cached - // in the browser - long headerIfModifiedSince = request - .getDateHeader("If-Modified-Since"); - - if (headerIfModifiedSince >= resourceLastModifiedTimestamp) { - // Browser has this an up-to-date version of the resource - return true; - } - } catch (Exception e) { - // Failed to parse header. Fail silently - the browser does not have - // an up-to-date version in its cache. - } - return false; - } - - protected enum RequestType { - FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT; - } - - protected RequestType getRequestType(WrappedHttpServletRequest request) { - if (ServletPortletHelper.isFileUploadRequest(request)) { - return RequestType.FILE_UPLOAD; - } else if (ServletPortletHelper.isConnectorResourceRequest(request)) { - return RequestType.CONNECTOR_RESOURCE; - } else if (isBrowserDetailsRequest(request)) { - return RequestType.BROWSER_DETAILS; - } else if (ServletPortletHelper.isUIDLRequest(request)) { - return RequestType.UIDL; - } else if (isStaticResourceRequest(request)) { - return RequestType.STATIC_FILE; - } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { - return RequestType.APPLICATION_RESOURCE; - } else if (ServletPortletHelper.isHeartbeatRequest(request)) { - return RequestType.HEARTBEAT; - } - return RequestType.OTHER; - - } - - private static boolean isBrowserDetailsRequest(HttpServletRequest request) { - return "POST".equals(request.getMethod()) - && request.getParameter("browserDetails") != null; - } - - private boolean isStaticResourceRequest(HttpServletRequest request) { - String pathInfo = request.getPathInfo(); - if (pathInfo == null || pathInfo.length() <= 10) { - return false; - } - - if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { - return true; - } - - return false; - } - - private boolean isOnUnloadRequest(HttpServletRequest request) { - return request.getParameter(ApplicationConstants.PARAM_UNLOADBURST) != null; - } - - /** - * Get system messages - * - * @return - */ - protected SystemMessages getSystemMessages() { - return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; - } - - /** - * Return the URL from where static files, e.g. the widgetset and the theme, - * are served. In a standard configuration the VAADIN folder inside the - * returned folder is what is used for widgetsets and themes. - * - * The returned folder is usually the same as the context path and - * independent of the application. - * - * @param request - * @return The location of static resources (should contain the VAADIN - * directory). Never ends with a slash (/). - */ - protected String getStaticFilesLocation(HttpServletRequest request) { - - return getWebApplicationsStaticFileLocation(request); - } - - /** - * The default method to fetch static files location (URL). This method does - * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH} - * - * @param request - * @return - */ - private String getWebApplicationsStaticFileLocation( - HttpServletRequest request) { - String staticFileLocation; - // if property is defined in configurations, use that - staticFileLocation = getDeploymentConfiguration() - .getApplicationOrSystemProperty(PARAMETER_VAADIN_RESOURCES, - null); - if (staticFileLocation != null) { - return staticFileLocation; - } - - // the last (but most common) option is to generate default location - // from request - - // if context is specified add it to widgetsetUrl - String ctxPath = request.getContextPath(); - - // FIXME: ctxPath.length() == 0 condition is probably unnecessary and - // might even be wrong. - - 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"); - } - - // Remove heading and trailing slashes from the context path - ctxPath = removeHeadingOrTrailing(ctxPath, "/"); - - if (ctxPath.equals("")) { - return ""; - } else { - return "/" + ctxPath; - } - } - - /** - * Remove any heading or trailing "what" from the "string". - * - * @param string - * @param what - * @return - */ - private static String removeHeadingOrTrailing(String string, String what) { - while (string.startsWith(what)) { - string = string.substring(1); - } - - while (string.endsWith(what)) { - string = string.substring(0, string.length() - 1); - } - - return string; - } - - /** - * Write a redirect response to the main page of the application. - * - * @param request - * @param response - * @throws IOException - * if sending the redirect fails due to an input/output error or - * a bad application URL - */ - private void redirectToApplication(HttpServletRequest request, - HttpServletResponse response) throws IOException { - String applicationUrl = getApplicationUrl(request).toExternalForm(); - response.sendRedirect(response.encodeRedirectURL(applicationUrl)); - } - - /** - * 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. - */ - protected 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. - * @param allowSessionCreation - * true if a session should be created if no session exists, - * false if no session should be created - * @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 IllegalAccessException - * @throws InstantiationException - * @throws SessionExpiredException - */ - protected Application getExistingApplication(HttpServletRequest request, - boolean allowSessionCreation) throws MalformedURLException, - SessionExpiredException { - - // Ensures that the session is still valid - final HttpSession session = request.getSession(allowSessionCreation); - if (session == null) { - throw new SessionExpiredException(); - } - - ServletApplicationContext context = 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 = 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 - 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) { - getApplicationContext(session).removeApplication(application); - } - - response.sendRedirect(response.encodeRedirectURL(logoutUrl)); - } - - /** - * Returns the path info; note that this _can_ be different than - * request.getPathInfo(). Examples where this might be useful: - *

    - *
  • An application runner servlet that runs different Vaadin applications - * based on an identifier.
  • - *
  • Providing a REST interface in the context root, while serving a - * Vaadin UI on a sub-URI using only one servlet (e.g. REST on - * http://example.com/foo, UI on http://example.com/foo/vaadin)
  • - * - * @param request - * @return - */ - protected 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) { - ServletApplicationContext context = getApplicationContext(session); - context.removeApplication(application); - } - } - - /** - * - * Gets the application context from an HttpSession. If no context is - * currently stored in a session a new context is created and stored in the - * session. - * - * @param session - * the HTTP session. - * @return the application context for HttpSession. - */ - protected ServletApplicationContext getApplicationContext(HttpSession session) { - /* - * TODO the ApplicationContext.getApplicationContext() should be removed - * and logic moved here. Now overriding context type is possible, but - * the whole creation logic should be here. MT 1101 - */ - return ServletApplicationContext.getApplicationContext(session); - } - - public class RequestError implements Terminal.ErrorEvent, Serializable { - - private final Throwable throwable; - - public RequestError(Throwable throwable) { - this.throwable = throwable; - } - - @Override - public Throwable getThrowable() { - return throwable; - } - - } - - /** - * Override this method if you need to use a specialized communicaiton - * mananger implementation. - * - * @deprecated Instead of overriding this method, override - * {@link ServletApplicationContext} implementation via - * {@link AbstractApplicationServlet#getApplicationContext(HttpSession)} - * method and in that customized implementation return your - * CommunicationManager in - * {@link ServletApplicationContext#getApplicationManager(Application, AbstractApplicationServlet)} - * method. - * - * @param application - * @return - */ - @Deprecated - public CommunicationManager createCommunicationManager( - Application application) { - return new CommunicationManager(application); - } - - /** - * Escapes characters to html entities. An exception is made for some - * "safe characters" to keep the text somewhat readable. - * - * @param unsafe - * @return a safe string to be added inside an html tag - */ - public static final String safeEscapeForHtml(String unsafe) { - if (null == unsafe) { - return null; - } - StringBuilder safe = new StringBuilder(); - char[] charArray = unsafe.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (isSafe(c)) { - safe.append(c); - } else { - safe.append("&#"); - safe.append((int) c); - safe.append(";"); - } - } - - return safe.toString(); - } - - private static boolean isSafe(char c) { - return // - c > 47 && c < 58 || // alphanum - c > 64 && c < 91 || // A-Z - c > 96 && c < 123 // a-z - ; - } - - private static final Logger getLogger() { - return Logger.getLogger(AbstractApplicationServlet.class.getName()); - } -} diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 740ecf843b..2e42f51249 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -1484,7 +1484,7 @@ public abstract class AbstractCommunicationManager implements Serializable { themeName = requestThemeName; } if (themeName == null) { - themeName = AbstractApplicationServlet.getDefaultTheme(); + themeName = VaadinServlet.getDefaultTheme(); } return themeName; } @@ -1502,7 +1502,7 @@ public abstract class AbstractCommunicationManager implements Serializable { public boolean isXSRFEnabled(Application application) { return !"true" .equals(application - .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); + .getProperty(VaadinServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); } /** diff --git a/server/src/com/vaadin/server/AbstractErrorMessage.java b/server/src/com/vaadin/server/AbstractErrorMessage.java index 0928ea4bd4..4e30dff06f 100644 --- a/server/src/com/vaadin/server/AbstractErrorMessage.java +++ b/server/src/com/vaadin/server/AbstractErrorMessage.java @@ -109,12 +109,11 @@ public abstract class AbstractErrorMessage implements ErrorMessage { String result = null; switch (getMode()) { case TEXT: - result = AbstractApplicationServlet.safeEscapeForHtml(getMessage()); + result = VaadinServlet.safeEscapeForHtml(getMessage()); break; case PREFORMATTED: - result = "
    "
    -                    + AbstractApplicationServlet
    -                            .safeEscapeForHtml(getMessage()) + "
    "; + result = "
    " + VaadinServlet.safeEscapeForHtml(getMessage())
    +                    + "
    "; break; case XHTML: result = getMessage(); diff --git a/server/src/com/vaadin/server/ApplicationServlet.java b/server/src/com/vaadin/server/ApplicationServlet.java deleted file mode 100644 index 905b48d62f..0000000000 --- a/server/src/com/vaadin/server/ApplicationServlet.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.server; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import com.vaadin.Application; -import com.vaadin.server.ServletPortletHelper.ApplicationClassException; - -/** - * This servlet connects a Vaadin Application to Web. - * - * @author Vaadin Ltd. - * @since 5.0 - */ - -@SuppressWarnings("serial") -public class ApplicationServlet extends AbstractApplicationServlet { - - // Private fields - private Class applicationClass; - - /** - * 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); - - // Loads the application class using the classloader defined in the - // deployment configuration - - try { - applicationClass = ServletPortletHelper - .getApplicationClass(getDeploymentConfiguration()); - } catch (ApplicationClassException e) { - throw new ServletException(e); - } - } - - @Override - protected Application getNewApplication(HttpServletRequest request) - throws ServletException { - - // Creates a new application instance - try { - final Application application = getApplicationClass().newInstance(); - application.addUIProvider(new DefaultUIProvider()); - - return application; - } catch (final IllegalAccessException e) { - throw new ServletException("getNewApplication failed", e); - } catch (final InstantiationException e) { - throw new ServletException("getNewApplication failed", e); - } - } - - protected Class getApplicationClass() { - return applicationClass; - } -} diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index 98ed1071de..a1438312b6 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -277,7 +277,7 @@ public abstract class BootstrapHandler implements RequestHandler { .getConfiguredWidgetset(request); } - widgetset = AbstractApplicationServlet.stripSpecialChars(widgetset); + widgetset = VaadinServlet.stripSpecialChars(widgetset); return widgetset; } @@ -450,7 +450,7 @@ public abstract class BootstrapHandler implements RequestHandler { String staticFileLocation = deploymentConfiguration .getStaticFileLocation(request); String widgetsetBase = staticFileLocation + "/" - + AbstractApplicationServlet.WIDGETSET_DIRECTORY_PATH; + + VaadinServlet.WIDGETSET_DIRECTORY_PATH; defaults.put("widgetsetBase", widgetsetBase); if (!application.isProductionMode()) { @@ -486,8 +486,8 @@ public abstract class BootstrapHandler implements RequestHandler { WrappedRequest request = context.getRequest(); final String staticFilePath = request.getDeploymentConfiguration() .getStaticFileLocation(request); - return staticFilePath + "/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName; + return staticFilePath + "/" + VaadinServlet.THEME_DIRECTORY_PATH + + themeName; } /** @@ -517,7 +517,7 @@ public abstract class BootstrapHandler implements RequestHandler { // XSS preventation, theme names shouldn't contain special chars anyway. // The servlet denies them via url parameter. - themeName = AbstractApplicationServlet.stripSpecialChars(themeName); + themeName = VaadinServlet.stripSpecialChars(themeName); return themeName; } diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java index af28438f57..cc92023919 100644 --- a/server/src/com/vaadin/server/CommunicationManager.java +++ b/server/src/com/vaadin/server/CommunicationManager.java @@ -45,7 +45,7 @@ public class CommunicationManager extends AbstractCommunicationManager { */ @Deprecated public CommunicationManager(Application application, - AbstractApplicationServlet applicationServlet) { + VaadinServlet applicationServlet) { super(application); } @@ -100,7 +100,7 @@ public class CommunicationManager extends AbstractCommunicationManager { @Override public String getThemeName(BootstrapContext context) { String themeName = context.getRequest().getParameter( - AbstractApplicationServlet.URL_PARAMETER_THEME); + VaadinServlet.URL_PARAMETER_THEME); if (themeName == null) { themeName = super.getThemeName(context); } @@ -117,7 +117,7 @@ public class CommunicationManager extends AbstractCommunicationManager { ServletContext servletContext = context.getHttpSession() .getServletContext(); return servletContext.getResourceAsStream("/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName - + "/" + resource); + + VaadinServlet.THEME_DIRECTORY_PATH + themeName + "/" + + resource); } } diff --git a/server/src/com/vaadin/server/GAEApplicationServlet.java b/server/src/com/vaadin/server/GAEApplicationServlet.java deleted file mode 100644 index 240984c760..0000000000 --- a/server/src/com/vaadin/server/GAEApplicationServlet.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import com.google.appengine.api.datastore.Blob; -import com.google.appengine.api.datastore.DatastoreService; -import com.google.appengine.api.datastore.DatastoreServiceFactory; -import com.google.appengine.api.datastore.Entity; -import com.google.appengine.api.datastore.EntityNotFoundException; -import com.google.appengine.api.datastore.FetchOptions.Builder; -import com.google.appengine.api.datastore.Key; -import com.google.appengine.api.datastore.KeyFactory; -import com.google.appengine.api.datastore.PreparedQuery; -import com.google.appengine.api.datastore.Query; -import com.google.appengine.api.datastore.Query.FilterOperator; -import com.google.appengine.api.memcache.Expiration; -import com.google.appengine.api.memcache.MemcacheService; -import com.google.appengine.api.memcache.MemcacheServiceFactory; -import com.google.apphosting.api.DeadlineExceededException; - -/** - * ApplicationServlet to be used when deploying to Google App Engine, in - * web.xml: - * - *
    - *      <servlet>
    - *              <servlet-name>HelloWorld</servlet-name>
    - *              <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
    - *              <init-param>
    - *                      <param-name>application</param-name>
    - *                      <param-value>com.vaadin.demo.HelloWorld</param-value>
    - *              </init-param>
    - *      </servlet>
    - * 
    - * - * Session support must be enabled in appengine-web.xml: - * - *
    - *      <sessions-enabled>true</sessions-enabled>
    - * 
    - * - * Appengine datastore cleanup can be invoked by calling one of the applications - * with an additional path "/CLEAN". This can be set up as a cron-job in - * cron.xml (see appengine documentation for more information): - * - *
    - * <cronentries>
    - *   <cron>
    - *     <url>/HelloWorld/CLEAN</url>
    - *     <description>Clean up sessions</description>
    - *     <schedule>every 2 hours</schedule>
    - *   </cron>
    - * </cronentries>
    - * 
    - * - * It is recommended (but not mandatory) to extract themes and widgetsets and - * have App Engine server these statically. Extract VAADIN folder (and it's - * contents) 'next to' the WEB-INF folder, and add the following to - * appengine-web.xml: - * - *
    - *      <static-files>
    - *           <include path="/VAADIN/**" />
    - *      </static-files>
    - * 
    - * - * Additional limitations: - *
      - *
    • Do not change application state when serving an ApplicationResource. - *
    • Avoid changing application state in transaction handlers, unless you're - * confident you fully understand the synchronization issues in App Engine. - *
    • The application remains locked while uploading - no progressbar is - * possible. - *
    - */ -public class GAEApplicationServlet extends ApplicationServlet { - - // memcache mutex is MUTEX_BASE + sessio id - private static final String MUTEX_BASE = "_vmutex"; - - // used identify ApplicationContext in memcache and datastore - private static final String AC_BASE = "_vac"; - - // UIDL requests will attempt to gain access for this long before telling - // the client to retry - private static final int MAX_UIDL_WAIT_MILLISECONDS = 5000; - - // Tell client to retry after this delay. - // Note: currently interpreting Retry-After as ms, not sec - private static final int RETRY_AFTER_MILLISECONDS = 100; - - // Properties used in the datastore - private static final String PROPERTY_EXPIRES = "expires"; - private static final String PROPERTY_DATA = "data"; - - // path used for cleanup - private static final String CLEANUP_PATH = "/CLEAN"; - // max entities to clean at once - private static final int CLEANUP_LIMIT = 200; - // appengine session kind - private static final String APPENGINE_SESSION_KIND = "_ah_SESSION"; - // appengine session expires-parameter - private static final String PROPERTY_APPENGINE_EXPIRES = "_expires"; - - protected void sendDeadlineExceededNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "Deadline Exceeded", - "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...", - "", null); - } - - protected void sendNotSerializableNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "NotSerializableException", - "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", - "", getApplicationUrl(request).toString() - + "?restartApplication"); - } - - protected void sendCriticalErrorNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "Critical error", - "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", - "", getApplicationUrl(request).toString() - + "?restartApplication"); - } - - @Override - protected void service(HttpServletRequest unwrappedRequest, - HttpServletResponse unwrappedResponse) throws ServletException, - IOException { - WrappedHttpServletRequest request = new WrappedHttpServletRequest( - unwrappedRequest, getDeploymentConfiguration()); - WrappedHttpServletResponse response = new WrappedHttpServletResponse( - unwrappedResponse, getDeploymentConfiguration()); - - if (isCleanupRequest(request)) { - cleanDatastore(); - return; - } - - RequestType requestType = getRequestType(request); - - if (requestType == RequestType.STATIC_FILE) { - // no locking needed, let superclass handle - super.service(request, response); - cleanSession(request); - return; - } - - if (requestType == RequestType.APPLICATION_RESOURCE) { - // no locking needed, let superclass handle - getApplicationContext(request, - MemcacheServiceFactory.getMemcacheService()); - super.service(request, response); - cleanSession(request); - return; - } - - final HttpSession session = request - .getSession(requestCanCreateApplication(request, requestType)); - if (session == null) { - handleServiceSessionExpired(request, response); - cleanSession(request); - return; - } - - boolean locked = false; - MemcacheService memcache = null; - String mutex = MUTEX_BASE + session.getId(); - memcache = MemcacheServiceFactory.getMemcacheService(); - try { - // try to get lock - long started = new Date().getTime(); - // non-UIDL requests will try indefinitely - while (requestType != RequestType.UIDL - || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) { - locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40), - MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); - if (locked) { - break; - } - try { - Thread.sleep(RETRY_AFTER_MILLISECONDS); - } catch (InterruptedException e) { - getLogger().finer( - "Thread.sleep() interrupted while waiting for lock. Trying again. " - + e); - } - } - - if (!locked) { - // Not locked; only UIDL can get trough here unlocked: tell - // client to retry - response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - // Note: currently interpreting Retry-After as ms, not sec - response.setHeader("Retry-After", "" + RETRY_AFTER_MILLISECONDS); - return; - } - - // de-serialize or create application context, store in session - ApplicationContext ctx = getApplicationContext(request, memcache); - - super.service(request, response); - - // serialize - started = new Date().getTime(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(ctx); - oos.flush(); - byte[] bytes = baos.toByteArray(); - - started = new Date().getTime(); - - String id = AC_BASE + session.getId(); - Date expire = new Date(started - + (session.getMaxInactiveInterval() * 1000)); - Expiration expires = Expiration.onDate(expire); - - memcache.put(id, bytes, expires); - - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - Entity entity = new Entity(AC_BASE, id); - entity.setProperty(PROPERTY_EXPIRES, expire.getTime()); - entity.setProperty(PROPERTY_DATA, new Blob(bytes)); - ds.put(entity); - - } catch (DeadlineExceededException e) { - getLogger().warning("DeadlineExceeded for " + session.getId()); - sendDeadlineExceededNotification(request, response); - } catch (NotSerializableException e) { - getLogger().log(Level.SEVERE, "Not serializable!", e); - - // TODO this notification is usually not shown - should we redirect - // in some other way - can we? - sendNotSerializableNotification(request, response); - } catch (Exception e) { - getLogger().log(Level.WARNING, - "An exception occurred while servicing request.", e); - - sendCriticalErrorNotification(request, response); - } finally { - // "Next, please!" - if (locked) { - memcache.delete(mutex); - } - cleanSession(request); - } - } - - protected ApplicationContext getApplicationContext( - HttpServletRequest request, MemcacheService memcache) { - HttpSession session = request.getSession(); - String id = AC_BASE + session.getId(); - byte[] serializedAC = (byte[]) memcache.get(id); - if (serializedAC == null) { - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - Key key = KeyFactory.createKey(AC_BASE, id); - Entity entity = null; - try { - entity = ds.get(key); - } catch (EntityNotFoundException e) { - // Ok, we were a bit optimistic; we'll create a new one later - } - if (entity != null) { - Blob blob = (Blob) entity.getProperty(PROPERTY_DATA); - serializedAC = blob.getBytes(); - // bring it to memcache - memcache.put(AC_BASE + session.getId(), serializedAC, - Expiration.byDeltaSeconds(session - .getMaxInactiveInterval()), - MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); - } - } - if (serializedAC != null) { - ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC); - ObjectInputStream ois; - try { - ois = new ObjectInputStream(bais); - ApplicationContext applicationContext = (ApplicationContext) ois - .readObject(); - session.setAttribute(ServletApplicationContext.class.getName(), - applicationContext); - } catch (IOException e) { - getLogger().log( - Level.WARNING, - "Could not de-serialize ApplicationContext for " - + session.getId() - + " A new one will be created. ", e); - } catch (ClassNotFoundException e) { - getLogger().log( - Level.WARNING, - "Could not de-serialize ApplicationContext for " - + session.getId() - + " A new one will be created. ", e); - } - } - // will create new context if the above did not - return getApplicationContext(session); - - } - - private boolean isCleanupRequest(HttpServletRequest request) { - String path = getRequestPathInfo(request); - if (path != null && path.equals(CLEANUP_PATH)) { - return true; - } - return false; - } - - /** - * Removes the ApplicationContext from the session in order to minimize the - * data serialized to datastore and memcache. - * - * @param request - */ - private void cleanSession(HttpServletRequest request) { - HttpSession session = request.getSession(false); - if (session != null) { - session.removeAttribute(ServletApplicationContext.class.getName()); - } - } - - /** - * This will look at the timestamp and delete expired persisted Vaadin and - * appengine sessions from the datastore. - * - * TODO Possible improvements include: 1. Use transactions (requires entity - * groups - overkill?) 2. Delete one-at-a-time, catch possible exception, - * continue w/ next. - */ - private void cleanDatastore() { - long expire = new Date().getTime(); - try { - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - // Vaadin stuff first - { - Query q = new Query(AC_BASE); - q.setKeysOnly(); - - q.addFilter(PROPERTY_EXPIRES, - FilterOperator.LESS_THAN_OR_EQUAL, expire); - PreparedQuery pq = ds.prepare(q); - List entities = pq.asList(Builder - .withLimit(CLEANUP_LIMIT)); - if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired Vaadin sessions."); - List keys = new ArrayList(); - for (Entity e : entities) { - keys.add(e.getKey()); - } - ds.delete(keys); - } - } - // Also cleanup GAE sessions - { - Query q = new Query(APPENGINE_SESSION_KIND); - q.setKeysOnly(); - q.addFilter(PROPERTY_APPENGINE_EXPIRES, - FilterOperator.LESS_THAN_OR_EQUAL, expire); - PreparedQuery pq = ds.prepare(q); - List entities = pq.asList(Builder - .withLimit(CLEANUP_LIMIT)); - if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired appengine sessions."); - List keys = new ArrayList(); - for (Entity e : entities) { - keys.add(e.getKey()); - } - ds.delete(keys); - } - } - } catch (Exception e) { - getLogger().log(Level.WARNING, "Exception while cleaning.", e); - } - } - - private static final Logger getLogger() { - return Logger.getLogger(GAEApplicationServlet.class.getName()); - } -} diff --git a/server/src/com/vaadin/server/GAEVaadinServlet.java b/server/src/com/vaadin/server/GAEVaadinServlet.java new file mode 100644 index 0000000000..642737f73b --- /dev/null +++ b/server/src/com/vaadin/server/GAEVaadinServlet.java @@ -0,0 +1,428 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.google.appengine.api.datastore.Blob; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions.Builder; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.memcache.Expiration; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.apphosting.api.DeadlineExceededException; + +/** + * ApplicationServlet to be used when deploying to Google App Engine, in + * web.xml: + * + *
    + *      <servlet>
    + *              <servlet-name>HelloWorld</servlet-name>
    + *              <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
    + *              <init-param>
    + *                      <param-name>application</param-name>
    + *                      <param-value>com.vaadin.demo.HelloWorld</param-value>
    + *              </init-param>
    + *      </servlet>
    + * 
    + * + * Session support must be enabled in appengine-web.xml: + * + *
    + *      <sessions-enabled>true</sessions-enabled>
    + * 
    + * + * Appengine datastore cleanup can be invoked by calling one of the applications + * with an additional path "/CLEAN". This can be set up as a cron-job in + * cron.xml (see appengine documentation for more information): + * + *
    + * <cronentries>
    + *   <cron>
    + *     <url>/HelloWorld/CLEAN</url>
    + *     <description>Clean up sessions</description>
    + *     <schedule>every 2 hours</schedule>
    + *   </cron>
    + * </cronentries>
    + * 
    + * + * It is recommended (but not mandatory) to extract themes and widgetsets and + * have App Engine server these statically. Extract VAADIN folder (and it's + * contents) 'next to' the WEB-INF folder, and add the following to + * appengine-web.xml: + * + *
    + *      <static-files>
    + *           <include path="/VAADIN/**" />
    + *      </static-files>
    + * 
    + * + * Additional limitations: + *
      + *
    • Do not change application state when serving an ApplicationResource. + *
    • Avoid changing application state in transaction handlers, unless you're + * confident you fully understand the synchronization issues in App Engine. + *
    • The application remains locked while uploading - no progressbar is + * possible. + *
    + */ +public class GAEVaadinServlet extends VaadinServlet { + + // memcache mutex is MUTEX_BASE + sessio id + private static final String MUTEX_BASE = "_vmutex"; + + // used identify ApplicationContext in memcache and datastore + private static final String AC_BASE = "_vac"; + + // UIDL requests will attempt to gain access for this long before telling + // the client to retry + private static final int MAX_UIDL_WAIT_MILLISECONDS = 5000; + + // Tell client to retry after this delay. + // Note: currently interpreting Retry-After as ms, not sec + private static final int RETRY_AFTER_MILLISECONDS = 100; + + // Properties used in the datastore + private static final String PROPERTY_EXPIRES = "expires"; + private static final String PROPERTY_DATA = "data"; + + // path used for cleanup + private static final String CLEANUP_PATH = "/CLEAN"; + // max entities to clean at once + private static final int CLEANUP_LIMIT = 200; + // appengine session kind + private static final String APPENGINE_SESSION_KIND = "_ah_SESSION"; + // appengine session expires-parameter + private static final String PROPERTY_APPENGINE_EXPIRES = "_expires"; + + protected void sendDeadlineExceededNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "Deadline Exceeded", + "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...", + "", null); + } + + protected void sendNotSerializableNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "NotSerializableException", + "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", + "", getApplicationUrl(request).toString() + + "?restartApplication"); + } + + protected void sendCriticalErrorNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "Critical error", + "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", + "", getApplicationUrl(request).toString() + + "?restartApplication"); + } + + @Override + protected void service(HttpServletRequest unwrappedRequest, + HttpServletResponse unwrappedResponse) throws ServletException, + IOException { + WrappedHttpServletRequest request = new WrappedHttpServletRequest( + unwrappedRequest, getDeploymentConfiguration()); + WrappedHttpServletResponse response = new WrappedHttpServletResponse( + unwrappedResponse, getDeploymentConfiguration()); + + if (isCleanupRequest(request)) { + cleanDatastore(); + return; + } + + RequestType requestType = getRequestType(request); + + if (requestType == RequestType.STATIC_FILE) { + // no locking needed, let superclass handle + super.service(request, response); + cleanSession(request); + return; + } + + if (requestType == RequestType.APPLICATION_RESOURCE) { + // no locking needed, let superclass handle + getApplicationContext(request, + MemcacheServiceFactory.getMemcacheService()); + super.service(request, response); + cleanSession(request); + return; + } + + final HttpSession session = request + .getSession(requestCanCreateApplication(request, requestType)); + if (session == null) { + handleServiceSessionExpired(request, response); + cleanSession(request); + return; + } + + boolean locked = false; + MemcacheService memcache = null; + String mutex = MUTEX_BASE + session.getId(); + memcache = MemcacheServiceFactory.getMemcacheService(); + try { + // try to get lock + long started = new Date().getTime(); + // non-UIDL requests will try indefinitely + while (requestType != RequestType.UIDL + || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) { + locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40), + MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); + if (locked) { + break; + } + try { + Thread.sleep(RETRY_AFTER_MILLISECONDS); + } catch (InterruptedException e) { + getLogger().finer( + "Thread.sleep() interrupted while waiting for lock. Trying again. " + + e); + } + } + + if (!locked) { + // Not locked; only UIDL can get trough here unlocked: tell + // client to retry + response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + // Note: currently interpreting Retry-After as ms, not sec + response.setHeader("Retry-After", "" + RETRY_AFTER_MILLISECONDS); + return; + } + + // de-serialize or create application context, store in session + ApplicationContext ctx = getApplicationContext(request, memcache); + + super.service(request, response); + + // serialize + started = new Date().getTime(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(ctx); + oos.flush(); + byte[] bytes = baos.toByteArray(); + + started = new Date().getTime(); + + String id = AC_BASE + session.getId(); + Date expire = new Date(started + + (session.getMaxInactiveInterval() * 1000)); + Expiration expires = Expiration.onDate(expire); + + memcache.put(id, bytes, expires); + + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + Entity entity = new Entity(AC_BASE, id); + entity.setProperty(PROPERTY_EXPIRES, expire.getTime()); + entity.setProperty(PROPERTY_DATA, new Blob(bytes)); + ds.put(entity); + + } catch (DeadlineExceededException e) { + getLogger().warning("DeadlineExceeded for " + session.getId()); + sendDeadlineExceededNotification(request, response); + } catch (NotSerializableException e) { + getLogger().log(Level.SEVERE, "Not serializable!", e); + + // TODO this notification is usually not shown - should we redirect + // in some other way - can we? + sendNotSerializableNotification(request, response); + } catch (Exception e) { + getLogger().log(Level.WARNING, + "An exception occurred while servicing request.", e); + + sendCriticalErrorNotification(request, response); + } finally { + // "Next, please!" + if (locked) { + memcache.delete(mutex); + } + cleanSession(request); + } + } + + protected ApplicationContext getApplicationContext( + HttpServletRequest request, MemcacheService memcache) { + HttpSession session = request.getSession(); + String id = AC_BASE + session.getId(); + byte[] serializedAC = (byte[]) memcache.get(id); + if (serializedAC == null) { + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + Key key = KeyFactory.createKey(AC_BASE, id); + Entity entity = null; + try { + entity = ds.get(key); + } catch (EntityNotFoundException e) { + // Ok, we were a bit optimistic; we'll create a new one later + } + if (entity != null) { + Blob blob = (Blob) entity.getProperty(PROPERTY_DATA); + serializedAC = blob.getBytes(); + // bring it to memcache + memcache.put(AC_BASE + session.getId(), serializedAC, + Expiration.byDeltaSeconds(session + .getMaxInactiveInterval()), + MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); + } + } + if (serializedAC != null) { + ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC); + ObjectInputStream ois; + try { + ois = new ObjectInputStream(bais); + ApplicationContext applicationContext = (ApplicationContext) ois + .readObject(); + session.setAttribute(ServletApplicationContext.class.getName(), + applicationContext); + } catch (IOException e) { + getLogger().log( + Level.WARNING, + "Could not de-serialize ApplicationContext for " + + session.getId() + + " A new one will be created. ", e); + } catch (ClassNotFoundException e) { + getLogger().log( + Level.WARNING, + "Could not de-serialize ApplicationContext for " + + session.getId() + + " A new one will be created. ", e); + } + } + // will create new context if the above did not + return getApplicationContext(session); + + } + + private boolean isCleanupRequest(HttpServletRequest request) { + String path = getRequestPathInfo(request); + if (path != null && path.equals(CLEANUP_PATH)) { + return true; + } + return false; + } + + /** + * Removes the ApplicationContext from the session in order to minimize the + * data serialized to datastore and memcache. + * + * @param request + */ + private void cleanSession(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null) { + session.removeAttribute(ServletApplicationContext.class.getName()); + } + } + + /** + * This will look at the timestamp and delete expired persisted Vaadin and + * appengine sessions from the datastore. + * + * TODO Possible improvements include: 1. Use transactions (requires entity + * groups - overkill?) 2. Delete one-at-a-time, catch possible exception, + * continue w/ next. + */ + private void cleanDatastore() { + long expire = new Date().getTime(); + try { + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + // Vaadin stuff first + { + Query q = new Query(AC_BASE); + q.setKeysOnly(); + + q.addFilter(PROPERTY_EXPIRES, + FilterOperator.LESS_THAN_OR_EQUAL, expire); + PreparedQuery pq = ds.prepare(q); + List entities = pq.asList(Builder + .withLimit(CLEANUP_LIMIT)); + if (entities != null) { + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired Vaadin sessions."); + List keys = new ArrayList(); + for (Entity e : entities) { + keys.add(e.getKey()); + } + ds.delete(keys); + } + } + // Also cleanup GAE sessions + { + Query q = new Query(APPENGINE_SESSION_KIND); + q.setKeysOnly(); + q.addFilter(PROPERTY_APPENGINE_EXPIRES, + FilterOperator.LESS_THAN_OR_EQUAL, expire); + PreparedQuery pq = ds.prepare(q); + List entities = pq.asList(Builder + .withLimit(CLEANUP_LIMIT)); + if (entities != null) { + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired appengine sessions."); + List keys = new ArrayList(); + for (Entity e : entities) { + keys.add(e.getKey()); + } + ds.delete(keys); + } + } + } catch (Exception e) { + getLogger().log(Level.WARNING, "Exception while cleaning.", e); + } + } + + private static final Logger getLogger() { + return Logger.getLogger(GAEVaadinServlet.class.getName()); + } +} diff --git a/server/src/com/vaadin/server/ServletApplicationContext.java b/server/src/com/vaadin/server/ServletApplicationContext.java index 639ff117d1..a38c523254 100644 --- a/server/src/com/vaadin/server/ServletApplicationContext.java +++ b/server/src/com/vaadin/server/ServletApplicationContext.java @@ -123,7 +123,7 @@ public class ServletApplicationContext extends ApplicationContext { */ @Override public File getBaseDirectory() { - final String realPath = ApplicationServlet.getResourcePath( + final String realPath = VaadinServlet.getResourcePath( session.getServletContext(), "/"); if (realPath == null) { return null; @@ -175,7 +175,7 @@ public class ServletApplicationContext extends ApplicationContext { * @return CommunicationManager */ public CommunicationManager getApplicationManager(Application application, - AbstractApplicationServlet servlet) { + VaadinServlet servlet) { CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap .get(application); diff --git a/server/src/com/vaadin/server/SystemError.java b/server/src/com/vaadin/server/SystemError.java index 5d1426e87c..aa9ffcaf52 100644 --- a/server/src/com/vaadin/server/SystemError.java +++ b/server/src/com/vaadin/server/SystemError.java @@ -80,8 +80,7 @@ public class SystemError extends AbstractErrorMessage { StringBuilder sb = new StringBuilder(); if (getMessage() != null) { sb.append("

    "); - sb.append(AbstractApplicationServlet - .safeEscapeForHtml(getMessage())); + sb.append(VaadinServlet.safeEscapeForHtml(getMessage())); sb.append("

    "); } return sb.toString(); diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java new file mode 100644 index 0000000000..0766d46e93 --- /dev/null +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -0,0 +1,1564 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +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.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.vaadin.Application; +import com.vaadin.Application.ApplicationStartEvent; +import com.vaadin.server.AbstractCommunicationManager.Callback; +import com.vaadin.server.ServletPortletHelper.ApplicationClassException; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.ui.UI; + +@SuppressWarnings("serial") +public class VaadinServlet extends HttpServlet implements Constants { + + private static class AbstractApplicationServletWrapper implements Callback { + + private final VaadinServlet servlet; + + public AbstractApplicationServletWrapper(VaadinServlet servlet) { + this.servlet = servlet; + } + + @Override + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException { + servlet.criticalNotification( + WrappedHttpServletRequest.cast(request), + ((WrappedHttpServletResponse) response), cap, msg, details, + outOfSyncURL); + } + } + + // TODO Move some (all?) of the constants to a separate interface (shared + // with portlet) + + private final String resourcePath = null; + + private DeploymentConfiguration deploymentConfiguration; + + private AddonContext addonContext; + + /** + * 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); + Properties applicationProperties = new Properties(); + + // Read default parameters from server.xml + final ServletContext context = servletConfig.getServletContext(); + for (final Enumeration e = context.getInitParameterNames(); e + .hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + context.getInitParameter(name)); + } + + // Override with application config from web.xml + for (final Enumeration e = servletConfig + .getInitParameterNames(); e.hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + servletConfig.getInitParameter(name)); + } + + deploymentConfiguration = new AbstractDeploymentConfiguration( + getClass(), applicationProperties) { + + @Override + public String getStaticFileLocation(WrappedRequest request) { + HttpServletRequest servletRequest = WrappedHttpServletRequest + .cast(request); + return VaadinServlet.this + .getStaticFilesLocation(servletRequest); + } + + @Override + public String getConfiguredWidgetset(WrappedRequest request) { + return getApplicationOrSystemProperty( + VaadinServlet.PARAMETER_WIDGETSET, + VaadinServlet.DEFAULT_WIDGETSET); + } + + @Override + public String getConfiguredTheme(WrappedRequest request) { + // Use the default + return VaadinServlet.getDefaultTheme(); + } + + @Override + public boolean isStandalone(WrappedRequest request) { + return true; + } + + @Override + public String getMimeType(String resourceName) { + return getServletContext().getMimeType(resourceName); + } + + @Override + public SystemMessages getSystemMessages() { + return VaadinServlet.this.getSystemMessages(); + } + }; + + addonContext = new AddonContext(deploymentConfiguration); + addonContext.init(); + } + + @Override + public void destroy() { + super.destroy(); + + addonContext.destroy(); + } + + /** + * 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 getDeploymentConfiguration().isProductionMode(); + } + + /** + * Returns the number of seconds the browser should cache a file. Default is + * 1 hour (3600 s). + * + * @return The number of seconds files are cached in the browser + */ + public int getResourceCacheTime() { + return getDeploymentConfiguration().getResourceCacheTime(); + } + + /** + * 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 { + service(createWrappedRequest(request), createWrappedResponse(response)); + } + + private void service(WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws ServletException, + IOException { + RequestTimer requestTimer = new RequestTimer(); + requestTimer.start(); + + AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( + this); + + RequestType requestType = getRequestType(request); + if (!ensureCookiesEnabled(requestType, request, response)) { + return; + } + + if (requestType == RequestType.STATIC_FILE) { + serveStaticResources(request, response); + return; + } + + Application application = null; + boolean transactionStarted = false; + boolean requestStarted = false; + boolean applicationRunning = false; + + try { + // If a duplicate "close application" URL is received for an + // application that is not open, redirect to the application's main + // page. + // This is needed as e.g. Spring Security remembers the last + // URL from the application, which is the logout URL, and repeats + // it. + // We can tell apart a real onunload request from a repeated one + // based on the real one having content (at least the UIDL security + // key). + if (requestType == RequestType.UIDL + && request.getParameterMap().containsKey( + ApplicationConstants.PARAM_UNLOADBURST) + && request.getContentLength() < 1 + && getExistingApplication(request, false) == null) { + redirectToApplication(request, response); + return; + } + + // Find out which application this request is related to + application = findApplicationInstance(request, requestType); + if (application == null) { + return; + } + Application.setCurrent(application); + + /* + * Get or create a WebApplicationContext and an ApplicationManager + * for the session + */ + ServletApplicationContext webApplicationContext = getApplicationContext(request + .getSession()); + CommunicationManager applicationManager = webApplicationContext + .getApplicationManager(application, this); + + if (requestType == RequestType.CONNECTOR_RESOURCE) { + applicationManager.serveConnectorResource(request, response); + return; + } else if (requestType == RequestType.HEARTBEAT) { + applicationManager.handleHeartbeatRequest(request, response, + application); + return; + } + + /* Update browser information from the request */ + webApplicationContext.getBrowser().updateRequestDetails(request); + + /* + * Call application requestStart before Application.init() is called + * (bypasses the limitation in TransactionListener) + */ + if (application instanceof HttpServletRequestListener) { + ((HttpServletRequestListener) application).onRequestStart( + request, response); + requestStarted = true; + } + + // Start the application if it's newly created + startApplication(request, application, webApplicationContext); + applicationRunning = true; + + /* + * Transaction starts. Call transaction listeners. Transaction end + * is called in the finally block below. + */ + webApplicationContext.startTransaction(application, request); + transactionStarted = true; + + /* Handle the request */ + if (requestType == RequestType.FILE_UPLOAD) { + // UI is resolved in communication manager + applicationManager.handleFileUpload(application, request, + response); + return; + } else if (requestType == RequestType.UIDL) { + UI uI = application.getUIForRequest(request); + if (uI == null) { + throw new ServletException(ERROR_NO_UI_FOUND); + } + // Handles AJAX UIDL requests + applicationManager.handleUidlRequest(request, response, + servletWrapper, uI); + return; + } else if (requestType == RequestType.BROWSER_DETAILS) { + // Browser details - not related to a specific UI + applicationManager.handleBrowserDetailsRequest(request, + response, application); + return; + } + + // Removes application if it has stopped (maybe by thread or + // transactionlistener) + if (!application.isRunning()) { + endApplication(request, response, application); + return; + } + + if (applicationManager.handleApplicationRequest(request, response)) { + return; + } + // TODO Should return 404 error here and not do anything more + + } catch (final SessionExpiredException e) { + // Session has expired, notify user + handleServiceSessionExpired(request, response); + } catch (final GeneralSecurityException e) { + handleServiceSecurityException(request, response); + } catch (final Throwable e) { + handleServiceException(request, response, application, e); + } finally { + + if (applicationRunning) { + application.closeInactiveUIs(); + } + + // Notifies transaction end + try { + if (transactionStarted) { + ((ServletApplicationContext) application.getContext()) + .endTransaction(application, request); + + } + + } finally { + try { + if (requestStarted) { + ((HttpServletRequestListener) application) + .onRequestEnd(request, response); + } + } finally { + UI.setCurrent(null); + Application.setCurrent(null); + + HttpSession session = request.getSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } + } + } + + } + } + + private WrappedHttpServletResponse createWrappedResponse( + HttpServletResponse response) { + WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse( + response, getDeploymentConfiguration()); + return wrappedResponse; + } + + /** + * Create a wrapped request for a http servlet request. This method can be + * overridden if the wrapped request should have special properties. + * + * @param request + * the original http servlet request + * @return a wrapped request for the original request + */ + protected WrappedHttpServletRequest createWrappedRequest( + HttpServletRequest request) { + return new WrappedHttpServletRequest(request, + getDeploymentConfiguration()); + } + + /** + * Gets a the deployment configuration for this servlet. + * + * @return the deployment configuration + */ + protected DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + + /** + * Check that cookie support is enabled in the browser. Only checks UIDL + * requests. + * + * @param requestType + * Type of the request as returned by + * {@link #getRequestType(HttpServletRequest)} + * @param request + * The request from the browser + * @param response + * The response to which an error can be written + * @return false if cookies are disabled, true otherwise + * @throws IOException + */ + private boolean ensureCookiesEnabled(RequestType requestType, + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + if (requestType == RequestType.UIDL && !isRepaintAll(request)) { + // In all other but the first UIDL request a cookie should be + // returned by the browser. + // This can be removed if cookieless mode (#3228) is supported + if (request.getRequestedSessionId() == null) { + // User has cookies disabled + criticalNotification(request, response, getSystemMessages() + .getCookiesDisabledCaption(), getSystemMessages() + .getCookiesDisabledMessage(), null, getSystemMessages() + .getCookiesDisabledURL()); + return false; + } + } + return true; + } + + /** + * Send a notification to client's application. Used to notify client of + * critical errors, session expiration and more. 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 + * the notification caption + * @param message + * to notification body + * @param details + * a detail message to show in addition to the message. Currently + * shown directly below the message but could be hidden behind a + * details drop down in the future. Mainly used to give + * additional information not necessarily useful to the end user. + * @param url + * url to load when the message is dismissed. Null will reload + * the current page. + * @throws IOException + * if the writing failed due to input/output error. + */ + protected void criticalNotification(WrappedHttpServletRequest request, + HttpServletResponse response, String caption, String message, + String details, String url) throws IOException { + + if (ServletPortletHelper.isUIDLRequest(request)) { + + if (caption != null) { + caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; + } + if (details != null) { + if (message == null) { + message = details; + } else { + message += "

    " + details; + } + } + + if (message != null) { + message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; + } + if (url != null) { + url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; + } + + String output = "for(;;);[{\"changes\":[], \"meta\" : {" + + "\"appError\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}}, \"resources\": {}, \"locales\":[]}]"; + writeResponse(response, "application/json; charset=UTF-8", output); + } else { + // Create an HTML reponse with the error + String output = ""; + + if (url != null) { + output += ""; + } + if (caption != null) { + output += "" + caption + "
    "; + } + if (message != null) { + output += message; + output += "

    "; + } + + if (details != null) { + output += details; + output += "

    "; + } + if (url != null) { + output += "
    "; + } + writeResponse(response, "text/html; charset=UTF-8", output); + + } + + } + + /** + * Writes the response in {@code output} using the contentType given in + * {@code contentType} to the provided {@link HttpServletResponse} + * + * @param response + * @param contentType + * @param output + * Output to write (UTF-8 encoded) + * @throws IOException + */ + private void writeResponse(HttpServletResponse response, + String contentType, String output) throws IOException { + response.setContentType(contentType); + final ServletOutputStream out = response.getOutputStream(); + // Set the response type + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print(output); + 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 IllegalAccessException + * @throws InstantiationException + * @throws ServletException + * @throws SessionExpiredException + */ + private Application findApplicationInstance(HttpServletRequest request, + RequestType requestType) throws MalformedURLException, + ServletException, SessionExpiredException { + + boolean requestCanCreateApplication = requestCanCreateApplication( + request, requestType); + + /* Find an existing application for this request. */ + Application application = getExistingApplication(request, + requestCanCreateApplication); + + 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. + */ + + final boolean restartApplication = (request + .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); + final boolean closeApplication = (request + .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); + + 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 (requestCanCreateApplication) { + /* + * If the request is such that it should create a new application if + * one as not found, we do that. + */ + return createApplication(request); + } else { + /* + * The application was not found and a new one should not be + * created. Assume the session has expired. + */ + throw new SessionExpiredException(); + } + + } + + /** + * Check if the request should create an application if an existing + * application is not found. + * + * @param request + * @param requestType + * @return true if an application should be created, false otherwise + */ + boolean requestCanCreateApplication(HttpServletRequest request, + RequestType requestType) { + 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 the bootstrap page. + */ + return true; + + } else if (requestType == RequestType.OTHER) { + /* + * I.e URIs that are not application resources or static (theme) + * files. + */ + return true; + + } + + return false; + } + + /** + * 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 + getLogger().log(Level.INFO, + "Could not find resource path " + path, e); + } + } + return resultPath; + } + + /** + * Creates a new application and registers it into WebApplicationContext + * (aka session). 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); + + final ServletApplicationContext context = getApplicationContext(request + .getSession()); + context.addApplication(newApplication); + + return newApplication; + } + + private void handleServiceException(WrappedHttpServletRequest request, + WrappedHttpServletResponse response, Application application, + Throwable e) throws IOException, ServletException { + // if this was an UIDL request, response UIDL back to client + if (getRequestType(request) == RequestType.UIDL) { + SystemMessages ci = getSystemMessages(); + criticalNotification(request, response, + ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), + null, 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); + } + + } + + /** + * A helper method to strip away characters that might somehow be used for + * XSS attacs. Leaves at least alphanumeric characters intact. Also removes + * eg. ( and ), so values should be safe in javascript too. + * + * @param themeName + * @return + */ + protected static String stripSpecialChars(String themeName) { + StringBuilder sb = new StringBuilder(); + char[] charArray = themeName.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (!CHAR_BLACKLIST.contains(c)) { + sb.append(c); + } + } + return sb.toString(); + } + + private static final Collection CHAR_BLACKLIST = new HashSet( + Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')', + ';' })); + + /** + * Returns the default theme. Must never return null. + * + * @return + */ + public static String getDefaultTheme() { + return DEFAULT_THEME_NAME; + } + + void handleServiceSessionExpired(WrappedHttpServletRequest request, + WrappedHttpServletResponse 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 { + 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 { + /* + * 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. + * + * Session must be invalidated before criticalNotification as it + * commits the response. + */ + request.getSession().invalidate(); + + // send uidl redirect + criticalNotification(request, response, + ci.getSessionExpiredCaption(), + ci.getSessionExpiredMessage(), null, + ci.getSessionExpiredURL()); + + } + } catch (SystemMessageException ee) { + throw new ServletException(ee); + } + + } + + private void handleServiceSecurityException( + WrappedHttpServletRequest request, + WrappedHttpServletResponse 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 { + SystemMessages ci = getSystemMessages(); + if (getRequestType(request) != RequestType.UIDL) { + // 'plain' http req - e.g. browser reload; + // just go ahead redirect the browser + response.sendRedirect(ci.getCommunicationErrorURL()); + } else { + // send uidl redirect + criticalNotification(request, response, + ci.getCommunicationErrorCaption(), + ci.getCommunicationErrorMessage(), + INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL()); + /* + * Invalidate session. Portal integration will fail otherwise + * since the session is not created by the portal. + */ + request.getSession().invalidate(); + } + } catch (SystemMessageException ee) { + throw new ServletException(ee); + } + + log("Invalid security key received from " + request.getRemoteHost()); + } + + /** + * Creates a new application for the given request. + * + * @param request + * the HTTP request. + * @return A new Application instance. + * @throws ServletException + */ + protected Application getNewApplication(HttpServletRequest request) + throws ServletException { + + // Creates a new application instance + try { + Class applicationClass = ServletPortletHelper + .getApplicationClass(getDeploymentConfiguration()); + + final Application application = applicationClass.newInstance(); + application.addUIProvider(new DefaultUIProvider()); + + return application; + } catch (final IllegalAccessException e) { + throw new ServletException("getNewApplication failed", e); + } catch (final InstantiationException e) { + throw new ServletException("getNewApplication failed", e); + } catch (ApplicationClassException e) { + throw new ServletException("getNewApplication failed", e); + } + } + + /** + * Starts the application if it is not already running. + * + * @param request + * @param application + * @param webApplicationContext + * @throws ServletException + * @throws MalformedURLException + */ + private void startApplication(HttpServletRequest request, + Application application, + ServletApplicationContext webApplicationContext) + throws ServletException, MalformedURLException { + + if (!application.isRunning()) { + // Create application + final URL applicationUrl = getApplicationUrl(request); + + // Initial locale comes from the request + Locale locale = request.getLocale(); + application.setLocale(locale); + application.start(new ApplicationStartEvent(applicationUrl, + getDeploymentConfiguration(), webApplicationContext)); + addonContext.fireApplicationStarted(application); + } + } + + /** + * Check if this is a request for a static resource and, if it is, serve the + * resource to the client. + * + * @param request + * @param response + * @return true if a file was served and the request has been handled, false + * otherwise. + * @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("/VAADIN/"))) { + serveStaticResourcesInVAADIN(request.getRequestURI(), request, + response); + return true; + } else if (request.getRequestURI().startsWith( + request.getContextPath() + "/VAADIN/")) { + serveStaticResourcesInVAADIN( + request.getRequestURI().substring( + request.getContextPath().length()), request, + response); + return true; + } + + return false; + } + + /** + * Serve resources from VAADIN directory. + * + * @param filename + * The filename to serve. Should always start with /VAADIN/. + * @param request + * @param response + * @throws IOException + * @throws ServletException + */ + private void serveStaticResourcesInVAADIN(String filename, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + final ServletContext sc = getServletContext(); + URL resourceUrl = sc.getResource(filename); + if (resourceUrl == null) { + // try if requested file is found from classloader + + // strip leading "/" otherwise stream from JAR wont work + filename = filename.substring(1); + resourceUrl = getDeploymentConfiguration().getClassLoader() + .getResource(filename); + + if (resourceUrl == null) { + // cannot serve requested file + getLogger() + .info("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/VAADIN folder."); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // security check: do not permit navigation out of the VAADIN + // directory + if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { + getLogger() + .info("Requested resource [" + + filename + + "] not accessible in the VAADIN directory or access to it is forbidden."); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + // Find the modification timestamp + long lastModifiedTime = 0; + URLConnection connection = null; + try { + connection = resourceUrl.openConnection(); + lastModifiedTime = connection.getLastModified(); + // Remove milliseconds to avoid comparison problems (milliseconds + // are not returned by the browser in the "If-Modified-Since" + // header). + lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000; + + if (browserHasNewestVersion(request, lastModifiedTime)) { + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + } catch (Exception e) { + // Failed to find out last modified timestamp. Continue without it. + getLogger() + .log(Level.FINEST, + "Failed to find out last modified timestamp. Continuing without it.", + e); + } finally { + if (connection instanceof URLConnection) { + try { + // Explicitly close the input stream to prevent it + // from remaining hanging + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 + InputStream is = connection.getInputStream(); + if (is != null) { + is.close(); + } + } catch (IOException e) { + getLogger().log(Level.INFO, + "Error closing URLConnection input stream", e); + } + } + } + + // Set type mime type if we can determine it based on the filename + final String mimetype = sc.getMimeType(filename); + if (mimetype != null) { + response.setContentType(mimetype); + } + + // Provide modification timestamp to the browser if it is known. + if (lastModifiedTime > 0) { + response.setDateHeader("Last-Modified", lastModifiedTime); + /* + * The browser is allowed to cache for 1 hour without checking if + * the file has changed. This forces browsers to fetch a new version + * when the Vaadin version is updated. This will cause more requests + * to the servlet than without this but for high volume sites the + * static files should never be served through the servlet. The + * cache timeout can be configured by setting the resourceCacheTime + * parameter in web.xml + */ + response.setHeader("Cache-Control", + "max-age= " + String.valueOf(getResourceCacheTime())); + } + + // Write the resource to the client. + final OutputStream os = response.getOutputStream(); + final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; + int bytes; + InputStream is = resourceUrl.openStream(); + while ((bytes = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytes); + } + is.close(); + } + + /** + * Check whether a URL obtained from a classloader refers to a valid static + * resource in the directory VAADIN. + * + * Warning: Overriding of this method is not recommended, but is possible to + * support non-default classloaders or servers that may produce URLs + * different from the normal ones. The method prototype may change in the + * future. Care should be taken not to expose class files or other resources + * outside the VAADIN directory if the method is overridden. + * + * @param request + * @param resourceUrl + * @return + * + * @since 6.6.7 + */ + protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request, + URL resourceUrl) { + if ("jar".equals(resourceUrl.getProtocol())) { + // This branch is used for accessing resources directly from the + // Vaadin JAR in development environments and in similar cases. + + // Inside a JAR, a ".." would mean a real directory named ".." so + // using it in paths should just result in the file not being found. + // However, performing a check in case some servers or class loaders + // try to normalize the path by collapsing ".." before the class + // loader sees it. + + if (!resourceUrl.getPath().contains("!/VAADIN/")) { + getLogger().info( + "Blocked attempt to access a JAR entry not starting with /VAADIN/: " + + resourceUrl); + return false; + } + getLogger().fine( + "Accepted access to a JAR entry using a class loader: " + + resourceUrl); + return true; + } else { + // Some servers such as GlassFish extract files from JARs (file:) + // and e.g. JBoss 5+ use protocols vsf: and vfsfile: . + + // Check that the URL is in a VAADIN directory and does not contain + // "/../" + if (!resourceUrl.getPath().contains("/VAADIN/") + || resourceUrl.getPath().contains("/../")) { + getLogger().info( + "Blocked attempt to access file: " + resourceUrl); + return false; + } + getLogger().fine( + "Accepted access to a file using a class loader: " + + resourceUrl); + return true; + } + } + + /** + * Checks if the browser has an up to date cached version of requested + * resource. Currently the check is performed using the "If-Modified-Since" + * header. Could be expanded if needed. + * + * @param request + * The HttpServletRequest from the browser. + * @param resourceLastModifiedTimestamp + * The timestamp when the resource was last modified. 0 if the + * last modification time is unknown. + * @return true if the If-Modified-Since header tells the cached version in + * the browser is up to date, false otherwise + */ + private boolean browserHasNewestVersion(HttpServletRequest request, + long resourceLastModifiedTimestamp) { + if (resourceLastModifiedTimestamp < 1) { + // We do not know when it was modified so the browser cannot have an + // up-to-date version + return false; + } + /* + * The browser can request the resource conditionally using an + * If-Modified-Since header. Check this against the last modification + * time. + */ + try { + // If-Modified-Since represents the timestamp of the version cached + // in the browser + long headerIfModifiedSince = request + .getDateHeader("If-Modified-Since"); + + if (headerIfModifiedSince >= resourceLastModifiedTimestamp) { + // Browser has this an up-to-date version of the resource + return true; + } + } catch (Exception e) { + // Failed to parse header. Fail silently - the browser does not have + // an up-to-date version in its cache. + } + return false; + } + + protected enum RequestType { + FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT; + } + + protected RequestType getRequestType(WrappedHttpServletRequest request) { + if (ServletPortletHelper.isFileUploadRequest(request)) { + return RequestType.FILE_UPLOAD; + } else if (ServletPortletHelper.isConnectorResourceRequest(request)) { + return RequestType.CONNECTOR_RESOURCE; + } else if (isBrowserDetailsRequest(request)) { + return RequestType.BROWSER_DETAILS; + } else if (ServletPortletHelper.isUIDLRequest(request)) { + return RequestType.UIDL; + } else if (isStaticResourceRequest(request)) { + return RequestType.STATIC_FILE; + } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { + return RequestType.APPLICATION_RESOURCE; + } else if (ServletPortletHelper.isHeartbeatRequest(request)) { + return RequestType.HEARTBEAT; + } + return RequestType.OTHER; + + } + + private static boolean isBrowserDetailsRequest(HttpServletRequest request) { + return "POST".equals(request.getMethod()) + && request.getParameter("browserDetails") != null; + } + + private boolean isStaticResourceRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + if (pathInfo == null || pathInfo.length() <= 10) { + return false; + } + + if ((request.getContextPath() != null) + && (request.getRequestURI().startsWith("/VAADIN/"))) { + return true; + } else if (request.getRequestURI().startsWith( + request.getContextPath() + "/VAADIN/")) { + return true; + } + + return false; + } + + private boolean isOnUnloadRequest(HttpServletRequest request) { + return request.getParameter(ApplicationConstants.PARAM_UNLOADBURST) != null; + } + + /** + * Get system messages + * + * @return + */ + protected SystemMessages getSystemMessages() { + return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; + } + + /** + * Return the URL from where static files, e.g. the widgetset and the theme, + * are served. In a standard configuration the VAADIN folder inside the + * returned folder is what is used for widgetsets and themes. + * + * The returned folder is usually the same as the context path and + * independent of the application. + * + * @param request + * @return The location of static resources (should contain the VAADIN + * directory). Never ends with a slash (/). + */ + protected String getStaticFilesLocation(HttpServletRequest request) { + + return getWebApplicationsStaticFileLocation(request); + } + + /** + * The default method to fetch static files location (URL). This method does + * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH} + * + * @param request + * @return + */ + private String getWebApplicationsStaticFileLocation( + HttpServletRequest request) { + String staticFileLocation; + // if property is defined in configurations, use that + staticFileLocation = getDeploymentConfiguration() + .getApplicationOrSystemProperty(PARAMETER_VAADIN_RESOURCES, + null); + if (staticFileLocation != null) { + return staticFileLocation; + } + + // the last (but most common) option is to generate default location + // from request + + // if context is specified add it to widgetsetUrl + String ctxPath = request.getContextPath(); + + // FIXME: ctxPath.length() == 0 condition is probably unnecessary and + // might even be wrong. + + 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"); + } + + // Remove heading and trailing slashes from the context path + ctxPath = removeHeadingOrTrailing(ctxPath, "/"); + + if (ctxPath.equals("")) { + return ""; + } else { + return "/" + ctxPath; + } + } + + /** + * Remove any heading or trailing "what" from the "string". + * + * @param string + * @param what + * @return + */ + private static String removeHeadingOrTrailing(String string, String what) { + while (string.startsWith(what)) { + string = string.substring(1); + } + + while (string.endsWith(what)) { + string = string.substring(0, string.length() - 1); + } + + return string; + } + + /** + * Write a redirect response to the main page of the application. + * + * @param request + * @param response + * @throws IOException + * if sending the redirect fails due to an input/output error or + * a bad application URL + */ + private void redirectToApplication(HttpServletRequest request, + HttpServletResponse response) throws IOException { + String applicationUrl = getApplicationUrl(request).toExternalForm(); + response.sendRedirect(response.encodeRedirectURL(applicationUrl)); + } + + /** + * 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. + */ + protected 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. + * @param allowSessionCreation + * true if a session should be created if no session exists, + * false if no session should be created + * @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 IllegalAccessException + * @throws InstantiationException + * @throws SessionExpiredException + */ + protected Application getExistingApplication(HttpServletRequest request, + boolean allowSessionCreation) throws MalformedURLException, + SessionExpiredException { + + // Ensures that the session is still valid + final HttpSession session = request.getSession(allowSessionCreation); + if (session == null) { + throw new SessionExpiredException(); + } + + ServletApplicationContext context = 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 = 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 + 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) { + getApplicationContext(session).removeApplication(application); + } + + response.sendRedirect(response.encodeRedirectURL(logoutUrl)); + } + + /** + * Returns the path info; note that this _can_ be different than + * request.getPathInfo(). Examples where this might be useful: + *
      + *
    • An application runner servlet that runs different Vaadin applications + * based on an identifier.
    • + *
    • Providing a REST interface in the context root, while serving a + * Vaadin UI on a sub-URI using only one servlet (e.g. REST on + * http://example.com/foo, UI on http://example.com/foo/vaadin)
    • + * + * @param request + * @return + */ + protected 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) { + ServletApplicationContext context = getApplicationContext(session); + context.removeApplication(application); + } + } + + /** + * + * Gets the application context from an HttpSession. If no context is + * currently stored in a session a new context is created and stored in the + * session. + * + * @param session + * the HTTP session. + * @return the application context for HttpSession. + */ + protected ServletApplicationContext getApplicationContext( + HttpSession session) { + /* + * TODO the ApplicationContext.getApplicationContext() should be removed + * and logic moved here. Now overriding context type is possible, but + * the whole creation logic should be here. MT 1101 + */ + return ServletApplicationContext.getApplicationContext(session); + } + + public class RequestError implements Terminal.ErrorEvent, Serializable { + + private final Throwable throwable; + + public RequestError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public Throwable getThrowable() { + return throwable; + } + + } + + /** + * Override this method if you need to use a specialized communicaiton + * mananger implementation. + * + * @deprecated Instead of overriding this method, override + * {@link ServletApplicationContext} implementation via + * {@link VaadinServlet#getApplicationContext(HttpSession)} + * method and in that customized implementation return your + * CommunicationManager in + * {@link ServletApplicationContext#getApplicationManager(Application, VaadinServlet)} + * method. + * + * @param application + * @return + */ + @Deprecated + public CommunicationManager createCommunicationManager( + Application application) { + return new CommunicationManager(application); + } + + /** + * Escapes characters to html entities. An exception is made for some + * "safe characters" to keep the text somewhat readable. + * + * @param unsafe + * @return a safe string to be added inside an html tag + */ + public static final String safeEscapeForHtml(String unsafe) { + if (null == unsafe) { + return null; + } + StringBuilder safe = new StringBuilder(); + char[] charArray = unsafe.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (isSafe(c)) { + safe.append(c); + } else { + safe.append("&#"); + safe.append((int) c); + safe.append(";"); + } + } + + return safe.toString(); + } + + private static boolean isSafe(char c) { + return // + c > 47 && c < 58 || // alphanum + c > 64 && c < 91 || // A-Z + c > 96 && c < 123 // a-z + ; + } + + private static final Logger getLogger() { + return Logger.getLogger(VaadinServlet.class.getName()); + } +} diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java index 92090da14a..96cab99e40 100644 --- a/server/src/com/vaadin/server/WebBrowser.java +++ b/server/src/com/vaadin/server/WebBrowser.java @@ -335,9 +335,8 @@ public class WebBrowser { } /** - * For internal use by AbstractApplicationServlet/AbstractApplicationPortlet - * only. Updates all properties in the class according to the given - * information. + * For internal use by VaadinServlet/VaadinPortlet only. Updates all + * properties in the class according to the given information. * * @param sw * Screen width @@ -406,9 +405,8 @@ public class WebBrowser { } /** - * For internal use by AbstractApplicationServlet/AbstractApplicationPortlet - * only. Updates all properties in the class according to the given - * information. + * For internal use by VaadinServlet/VaadinPortlet only. Updates all + * properties in the class according to the given information. * * @param request * the wrapped request to read the information from diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index d1ccaacde3..0f914d3947 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -33,7 +33,6 @@ import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; -import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.LegacyComponent; import com.vaadin.server.Page; import com.vaadin.server.Page.BrowserWindowResizeEvent; @@ -41,6 +40,7 @@ import com.vaadin.server.Page.BrowserWindowResizeListener; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; import com.vaadin.server.Resource; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.shared.EventId; @@ -63,7 +63,7 @@ import com.vaadin.tools.ReflectTools; *

      *

      * When a new UI instance is needed, typically because the user opens a URL in a - * browser window which points to {@link AbstractApplicationServlet}, + * browser window which points to {@link VaadinServlet}, * {@link Application#getUIForRequest(WrappedRequest)} is invoked to get a UI. * That method does by default create a UI according to the * {@value Application#UI_PARAMETER} parameter from web.xml. diff --git a/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java b/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java index df16e98bba..3ae41610fa 100644 --- a/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java +++ b/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java @@ -15,14 +15,11 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; -import com.vaadin.server.AbstractApplicationServlet; -import com.vaadin.server.ApplicationServlet; - import junit.framework.TestCase; public class TestAbstractApplicationServletStaticFilesLocation extends TestCase { - ApplicationServlet servlet; + VaadinServlet servlet; private Method getStaticFilesLocationMethod; @@ -30,18 +27,16 @@ public class TestAbstractApplicationServletStaticFilesLocation extends TestCase protected void setUp() throws Exception { super.setUp(); - servlet = new ApplicationServlet(); + servlet = new VaadinServlet(); // Workaround to avoid calling init and creating servlet config - Field f = AbstractApplicationServlet.class - .getDeclaredField("applicationProperties"); + Field f = VaadinServlet.class.getDeclaredField("applicationProperties"); f.setAccessible(true); f.set(servlet, new Properties()); - getStaticFilesLocationMethod = AbstractApplicationServlet.class - .getDeclaredMethod( - "getStaticFilesLocation", - new Class[] { javax.servlet.http.HttpServletRequest.class }); + getStaticFilesLocationMethod = VaadinServlet.class.getDeclaredMethod( + "getStaticFilesLocation", + new Class[] { javax.servlet.http.HttpServletRequest.class }); getStaticFilesLocationMethod.setAccessible(true); } diff --git a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java index e2fe5df4c7..bbe6e061fb 100644 --- a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java +++ b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java @@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; -import com.vaadin.server.AbstractApplicationServlet; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.AbstractUIProvider; import com.vaadin.server.WrappedHttpServletRequest; import com.vaadin.server.WrappedRequest; @@ -38,7 +38,7 @@ import com.vaadin.tests.components.TestBase; import com.vaadin.ui.UI; @SuppressWarnings("serial") -public class ApplicationRunnerServlet extends AbstractApplicationServlet { +public class ApplicationRunnerServlet extends VaadinServlet { /** * The name of the application class currently used. Only valid within one