/* @VaadinApache2LicenseForJavaFiles@ */ package com.vaadin.terminal.gwt.server; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; import java.util.Map; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; import com.vaadin.RootRequiresMoreInformationException; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.ui.Root; public abstract class BootstrapHandler implements RequestHandler { protected class BootstrapContext implements Serializable { private final WrappedResponse response; private final WrappedRequest request; private final Application application; private final Integer rootId; private Writer writer; private Root root; private String widgetsetName; private String themeName; private String appId; private boolean rootFetched = false; public BootstrapContext(WrappedResponse response, WrappedRequest request, Application application, Integer rootId) { this.response = response; this.request = request; this.application = application; this.rootId = rootId; } public WrappedResponse getResponse() { return response; } public WrappedRequest getRequest() { return request; } public Application getApplication() { return application; } public Writer getWriter() throws IOException { if (writer == null) { response.setContentType("text/html"); writer = new BufferedWriter(new OutputStreamWriter( response.getOutputStream(), "UTF-8")); } return writer; } public Integer getRootId() { return rootId; } public Root getRoot() { if (!rootFetched) { root = Root.getCurrentRoot(); rootFetched = true; } return root; } public String getWidgetsetName() { if (widgetsetName == null) { Root root = getRoot(); if (root != null) { widgetsetName = getWidgetsetForRoot(this); } } return widgetsetName; } public String getThemeName() { if (themeName == null) { Root root = getRoot(); if (root != null) { themeName = findAndEscapeThemeName(this); } } return themeName; } public String getAppId() { if (appId == null) { appId = getApplicationId(this); } return appId; } } public boolean handleRequest(Application application, WrappedRequest request, WrappedResponse response) throws IOException { // TODO Should all urls be handled here? Integer rootId = null; try { Root root = application.getRootForRequest(request); if (root == null) { writeError(response, new Throwable("No Root found")); return true; } rootId = Integer.valueOf(root.getRootId()); } catch (RootRequiresMoreInformationException e) { // Just keep going without rootId } try { writeBootstrapPage(request, response, application, rootId); } catch (JSONException e) { writeError(response, e); } return true; } protected final void writeBootstrapPage(WrappedRequest request, WrappedResponse response, Application application, Integer rootId) throws IOException, JSONException { BootstrapContext context = createContext(request, response, application, rootId); DeploymentConfiguration deploymentConfiguration = request .getDeploymentConfiguration(); boolean standalone = deploymentConfiguration.isStandalone(request); if (standalone) { setBootstrapPageHeaders(context); writeBootstrapPageHtmlHeadStart(context); writeBootstrapPageHtmlHeader(context); writeBootstrapPageHtmlBodyStart(context); } // TODO include initial UIDL in the scripts? writeBootstrapPageHtmlVaadinScripts(context); writeBootstrapPageHtmlMainDiv(context); Writer page = context.getWriter(); if (standalone) { page.write("\n\n"); } page.close(); } public BootstrapContext createContext(WrappedRequest request, WrappedResponse response, Application application, Integer rootId) { BootstrapContext context = new BootstrapContext(response, request, application, rootId); return context; } protected String getMainDivStyle(BootstrapContext context) { return null; } /** * Creates and returns a unique ID for the DIV where the application is to * be rendered. * * @param context * * @return the id to use in the DOM */ protected abstract String getApplicationId(BootstrapContext context); public String getWidgetsetForRoot(BootstrapContext context) { Root root = context.getRoot(); WrappedRequest request = context.getRequest(); String widgetset = root.getApplication().getWidgetsetForRoot(root); if (widgetset == null) { widgetset = request.getDeploymentConfiguration() .getConfiguredWidgetset(request); } widgetset = AbstractApplicationServlet.stripSpecialChars(widgetset); return widgetset; } /** * 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 context * * @throws IOException */ protected void writeBootstrapPageHtmlMainDiv(BootstrapContext context) throws IOException { Writer page = context.getWriter(); String style = getMainDivStyle(context); /*- Add classnames; * .v-app * .v-app-loading * .v-app- *- Additionally added from javascript: * .v-theme- */ String appClass = "v-app-" + getApplicationCSSClassName(context.getApplication()); String classNames = "v-app " + appClass; if (style != null && style.length() != 0) { style = " style=\"" + style + "\""; } page.write("

"); page.write("
"); page.write("
\n"); page.write(""); } /** * Returns a message printed for browsers without scripting support or if * browsers scripting support is disabled. */ protected String getNoScriptMessage() { return "You have to enable javascript in your browser to use an application built with Vaadin."; } /** * Returns the application class identifier for use in the application CSS * class name in the root DIV. The application CSS class name is of form * "v-app-"+getApplicationCSSClassName(). * * This method should normally not be overridden. * * @return The CSS class name to use in combination with "v-app-". */ protected String getApplicationCSSClassName(Application application) { return application.getClass().getSimpleName(); } /** * * 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. * * @throws IOException */ protected void writeBootstrapPageHtmlBodyStart(BootstrapContext context) throws IOException { Writer page = context.getWriter(); page.write("\n\n\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 context * * @throws IOException * @throws JSONException */ protected void writeBootstrapPageHtmlVaadinScripts(BootstrapContext context) throws IOException, JSONException { WrappedRequest request = context.getRequest(); Writer page = context.getWriter(); DeploymentConfiguration deploymentConfiguration = request .getDeploymentConfiguration(); String staticFileLocation = deploymentConfiguration .getStaticFileLocation(request); page.write(""); String bootstrapLocation = staticFileLocation + "/VAADIN/vaadinBootstrap.js"; page.write("\n"); page.write("\n"); } protected void writeMainScriptTagContents(BootstrapContext context) throws JSONException, IOException { JSONObject defaults = getDefaultParameters(context); JSONObject appConfig = getApplicationParameters(context); boolean isDebug = !context.getApplication().isProductionMode(); Writer page = context.getWriter(); page.write("vaadin.setDefaults("); printJsonObject(page, defaults, isDebug); page.write(");\n"); page.write("vaadin.initApplication(\""); page.write(context.getAppId()); page.write("\","); printJsonObject(page, appConfig, isDebug); page.write(");\n"); } private static void printJsonObject(Writer page, JSONObject jsonObject, boolean isDebug) throws IOException, JSONException { if (isDebug) { page.write(jsonObject.toString(4)); } else { page.write(jsonObject.toString()); } } protected JSONObject getApplicationParameters(BootstrapContext context) throws JSONException, PaintException { Application application = context.getApplication(); Integer rootId = context.getRootId(); JSONObject appConfig = new JSONObject(); if (rootId != null) { appConfig.put(ApplicationConnection.ROOT_ID_PARAMETER, rootId); } if (context.getThemeName() != null) { appConfig.put("themeUri", getThemeUri(context, context.getThemeName())); } JSONObject versionInfo = new JSONObject(); versionInfo.put("vaadinVersion", AbstractApplicationServlet.VERSION); versionInfo.put("applicationVersion", application.getVersion()); appConfig.put("versionInfo", versionInfo); appConfig.put("widgetset", context.getWidgetsetName()); if (rootId == null || application.isRootInitPending(rootId.intValue())) { appConfig.put("initialPath", context.getRequest() .getRequestPathInfo()); Map parameterMap = context.getRequest() .getParameterMap(); appConfig.put("initialParams", parameterMap); } else { // write the initial UIDL into the config appConfig.put("uidl", getInitialUIDL(context.getRequest(), context.getRoot())); } return appConfig; } protected JSONObject getDefaultParameters(BootstrapContext context) throws JSONException { JSONObject defaults = new JSONObject(); WrappedRequest request = context.getRequest(); Application application = context.getApplication(); // Get system messages Application.SystemMessages systemMessages = AbstractApplicationServlet .getSystemMessages(application.getClass()); if (systemMessages != null) { // Write the CommunicationError -message to client JSONObject comErrMsg = new JSONObject(); comErrMsg.put("caption", systemMessages.getCommunicationErrorCaption()); comErrMsg.put("message", systemMessages.getCommunicationErrorMessage()); comErrMsg.put("url", systemMessages.getCommunicationErrorURL()); defaults.put("comErrMsg", comErrMsg); JSONObject authErrMsg = new JSONObject(); authErrMsg.put("caption", systemMessages.getAuthenticationErrorCaption()); authErrMsg.put("message", systemMessages.getAuthenticationErrorMessage()); authErrMsg.put("url", systemMessages.getAuthenticationErrorURL()); defaults.put("authErrMsg", authErrMsg); } DeploymentConfiguration deploymentConfiguration = request .getDeploymentConfiguration(); String staticFileLocation = deploymentConfiguration .getStaticFileLocation(request); String widgetsetBase = staticFileLocation + "/" + AbstractApplicationServlet.WIDGETSET_DIRECTORY_PATH; defaults.put("widgetsetBase", widgetsetBase); if (!application.isProductionMode()) { defaults.put("debug", true); } if (deploymentConfiguration.isStandalone(request)) { defaults.put("standalone", true); } defaults.put("appUri", getAppUri(context)); return defaults; } protected abstract String getAppUri(BootstrapContext context); /** * 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. * * @throws IOException */ protected void writeBootstrapPageHtmlHeader(BootstrapContext context) throws IOException { Writer page = context.getWriter(); String themeName = context.getThemeName(); page.write("\n"); // Chrome frame in all versions of IE (only if Chrome frame is // installed) page.write("\n"); page.write(""); // Add favicon links if (themeName != null) { String themeUri = getThemeUri(context, themeName); page.write(""); page.write(""); } Root root = context.getRoot(); String title = ((root == null || root.getCaption() == null) ? "Vaadin " + AbstractApplicationServlet.VERSION_MAJOR : root.getCaption()); page.write("" + AbstractApplicationServlet.safeEscapeForHtml(title) + ""); } /** * Method to set http request headers for the Vaadin kickstart page. *

* Override this method if you need to customize http headers of the page. * * @param context */ protected void setBootstrapPageHeaders(BootstrapContext context) { WrappedResponse response = context.getResponse(); // 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"); } /** * 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 context * @throws IOException */ protected void writeBootstrapPageHtmlHeadStart(BootstrapContext context) throws IOException { Writer page = context.getWriter(); // write html header page.write("\n"); page.write("\n\n"); } /** * Get the URI for the application theme. * * A portal-wide default theme is fetched from the portal shared resource * directory (if any), other themes from the portlet. * * @param context * @param themeName * * @return */ public String getThemeUri(BootstrapContext context, String themeName) { WrappedRequest request = context.getRequest(); final String staticFilePath = request.getDeploymentConfiguration() .getStaticFileLocation(request); return staticFilePath + "/" + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName; } /** * Override if required * * @param context * @return */ public String getThemeName(BootstrapContext context) { return context.getApplication().getThemeForRoot(context.getRoot()); } /** * Don not override. * * @param context * @return */ public String findAndEscapeThemeName(BootstrapContext context) { String themeName = getThemeName(context); if (themeName == null) { WrappedRequest request = context.getRequest(); themeName = request.getDeploymentConfiguration() .getConfiguredTheme(request); } // XSS preventation, theme names shouldn't contain special chars anyway. // The servlet denies them via url parameter. themeName = AbstractApplicationServlet.stripSpecialChars(themeName); return themeName; } protected void writeError(WrappedResponse response, Throwable e) throws IOException { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); } /** * Gets the initial UIDL message to send to the client. * * @param request * the originating request * @param root * the root for which the UIDL should be generated * @return a string with the initial UIDL message * @throws PaintException * if an exception occurs while painting the components */ protected abstract String getInitialUIDL(WrappedRequest request, Root root) throws PaintException; }