package com.vaadin.terminal.gwt.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.xml.sax.SAXException;
import com.vaadin.Application;
import com.vaadin.Application.SystemMessages;
import com.vaadin.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.vaadin.terminal.DownloadStream;
import com.vaadin.terminal.ParameterHandler;
import com.vaadin.terminal.Terminal;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.terminal.URIHandler;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.ui.Window;
/**
* Abstract implementation of the ApplicationServlet which handles all
* communication between the client and the server.
*
* It is possible to extend this class to provide own functionality but in most
* cases this is unnecessary.
*
*
* @author IT Mill Ltd.
* @version
* @VERSION@
* @since 6.0
*/
@SuppressWarnings("serial")
public abstract class AbstractApplicationServlet extends HttpServlet implements
Constants {
// TODO Move some (all?) of the constants to a separate interface (shared
// with portlet)
/**
* The version number of this release. For example "6.2.0". Always in the
* format "major.minor.revision[.build]". The build part is optional. All of
* major, minor, revision must be integers.
*/
public static final String VERSION;
/**
* Major version number. For example 6 in 6.2.0.
*/
public static final int VERSION_MAJOR;
/**
* Minor version number. For example 2 in 6.2.0.
*/
public static final int VERSION_MINOR;
/**
* Version revision number. For example 0 in 6.2.0.
*/
public static final int VERSION_REVISION;
/**
* Build identifier. For example "nightly-20091123-c9963" in
* 6.2.0.nightly-20091123-c9963.
*/
public static final String VERSION_BUILD;
/* Initialize version numbers from string replaced by build-script. */
static {
if ("@VERSION@".equals("@" + "VERSION" + "@")) {
VERSION = "9.9.9.INTERNAL-DEBUG-BUILD";
} else {
VERSION = "@VERSION@";
}
final String[] digits = VERSION.split("\\.", 4);
VERSION_MAJOR = Integer.parseInt(digits[0]);
VERSION_MINOR = Integer.parseInt(digits[1]);
VERSION_REVISION = Integer.parseInt(digits[2]);
if (digits.length == 4) {
VERSION_BUILD = digits[3];
} else {
VERSION_BUILD = "";
}
}
/**
* If the attribute is present in the request, a html fragment will be
* written instead of a whole page.
*
* It is set to "true" by the {@link ApplicationPortlet} (Portlet 1.0) and
* read by {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_FRAGMENT = ApplicationServlet.class
.getName()
+ ".fragment";
/**
* This request attribute forces widgetsets to be loaded from under the
* specified base path; e.g shared widgetset for all portlets in a portal.
*
* It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
* {@link Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH} and read by
* {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_VAADIN_STATIC_FILE_PATH = ApplicationServlet.class
.getName()
+ ".widgetsetPath";
/**
* This request attribute forces widgetset used; e.g for portlets that can
* not have different widgetsets.
*
* It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
* {@link ApplicationPortlet.PORTLET_PARAMETER_WIDGETSET} and read by
* {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_WIDGETSET = ApplicationServlet.class
.getName()
+ ".widgetset";
/**
* This request attribute indicates the shared widgetset (e.g. portal-wide
* default widgetset).
*
* It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
* {@link Constants.PORTAL_PARAMETER_VAADIN_WIDGETSET} and read by
* {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_SHARED_WIDGETSET = ApplicationServlet.class
.getName()
+ ".sharedWidgetset";
/**
* If set, do not load the default theme but assume that loading it is
* handled e.g. by ApplicationPortlet.
*
* It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
* {@link Constants.PORTAL_PARAMETER_VAADIN_THEME} and read by
* {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_DEFAULT_THEME = ApplicationServlet.class
.getName()
+ ".defaultThemeUri";
/**
* This request attribute is used to add styles to the main element. E.g
* "height:500px" generates a style="height:500px" to the main element,
* useful from some embedding situations (e.g portlet include.)
*
* It is typically set by the {@link ApplicationPortlet} (Portlet 1.0) based
* on {@link ApplicationPortlet.PORTLET_PARAMETER_STYLE} and read by
* {@link AbstractApplicationServlet}.
*/
public static final String REQUEST_APPSTYLE = ApplicationServlet.class
.getName()
+ ".style";
private Properties applicationProperties;
private boolean productionMode = false;
private String resourcePath = null;
/**
* Called by the servlet container to indicate to a servlet that the servlet
* is being placed into service.
*
* @param servletConfig
* the object containing the servlet's configuration and
* initialization parameters
* @throws javax.servlet.ServletException
* if an exception has occurred that interferes with the
* servlet's normal operation.
*/
@SuppressWarnings("unchecked")
@Override
public void init(javax.servlet.ServletConfig servletConfig)
throws javax.servlet.ServletException {
super.init(servletConfig);
// Stores the application parameters into Properties object
applicationProperties = new Properties();
for (final Enumeration e = servletConfig.getInitParameterNames(); e
.hasMoreElements();) {
final String name = (String) e.nextElement();
applicationProperties.setProperty(name, servletConfig
.getInitParameter(name));
}
// Overrides with server.xml parameters
final ServletContext context = servletConfig.getServletContext();
for (final Enumeration e = context.getInitParameterNames(); e
.hasMoreElements();) {
final String name = (String) e.nextElement();
applicationProperties.setProperty(name, context
.getInitParameter(name));
}
checkProductionMode();
checkCrossSiteProtection();
}
private void checkCrossSiteProtection() {
if (getApplicationOrSystemProperty(
SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, "false").equals(
"true")) {
/*
* Print an information/warning message about running with xsrf
* protection disabled
*/
System.err.println(WARNING_XSRF_PROTECTION_DISABLED);
}
}
private void checkProductionMode() {
// Check if the application is in production mode.
// We are in production mode if Debug=false or productionMode=true
if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true")
.equals("false")) {
// "Debug=true" is the old way and should no longer be used
productionMode = true;
} else if (getApplicationOrSystemProperty(
SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) {
// "productionMode=true" is the real way to do it
productionMode = true;
}
if (!productionMode) {
/* Print an information/warning message about running in debug mode */
System.err.println(NOT_PRODUCTION_MODE_INFO);
}
}
/**
* Gets an application property value.
*
* @param parameterName
* the Name or the parameter.
* @return String value or null if not found
*/
protected String getApplicationProperty(String parameterName) {
String val = applicationProperties.getProperty(parameterName);
if (val != null) {
return val;
}
// Try lower case application properties for backward compatibility with
// 3.0.2 and earlier
val = applicationProperties.getProperty(parameterName.toLowerCase());
return val;
}
/**
* Gets an system property value.
*
* @param parameterName
* the Name or the parameter.
* @return String value or null if not found
*/
protected String getSystemProperty(String parameterName) {
String val = null;
String pkgName;
final Package pkg = getClass().getPackage();
if (pkg != null) {
pkgName = pkg.getName();
} else {
final String className = getClass().getName();
pkgName = new String(className.toCharArray(), 0, className
.lastIndexOf('.'));
}
val = System.getProperty(pkgName + "." + parameterName);
if (val != null) {
return val;
}
// Try lowercased system properties
val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
return val;
}
/**
* Gets an application or system property value.
*
* @param parameterName
* the Name or the parameter.
* @param defaultValue
* the Default to be used.
* @return String value or default if not found
*/
private String getApplicationOrSystemProperty(String parameterName,
String defaultValue) {
String val = null;
// Try application properties
val = getApplicationProperty(parameterName);
if (val != null) {
return val;
}
// Try system properties
val = getSystemProperty(parameterName);
if (val != null) {
return val;
}
return defaultValue;
}
/**
* Returns true if the servlet is running in production mode. Production
* mode disables all debug facilities.
*
* @return true if in production mode, false if in debug mode
*/
public boolean isProductionMode() {
return productionMode;
}
/**
* Receives standard HTTP requests from the public service method and
* dispatches them.
*
* @param request
* the object that contains the request the client made of the
* servlet.
* @param response
* the object that contains the response the servlet returns to
* the client.
* @throws ServletException
* if an input or output error occurs while the servlet is
* handling the TRACE request.
* @throws IOException
* if the request for the TRACE cannot be handled.
*/
@SuppressWarnings("unchecked")
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
RequestType requestType = getRequestType(request);
if (requestType == RequestType.STATIC_FILE) {
serveStaticResources(request, response);
return;
}
Application application = null;
boolean transactionStarted = false;
boolean requestStarted = 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(
ApplicationConnection.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;
}
/*
* Get or create a WebApplicationContext and an ApplicationManager
* for the session
*/
WebApplicationContext webApplicationContext = WebApplicationContext
.getApplicationContext(request.getSession());
CommunicationManager applicationManager = webApplicationContext
.getApplicationManager(application, this);
/* Update browser information from the request */
updateBrowserProperties(webApplicationContext.getBrowser(), 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 newly created application
startApplication(request, application, webApplicationContext);
/*
* 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) {
applicationManager.handleFileUpload(request, response);
return;
} else if (requestType == RequestType.UIDL) {
// Handles AJAX UIDL requests
applicationManager.handleUidlRequest(request, response, this);
return;
}
// Removes application if it has stopped (mayby by thread or
// transactionlistener)
if (!application.isRunning()) {
endApplication(request, response, application);
return;
}
// Finds the window within the application
Window window = getApplicationWindow(request, applicationManager,
application);
if (window == null) {
throw new ServletException(ERROR_NO_WINDOW_FOUND);
}
// Sets terminal type for the window, if not already set
if (window.getTerminal() == null) {
window.setTerminal(webApplicationContext.getBrowser());
}
// Handle parameters
final Map parameters = request.getParameterMap();
if (window != null && parameters != null) {
window.handleParameters(parameters);
}
/*
* Call the URI handlers and if this turns out to be a download
* request, send the file to the client
*/
if (handleURI(applicationManager, window, request, response)) {
return;
}
// Send initial AJAX page that kickstarts a Vaadin application
writeAjaxPage(request, response, window, application);
} 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 {
// Notifies transaction end
try {
if (transactionStarted) {
((WebApplicationContext) application.getContext())
.endTransaction(application, request);
}
} finally {
if (requestStarted) {
((HttpServletRequestListener) application).onRequestEnd(
request, response);
}
}
}
}
private void updateBrowserProperties(WebBrowser browser,
HttpServletRequest request) {
browser.updateBrowserProperties(request.getLocale(), request
.getRemoteAddr(), request.isSecure(), request
.getHeader("user-agent"), request.getParameter("sw"), request
.getParameter("sh"));
}
protected ClassLoader getClassLoader() throws ServletException {
// Gets custom class loader
final String classLoaderName = getApplicationOrSystemProperty(
"ClassLoader", null);
ClassLoader classLoader;
if (classLoaderName == null) {
classLoader = getClass().getClassLoader();
} else {
try {
final Class> classLoaderClass = getClass().getClassLoader()
.loadClass(classLoaderName);
final Constructor> c = classLoaderClass
.getConstructor(new Class[] { ClassLoader.class });
classLoader = (ClassLoader) c
.newInstance(new Object[] { getClass().getClassLoader() });
} catch (final Exception e) {
throw new ServletException(
"Could not find specified class loader: "
+ classLoaderName, e);
}
}
return classLoader;
}
/**
* Send notification to client's application. Used to notify client of
* critical errors and session expiration due to long inactivity. Server has
* no knowledge of what application client refers to.
*
* @param request
* the HTTP request instance.
* @param response
* the HTTP response to write to.
* @param caption
* for the notification
* @param message
* for the notification
* @param details
* a detail message to show in addition to the passed message.
* Currently shown directly but could be hidden behind a details
* drop down.
* @param url
* url to load after message, null for current page
* @throws IOException
* if the writing failed due to input/output error.
*/
void criticalNotification(HttpServletRequest request,
HttpServletResponse response, String caption, String message,
String details, String url) throws IOException {
// clients JS app is still running, but server application either
// no longer exists or it might fail to perform reasonably.
// send a notification to client's application and link how
// to "restart" application.
if (caption != null) {
caption = "\"" + caption + "\"";
}
if (details != null) {
if (message == null) {
message = details;
} else {
message += "
" + details;
}
}
if (message != null) {
message = "\"" + message + "\"";
}
if (url != null) {
url = "\"" + url + "\"";
}
// Set the response type
response.setContentType("application/json; charset=UTF-8");
final ServletOutputStream out = response.getOutputStream();
final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(out, "UTF-8")));
outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
+ "\"appError\": {" + "\"caption\":" + caption + ","
+ "\"message\" : " + message + "," + "\"url\" : " + url
+ "}}, \"resources\": {}, \"locales\":[]}]");
outWriter.flush();
outWriter.close();
out.flush();
}
/**
* Returns the application instance to be used for the request. If an
* existing instance is not found a new one is created or null is returned
* to indicate that the application is not available.
*
* @param request
* @param requestType
* @return
* @throws MalformedURLException
* @throws SAXException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ServletException
* @throws 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 writeAjaxPage.
*/
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
e.printStackTrace();
}
}
return resultPath;
}
/**
* Handles the requested URI. An application can add handlers to do special
* processing, when a certain URI is requested. The handlers are invoked
* before any windows URIs are processed and if a DownloadStream is returned
* it is sent to the client.
*
* @param stream
* the download stream.
*
* @param request
* the HTTP request instance.
* @param response
* the HTTP response to write to.
* @throws IOException
*
* @see com.vaadin.terminal.URIHandler
*/
@SuppressWarnings("unchecked")
private void handleDownload(DownloadStream stream,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (stream.getParameter("Location") != null) {
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.addHeader("Location", stream.getParameter("Location"));
return;
}
// Download from given stream
final InputStream data = stream.getStream();
if (data != null) {
// Sets content type
response.setContentType(stream.getContentType());
// Sets cache headers
final long cacheTime = stream.getCacheTime();
if (cacheTime <= 0) {
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
} else {
response.setHeader("Cache-Control", "max-age=" + cacheTime
/ 1000);
response.setDateHeader("Expires", System.currentTimeMillis()
+ cacheTime);
response.setHeader("Pragma", "cache"); // Required to apply
// caching in some
// Tomcats
}
// Copy download stream parameters directly
// to HTTP headers.
final Iterator i = stream.getParameterNames();
if (i != null) {
while (i.hasNext()) {
final String param = (String) i.next();
response.setHeader(param, stream.getParameter(param));
}
}
// suggest local filename from DownloadStream if Content-Disposition
// not explicitly set
String contentDispositionValue = stream
.getParameter("Content-Disposition");
if (contentDispositionValue == null) {
contentDispositionValue = "filename=\"" + stream.getFileName()
+ "\"";
response.setHeader("Content-Disposition",
contentDispositionValue);
}
int bufferSize = stream.getBufferSize();
if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) {
bufferSize = DEFAULT_BUFFER_SIZE;
}
final byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
final OutputStream out = response.getOutputStream();
while ((bytesRead = data.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
out.flush();
}
out.close();
data.close();
}
}
/**
* 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 WebApplicationContext context = WebApplicationContext
.getApplicationContext(request.getSession());
context.addApplication(newApplication);
return newApplication;
}
private void handleServiceException(HttpServletRequest request,
HttpServletResponse response, Application application, Throwable e)
throws IOException, ServletException {
// if this was an UIDL request, response UIDL back to client
if (getRequestType(request) == RequestType.UIDL) {
Application.SystemMessages ci = getSystemMessages();
criticalNotification(request, response, ci
.getInternalErrorCaption(), ci.getInternalErrorMessage(),
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);
}
}
/**
* Returns the theme for given request/window
*
* @param request
* @param window
* @return
*/
private String getThemeForWindow(HttpServletRequest request, Window window) {
// Finds theme name
String themeName;
if (request.getParameter(URL_PARAMETER_THEME) != null) {
themeName = request.getParameter(URL_PARAMETER_THEME);
} else {
themeName = window.getTheme();
}
if (themeName == null) {
// no explicit theme for window defined
if (request.getAttribute(REQUEST_DEFAULT_THEME) != null) {
// the default theme is defined in request (by portal)
themeName = (String) request
.getAttribute(REQUEST_DEFAULT_THEME);
} else {
// using the default theme defined by Vaadin
themeName = getDefaultTheme();
}
}
return themeName;
}
/**
* Returns the default theme. Must never return null.
*
* @return
*/
public static String getDefaultTheme() {
return DEFAULT_THEME_NAME;
}
/**
* Calls URI handlers for the request. If an URI handler returns a
* DownloadStream the stream is passed to the client for downloading.
*
* @param applicationManager
* @param window
* @param request
* @param response
* @return true if an DownloadStream was sent to the client, false otherwise
* @throws IOException
*/
protected boolean handleURI(CommunicationManager applicationManager,
Window window, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// Handles the URI
DownloadStream download = applicationManager.handleURI(window, request,
response, this);
// A download request
if (download != null) {
// Client downloads an resource
handleDownload(download, request, response);
return true;
}
return false;
}
void handleServiceSessionExpired(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (isOnUnloadRequest(request)) {
/*
* Request was an unload request (e.g. window close event) and the
* client expects no response if it fails.
*/
return;
}
try {
Application.SystemMessages ci = getSystemMessages();
if (getRequestType(request) != RequestType.UIDL) {
// 'plain' http req - e.g. browser reload;
// just go ahead redirect the browser
response.sendRedirect(ci.getSessionExpiredURL());
} else {
/*
* 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(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (isOnUnloadRequest(request)) {
/*
* Request was an unload request (e.g. window close event) and the
* client expects no response if it fails.
*/
return;
}
try {
Application.SystemMessages ci = getSystemMessages();
if (getRequestType(request) != RequestType.UIDL) {
// 'plain' http req - e.g. browser reload;
// just go ahead redirect the browser
response.sendRedirect(ci.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, WebApplicationContext 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(applicationUrl, applicationProperties,
webApplicationContext);
}
}
/**
* Check if this is a request for a static resource and, if it is, serve the
* resource to the client. Returns true if a file was served and the request
* has been handled, false otherwise.
*
* @param request
* @param response
* @return
* @throws IOException
* @throws ServletException
*/
private boolean serveStaticResources(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// FIXME What does 10 refer to?
String pathInfo = request.getPathInfo();
if (pathInfo == null || pathInfo.length() <= 10) {
return false;
}
if ((request.getContextPath() != null)
&& (request.getRequestURI().startsWith("/VAADIN/"))) {
serveStaticResourcesInVAADIN(request.getRequestURI(), response);
return true;
} else if (request.getRequestURI().startsWith(
request.getContextPath() + "/VAADIN/")) {
serveStaticResourcesInVAADIN(request.getRequestURI().substring(
request.getContextPath().length()), response);
return true;
}
return false;
}
/**
* Serve resources from VAADIN directory.
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void serveStaticResourcesInVAADIN(String filename,
HttpServletResponse response) throws IOException, ServletException {
final ServletContext sc = getServletContext();
InputStream is = sc.getResourceAsStream(filename);
if (is == null) {
// try if requested file is found from classloader
// strip leading "/" otherwise stream from JAR wont work
filename = filename.substring(1);
is = getClassLoader().getResourceAsStream(filename);
if (is == null) {
// cannot serve requested file
System.err
.println("Requested resource ["
+ filename
+ "] not found from filesystem or through class loader."
+ " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.");
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
}
final String mimetype = sc.getMimeType(filename);
if (mimetype != null) {
response.setContentType(mimetype);
}
final OutputStream os = response.getOutputStream();
final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
int bytes;
while ((bytes = is.read(buffer)) >= 0) {
os.write(buffer, 0, bytes);
}
}
enum RequestType {
FILE_UPLOAD, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE;
}
protected RequestType getRequestType(HttpServletRequest request) {
if (isFileUploadRequest(request)) {
return RequestType.FILE_UPLOAD;
} else if (isUIDLRequest(request)) {
return RequestType.UIDL;
} else if (isStaticResourceRequest(request)) {
return RequestType.STATIC_FILE;
} else if (isApplicationRequest(request)) {
return RequestType.APPLICATION_RESOURCE;
}
return RequestType.OTHER;
}
private boolean isApplicationRequest(HttpServletRequest request) {
String path = getRequestPathInfo(request);
if (path != null && path.startsWith("/APP/")) {
return true;
}
return false;
}
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 isUIDLRequest(HttpServletRequest request) {
String pathInfo = getRequestPathInfo(request);
if (pathInfo == null) {
return false;
}
String compare = AJAX_UIDL_URI;
if (pathInfo.startsWith(compare + "/") || pathInfo.endsWith(compare)) {
return true;
}
return false;
}
private boolean isFileUploadRequest(HttpServletRequest request) {
return ServletFileUpload.isMultipartContent(request);
}
private boolean isOnUnloadRequest(HttpServletRequest request) {
return request.getParameter(ApplicationConnection.PARAM_UNLOADBURST) != null;
}
/**
* Get system messages from the current application class
*
* @return
*/
protected SystemMessages getSystemMessages() {
try {
Class extends Application> appCls = getApplicationClass();
Method m = appCls.getMethod("getSystemMessages", (Class[]) null);
return (Application.SystemMessages) m.invoke(null, (Object[]) null);
} catch (ClassNotFoundException e) {
// This should never happen
throw new SystemMessageException(e);
} catch (SecurityException e) {
throw new SystemMessageException(
"Application.getSystemMessage() should be static public", e);
} catch (NoSuchMethodException e) {
// This is completely ok and should be silently ignored
} catch (IllegalArgumentException e) {
// This should never happen
throw new SystemMessageException(e);
} catch (IllegalAccessException e) {
throw new SystemMessageException(
"Application.getSystemMessage() should be static public", e);
} catch (InvocationTargetException e) {
// This should never happen
throw new SystemMessageException(e);
}
return Application.getSystemMessages();
}
protected abstract Class extends Application> getApplicationClass()
throws ClassNotFoundException;
/**
* 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) {
// request may have an attribute explicitly telling location (portal
// case)
String staticFileLocation = (String) request
.getAttribute(REQUEST_VAADIN_STATIC_FILE_PATH);
if (staticFileLocation != null) {
// TODO remove trailing slash if any?
return staticFileLocation;
}
return getWebApplicationsStaticFileLocation(request);
}
/**
* The default method to fetch static files location. 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 = 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));
}
/**
* This method writes the html host page (aka kickstart page) that starts
* the actual Vaadin application.
*
* If one needs to override parts of the host page, it is suggested that one * overrides on of several submethods which are called by this method: *