From dde20e758bea4aac22f73d88313c615bf00b0c59 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Thu, 13 Aug 2009 11:15:49 +0000 Subject: [PATCH] Lots of servlet and portlet related fixes and enhancements. Most notable changes * fixes #3191 (writeAjaxPage not protected and split into submethods) * fixes issues with #3113 * cleaned code (variable renaming, method signature changes) svn changeset:8478/svn branch:6.1 --- .../server/AbstractApplicationServlet.java | 498 ++++++++++++------ .../gwt/server/ApplicationPortlet.java | 39 +- .../gwt/server/WebApplicationContext.java | 2 +- 3 files changed, 360 insertions(+), 179 deletions(-) diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index f952e78f94..3ae6504ed1 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -102,7 +102,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * This request attribute forces widgetsets to be loaded from under the * specified base path; e.g shared widgetset for all portlets in a portal. */ - public static final String REQUEST_VAADIN_WIDGETSET_PATH = ApplicationServlet.class + public static final String REQUEST_VAADIN_STATIC_FILE_PATH = ApplicationServlet.class .getName() + ".widgetsetPath"; /** @@ -119,7 +119,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * The default theme should be located in * REQUEST_DEFAULT_THEME_URI/THEME_DIRECTORY_PATH/getDefaultTheme() . */ - public static final String REQUEST_DEFAULT_THEME_URI = ApplicationServlet.class + public static final String REQUEST_DEFAULT_THEME = ApplicationServlet.class .getName() + ".defaultThemeUri"; /** @@ -133,9 +133,17 @@ public abstract class AbstractApplicationServlet extends HttpServlet { private Properties applicationProperties; - private static final String NOT_PRODUCTION_MODE_INFO = "=================================================================\nVaadin is running in DEBUG MODE.\nAdd productionMode=true to web.xml to disable debug features.\nTo show debug window, add ?debug to your application URL.\n================================================================="; + private static final String NOT_PRODUCTION_MODE_INFO = "" + + "=================================================================\n" + + "Vaadin is running in DEBUG MODE.\nAdd productionMode=true to web.xml " + + "to disable debug features.\nTo show debug window, add ?debug to " + + "your application URL.\n" + + "================================================================="; - private static final String WARNING_XSRF_PROTECTION_DISABLED = "===========================================================\nWARNING: Cross-site request forgery protection is disabled!\n==========================================================="; + private static final String WARNING_XSRF_PROTECTION_DISABLED = "" + + "===========================================================\n" + + "WARNING: Cross-site request forgery protection is disabled!\n" + + "==========================================================="; private boolean productionMode = false; @@ -190,6 +198,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * if an exception has occurred that interferes with the * servlet's normal operation. */ + @SuppressWarnings("unchecked") @Override public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { @@ -353,6 +362,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * @throws IOException * if the request for the TRACE cannot be handled. */ + @SuppressWarnings("unchecked") @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -420,9 +430,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet { return; } - // Removes application if it has stopped + // Removes application if it has stopped (by thread, + // transactionlistener) if (!application.isRunning()) { - // FIXME How can this be reached? endApplication(request, response, application); return; } @@ -452,23 +462,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet { return; } - String defaultThemeUri = null; - Object reqObj = request.getAttribute(REQUEST_DEFAULT_THEME_URI); - if (reqObj instanceof String) { - defaultThemeUri = (String) reqObj; - } - - String themeName = getThemeForWindow(request, window, - defaultThemeUri != null); - // Handles theme resource requests - if (handleResourceRequest(request, response, themeName)) { + if (handleResourceRequest(request, response, window)) { return; } // Send initial AJAX page that kickstarts a Vaadin application - writeAjaxPage(request, response, window, themeName, - defaultThemeUri, application); + writeAjaxPage(request, response, window, application); } catch (final SessionExpired e) { // Session has expired, notify user @@ -720,6 +720,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * * @see com.vaadin.terminal.URIHandler */ + @SuppressWarnings("unchecked") private void handleDownload(DownloadStream stream, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -842,18 +843,33 @@ public abstract class AbstractApplicationServlet extends HttpServlet { } - private String getThemeForWindow(HttpServletRequest request, Window window, - boolean ignoreDefaultTheme) { + /** + * Returns the theme for given request/window + * + * @param request + * @param window + * @return + */ + private String getThemeForWindow(HttpServletRequest request, Window window) { // Finds theme name - String themeName = window.getTheme(); + String themeName; + if (request.getParameter(URL_PARAMETER_THEME) != null) { themeName = request.getParameter(URL_PARAMETER_THEME); + } else { + themeName = window.getTheme(); } - if (!ignoreDefaultTheme && themeName == null) { - themeName = getDefaultTheme(); + 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) + return (String) request.getAttribute(REQUEST_DEFAULT_THEME); + } else { + // using the default theme defined by Vaadin + themeName = getDefaultTheme(); + } } - return themeName; } @@ -1064,7 +1080,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { + 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(404); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } } @@ -1073,7 +1089,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { response.setContentType(mimetype); } final OutputStream os = response.getOutputStream(); - final byte buffer[] = new byte[20000]; + final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; int bytes; while ((bytes = is.read(buffer)) >= 0) { os.write(buffer, 0, bytes); @@ -1125,7 +1141,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { */ protected SystemMessages getSystemMessages() { try { - Class appCls = getApplicationClass(); + Class appCls = getApplicationClass(); Method m = appCls.getMethod("getSystemMessages", (Class[]) null); return (Application.SystemMessages) m.invoke(null, (Object[]) null); } catch (ClassNotFoundException e) { @@ -1149,7 +1165,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { return Application.getSystemMessages(); } - protected abstract Class getApplicationClass() + protected abstract Class getApplicationClass() throws ClassNotFoundException; /** @@ -1166,6 +1182,38 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * @throws MalformedURLException */ 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) { + 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(); @@ -1225,6 +1273,22 @@ public abstract class AbstractApplicationServlet extends HttpServlet { } /** + * 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: + *

* * @param request * the HTTP request. @@ -1241,48 +1305,24 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * if the application is denied access the persistent data store * represented by the given URL. */ - private void writeAjaxPage(HttpServletRequest request, - HttpServletResponse response, Window window, String themeName, - String defaultThemeUri, Application application) + protected void writeAjaxPage(HttpServletRequest request, + HttpServletResponse response, Window window, Application application) throws IOException, MalformedURLException, ServletException { // e.g portlets only want a html fragment boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null); if (fragment) { + // if this is a fragment request, the actual application is put to + // request so ApplicationPortlet can save it for a later use request.setAttribute(Application.class.getName(), application); } final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( response.getOutputStream())); - String pathInfo = getRequestPathInfo(request); - if (pathInfo == null) { - pathInfo = "/"; - } String title = ((window.getCaption() == null) ? "Vaadin 6" : window .getCaption()); - String widgetset = null; - // request widgetset takes precedence (e.g portlet include) - Object reqParam = request.getAttribute(REQUEST_WIDGETSET); - try { - widgetset = (String) reqParam; - } catch (Exception e) { - // FIXME: Handle exception - System.err.println("Warning: request param '" + REQUEST_WIDGETSET - + "' could not be used (is not a String)" + e); - } - - // TODO: Any reason this could not use - // getApplicationOrSystemProperty with DEFAULT_WIDGETSET as default - // value ? - if (widgetset == null) { - widgetset = getApplicationProperty(PARAMETER_WIDGETSET); - } - if (widgetset == null) { - widgetset = DEFAULT_WIDGETSET; - } - /* Fetch relative url to application */ // don't use server and port in uri. It may cause problems with some // virtual server configurations which lose the server name @@ -1291,60 +1331,15 @@ public abstract class AbstractApplicationServlet extends HttpServlet { appUrl = appUrl.substring(0, appUrl.length() - 1); } - final String staticFilesLocation = getStaticFilesLocation(request); + String themeName = getThemeForWindow(request, window); - final String staticFilePath = getApplicationOrSystemProperty( - PARAMETER_VAADIN_RESOURCES, staticFilesLocation); - - reqParam = request.getAttribute(REQUEST_VAADIN_WIDGETSET_PATH); - final String widgetsetFilePath = reqParam instanceof String ? (String) reqParam - : staticFilePath; - - // Default theme does not use theme URI - String themeBaseUri; - String themeUri; - if (themeName != null) { - // Using custom theme - themeBaseUri = staticFilePath; - themeUri = themeBaseUri + "/" + THEME_DIRECTORY_PATH + themeName; - } else { - themeBaseUri = defaultThemeUri; - themeUri = themeBaseUri + "/" + THEME_DIRECTORY_PATH - + getDefaultTheme(); - } + String themeUri = getThemeUri(themeName, request); if (!fragment) { - // Window renders are not cacheable - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - response.setContentType("text/html; charset=UTF-8"); - - // write html header - page.write("\n"); - - page.write("\n\n"); - page - .write("\n"); - page.write(""); - - // Add favicon links - page - .write(""); - page - .write(""); - - page.write("" + title + ""); - - page - .write("\n\n\n"); + writeAjaxPageHeaders(response); + writeAjaxPageHtmlHeadStart(page); + writeAjaxPageHtmlHeader(page, title, themeUri); + writeAjaxPageHtmlBodyStart(page); } String appId = appUrl; @@ -1360,6 +1355,120 @@ public abstract class AbstractApplicationServlet extends HttpServlet { } appId = appId + "-" + hashCode; + writeAjaxPageHtmlVaadinScripts(window, themeName, application, page, + appUrl, themeUri, appId, request); + + /*- Add classnames; + * .v-app + * .v-app-loading + * .v-app- + * .v-theme- + */ + + String appClass = "v-app-"; + try { + appClass += getApplicationClass().getSimpleName(); + } catch (ClassNotFoundException e) { + appClass += "unknown"; + e.printStackTrace(); + } + String themeClass = ""; + if (themeName != null) { + themeClass = "v-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", ""); + } else { + themeClass = "v-theme-" + + getDefaultTheme().replaceAll("[^a-zA-Z0-9]", ""); + } + + String classNames = "v-app v-app-loading " + themeClass + " " + + appClass; + + String divStyle = null; + if (request.getAttribute(REQUEST_APPSTYLE) != null) { + divStyle = "style=\"" + request.getAttribute(REQUEST_APPSTYLE) + + "\""; + } + + writeAjaxPageHtmlMainDiv(page, appId, classNames, divStyle); + + if (!fragment) { + writeAjaxPageHtmlBodyEnd(page); + } + + page.close(); + + } + + private String getThemeUri(String themeName, HttpServletRequest request) { + final String staticFilePath; + if (themeName.equals(request.getAttribute(REQUEST_DEFAULT_THEME))) { + // our window theme is the portal wide default theme, make it load + // from portals directory is defined + staticFilePath = getStaticFilesLocation(request); + } else { + /* + * theme is a custom theme, which does not necessary locate in + * portals VAADIN directory. Let default servlet conf decide + * (omitting request parameter) the location. Note that theme can + * still be placed to portal directory with servlet parameter. + */ + staticFilePath = getWebApplicationsStaticFileLocation(request); + } + return staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName; + } + + /** + * Method to write the div element into which that actual Vaadin application + * is rendered. + *

+ * Override this method if you want to add some custom html around around + * the div element into which the actual vaadin application will be + * rendered. + * + * @param page + * @param appId + * @param classNames + * @param divStyle + * @throws IOException + */ + protected void writeAjaxPageHtmlMainDiv(final BufferedWriter page, + String appId, String classNames, String divStyle) + throws IOException { + page.write("

\n"); + } + + /** + * + * * Method to write the script part of the page which loads needed vaadin + * scripts and themes. + *

+ * Override this method if you want to add some custom html around scripts. + * + * @param window + * @param themeName + * @param application + * @param page + * @param appUrl + * @param themeUri + * @param appId + * @param request + * @throws ServletException + * @throws IOException + */ + protected void writeAjaxPageHtmlVaadinScripts(Window window, + String themeName, Application application, + final BufferedWriter page, String appUrl, String themeUri, + String appId, HttpServletRequest request) throws ServletException, + IOException { + + String widgetset = getWidgetSet(request); + + final String staticFilePath = getStaticFilesLocation(request); + final String widgetsetFilePath = staticFilePath + "/" + + WIDGETSET_DIRECTORY_PATH + widgetset + "/" + widgetset + + ".nocache.js?" + new Date().getTime(); + // Get system messages Application.SystemMessages systemMessages = null; try { @@ -1383,12 +1492,16 @@ public abstract class AbstractApplicationServlet extends HttpServlet { + "style=\"width:0;height:0;border:0;overflow:" + "hidden\" src=\"javascript:false\">');\n"); page.write("document.write(\"\n"); + page + .write("setTimeout('if (typeof " + + widgetset.replace('.', '_') + + " == \"undefined\") {alert(\"Failed to load the widgetset: " + + widgetsetFilePath + "\")};',15000);\n" + + "//]]>\n\n"); + } - String style = null; - reqParam = request.getAttribute(REQUEST_APPSTYLE); - if (reqParam != null) { - style = "style=\"" + reqParam + "\""; + private String getWidgetSet(HttpServletRequest request) { + // request widgetset takes precedence (e.g portlet include) + String widgetset = (String) request.getAttribute(REQUEST_WIDGETSET); + if (widgetset == null) { + // use the value from configuration or DEFAULT_WIDGETSET + widgetset = getApplicationOrSystemProperty(PARAMETER_WIDGETSET, + DEFAULT_WIDGETSET); } - /*- Add classnames; - * .v-app - * .v-app-loading - * .v-app- - * .v-theme- - */ - String appClass = "v-app-"; - try { - appClass += getApplicationClass().getSimpleName(); - } catch (ClassNotFoundException e) { - appClass += "unknown"; + return widgetset; + } - e.printStackTrace(); - } - String themeClass = ""; - if (themeName != null) { - themeClass = "v-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", ""); - } else { - themeClass = "v-theme-" - + getDefaultTheme().replaceAll("[^a-zA-Z0-9]", ""); - } + /** + * Method to write the end of the html kickstart page. + *

+ * This method is responsible for closing body and html tags. + *

+ * Override this method if you want to add some custom html to the end of + * the page. + * + * @param page + * @throws IOException + */ + protected void writeAjaxPageHtmlBodyEnd(final BufferedWriter page) + throws IOException { + page.write(""); + page.write("\n\n"); + } - page.write("

\n"); + /** + * + * Method to open the body tag of the html kickstart page. + *

+ * This method is responsible for closing the head tag and opening the body + * tag. + *

+ * Override this method if you want to add some custom html to the page. + * + * @param page + * @throws IOException + */ + protected void writeAjaxPageHtmlBodyStart(final BufferedWriter page) + throws IOException { + page + .write("\n\n\n"); + } - if (!fragment) { - page.write(""); - page.write("\n\n"); - } - page.close(); + /** + * Method to write the contents of head element in html kickstart page. + *

+ * Override this method if you want to add some custom html to the header of + * the page. + * + * @param page + * @param title + * @param themeUri + * @throws IOException + */ + protected void writeAjaxPageHtmlHeader(final BufferedWriter page, + String title, String themeUri) throws IOException { + page + .write("\n"); + page.write(""); + // Add favicon links + page + .write(""); + page + .write(""); + + page.write("" + title + ""); + } + + /** + * Method to write the beginning of the html page. + *

+ * This method is responsible for writing appropriate doc type declarations + * and to open html and head tags. + *

+ * Override this method if you want to add some custom html to the very + * beginning of the page. + * + * @param page + * @throws IOException + */ + protected void writeAjaxPageHtmlHeadStart(final BufferedWriter page) + throws IOException { + // write html header + page.write("\n"); + + page.write("\n\n"); + } + + /** + * Method to write http request headers for the Vaadin kickstart page. + *

+ * Override this method if you need to customize http headers of the page. + * + * @param response + */ + protected void writeAjaxPageHeaders(HttpServletResponse response) { + // Window renders are not cacheable + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setContentType("text/html; charset=UTF-8"); } /** @@ -1512,7 +1702,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * servlet's normal operation. */ private boolean handleResourceRequest(HttpServletRequest request, - HttpServletResponse response, String themeName) + HttpServletResponse response, Window window) throws ServletException { // If the resource path is unassigned, initialize it if (resourcePath == null) { @@ -1529,17 +1719,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { return false; } - if (themeName == null) { - try { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } catch (final java.io.IOException e) { - // FIXME: Handle exception - System.err.println("Returning error code failed: " - + request.getRequestURI() + ". (" + e.getMessage() - + ")"); - } - return true; - } + String themeName = getThemeForWindow(request, window); // Checks the resource type resourceId = resourceId.substring(RESOURCE_URI.length()); diff --git a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java index 51e148a76c..24880d04ad 100644 --- a/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/ApplicationPortlet.java @@ -110,6 +110,15 @@ public class ApplicationPortlet implements Portlet, Serializable { portalResourcePath = "/html"; } + if (portalResourcePath != null) { + // if portalResourcePath is defined, set it as a request + // parameter which will override the default location in + // servlet + request.setAttribute( + ApplicationServlet.REQUEST_VAADIN_STATIC_FILE_PATH, + portalResourcePath); + } + // - if the user has specified a widgetset for this portlet, use // it from the portlet (not fully supported) // - otherwise, if specified, use the portal-wide widgetset @@ -117,13 +126,6 @@ public class ApplicationPortlet implements Portlet, Serializable { // - finally, default to use the default widgetset if nothing // else is found if (portalWidgetset != null) { - if (portalResourcePath != null) { - request - .setAttribute( - ApplicationServlet.REQUEST_VAADIN_WIDGETSET_PATH, - portalResourcePath); - } - request.setAttribute(ApplicationServlet.REQUEST_WIDGETSET, portalWidgetset); } else if (portletWidgetset != null) { @@ -136,21 +138,20 @@ public class ApplicationPortlet implements Portlet, Serializable { style); } - String themeUri = null; if (portalTheme != null) { - themeUri = portalResourcePath + "/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH - + portalTheme; request.setAttribute( - ApplicationServlet.REQUEST_DEFAULT_THEME_URI, + ApplicationServlet.REQUEST_DEFAULT_THEME, portalTheme); - } - /* - * Make sure portal default Vaadin theme is included exactly - * once in DOM. - */ - if (portalTheme != null) { + String defaultThemeUri = null; + defaultThemeUri = portalResourcePath + "/" + + AbstractApplicationServlet.THEME_DIRECTORY_PATH + + portalTheme; + /* + * Make sure portal default Vaadin theme is included in DOM. + * Vaadin portlet themes do not "inherit" base theme, so we + * need to force loading of the common base theme. + */ OutputStream out = response.getPortletOutputStream(); // Using portal-wide theme @@ -164,7 +165,7 @@ public class ApplicationPortlet implements Portlet, Serializable { + "stylesheet.setAttribute('rel', 'stylesheet');\n" + "stylesheet.setAttribute('type', 'text/css');\n" + "stylesheet.setAttribute('href', '" - + themeUri + + defaultThemeUri + "/styles.css');\n" + "document.getElementsByTagName('head')[0].appendChild(stylesheet);\n" + "vaadin.themesLoaded['" diff --git a/src/com/vaadin/terminal/gwt/server/WebApplicationContext.java b/src/com/vaadin/terminal/gwt/server/WebApplicationContext.java index 43c6ad2565..246de4e392 100644 --- a/src/com/vaadin/terminal/gwt/server/WebApplicationContext.java +++ b/src/com/vaadin/terminal/gwt/server/WebApplicationContext.java @@ -82,7 +82,7 @@ public class WebApplicationContext implements ApplicationContext, * * @see com.vaadin.service.ApplicationContext#getApplications() */ - public Collection getApplications() { + public Collection getApplications() { return Collections.unmodifiableCollection(applications); } -- 2.39.5