/* * 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.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.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.security.GeneralSecurityException; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.GenericPortlet; import javax.portlet.MimeResponse; import javax.portlet.PortalContext; import javax.portlet.PortletConfig; import javax.portlet.PortletContext; import javax.portlet.PortletException; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; import javax.portlet.PortletURL; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import javax.portlet.ResourceURL; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import com.liferay.portal.kernel.util.PortalClassLoaderUtil; import com.liferay.portal.kernel.util.PropsUtil; import com.vaadin.Application; import com.vaadin.Application.SystemMessages; import com.vaadin.terminal.DownloadStream; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.gwt.client.ApplicationConfiguration; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.ui.Window; /** * Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0 * deployments and handles various portlet requests from the browser. * * TODO Document me! * * @author peholmst */ public abstract class AbstractApplicationPortlet extends GenericPortlet implements Constants { /** * This portlet parameter is used to add styles to the main element. E.g * "height:500px" generates a style="height:500px" to the main element. */ public static final String PORTLET_PARAMETER_STYLE = "style"; private static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite"; // TODO some parts could be shared with AbstractApplicationServlet // TODO Can we close the application when the portlet is removed? Do we know // when the portlet is removed? // TODO What happens when the portlet window is resized? Do we know when the // window is resized? private Properties applicationProperties; private boolean productionMode = false; @Override public void init(PortletConfig config) throws PortletException { super.init(config); // Stores the application parameters into Properties object applicationProperties = new Properties(); for (final Enumeration e = config.getInitParameterNames(); e .hasMoreElements();) { final String name = e.nextElement(); applicationProperties.setProperty(name, config.getInitParameter(name)); } // Overrides with server.xml parameters final PortletContext context = config.getPortletContext(); for (final Enumeration e = context.getInitParameterNames(); e .hasMoreElements();) { final String name = 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 */ getLogger().warning(WARNING_XSRF_PROTECTION_DISABLED); } } /** * Checks that the version reported by the client (widgetset) matches that * of the server. * * @param request */ private void checkWidgetsetVersion(PortletRequest request) { if (!AbstractApplicationServlet.VERSION.equals(getHTTPRequestParameter( request, "wsver"))) { getLogger().warning( String.format(WIDGETSET_MISMATCH_INFO, AbstractApplicationServlet.VERSION, getHTTPRequestParameter(request, "wsver"))); } } 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 */ // TODO Maybe we need a different message for portlets? getLogger().warning(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 */ protected 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; } /** * 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. * * @param request * @return The location of static resources (inside which there should be a * VAADIN directory). Does not end with a slash (/). */ protected String getStaticFilesLocation(PortletRequest request) { // TODO allow overriding on portlet level? String staticFileLocation = getPortalProperty( Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH, request.getPortalContext()); if (staticFileLocation != null) { // remove trailing slash if any while (staticFileLocation.endsWith(".")) { staticFileLocation = staticFileLocation.substring(0, staticFileLocation.length() - 1); } return staticFileLocation; } else { // default for Liferay return "/html"; } } protected enum RequestType { FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN; } protected RequestType getRequestType(PortletRequest request) { if (request instanceof RenderRequest) { return RequestType.RENDER; } else if (request instanceof ResourceRequest) { if (isUIDLRequest((ResourceRequest) request)) { return RequestType.UIDL; } else if (isFileUploadRequest((ResourceRequest) request)) { return RequestType.FILE_UPLOAD; } else if (isApplicationResourceRequest((ResourceRequest) request)) { return RequestType.APPLICATION_RESOURCE; } else if (isDummyRequest((ResourceRequest) request)) { return RequestType.DUMMY; } else { // these are not served with ResourceRequests, but by a servlet // on the portal at portlet root path (configured by default by // Liferay at deployment time, similar on other portals) return RequestType.STATIC_FILE; } } else if (request instanceof ActionRequest) { return RequestType.ACTION; } else if (request instanceof EventRequest) { return RequestType.EVENT; } return RequestType.UNKNOWN; } private boolean isApplicationResourceRequest(ResourceRequest request) { return request.getResourceID() != null && request.getResourceID().startsWith("APP"); } private boolean isUIDLRequest(ResourceRequest request) { return request.getResourceID() != null && request.getResourceID().equals("UIDL"); } private boolean isDummyRequest(ResourceRequest request) { return request.getResourceID() != null && request.getResourceID().equals("DUMMY"); } private boolean isFileUploadRequest(ResourceRequest request) { return "UPLOAD".equals(request.getResourceID()); } /** * 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; } protected void handleRequest(PortletRequest request, PortletResponse response) throws PortletException, IOException { RequestTimer requestTimer = new RequestTimer(); requestTimer.start(); RequestType requestType = getRequestType(request); if (requestType == RequestType.UNKNOWN) { handleUnknownRequest(request, response); } else if (requestType == RequestType.DUMMY) { /* * This dummy page is used by action responses to redirect to, in * order to prevent the boot strap code from being rendered into * strange places such as iframes. */ ((ResourceResponse) response).setContentType("text/html"); final OutputStream out = ((ResourceResponse) response) .getPortletOutputStream(); final PrintWriter outWriter = new PrintWriter(new BufferedWriter( new OutputStreamWriter(out, "UTF-8"))); outWriter.print("dummy page"); outWriter.close(); } else { Application application = null; boolean transactionStarted = false; boolean requestStarted = false; try { // TODO What about PARAM_UNLOADBURST & redirectToApplication?? /* Find out which application this request is related to */ application = findApplicationInstance(request, requestType); if (application == null) { return; } /* * Get or create an application context and an application * manager for the session */ PortletApplicationContext2 applicationContext = getApplicationContext(request .getPortletSession()); applicationContext.setResponse(response); applicationContext.setPortletConfig(getPortletConfig()); PortletCommunicationManager applicationManager = applicationContext .getApplicationManager(application); /* Update browser information from request */ updateBrowserProperties(applicationContext.getBrowser(), request); /* * Call application requestStart before Application.init() is * called (bypasses the limitation in TransactionListener) */ if (application instanceof PortletRequestListener) { ((PortletRequestListener) application).onRequestStart( request, response); requestStarted = true; } /* Start the newly created application */ startApplication(request, application, applicationContext); /* * Transaction starts. Call transaction listeners. Transaction * end is called in the finally block below. */ applicationContext.startTransaction(application, request); transactionStarted = true; /* Notify listeners */ // Finds the window within the application Window window = null; synchronized (application) { if (application.isRunning()) { switch (requestType) { case FILE_UPLOAD: // no window break; case APPLICATION_RESOURCE: // use main window - should not need any window window = application.getMainWindow(); break; default: window = applicationManager.getApplicationWindow( request, this, application, null); } // if window not found, not a problem - use null } } // TODO Should this happen before or after the transaction // starts? if (request instanceof RenderRequest) { applicationContext.firePortletRenderRequest(application, window, (RenderRequest) request, (RenderResponse) response); } else if (request instanceof ActionRequest) { applicationContext.firePortletActionRequest(application, window, (ActionRequest) request, (ActionResponse) response); } else if (request instanceof EventRequest) { applicationContext.firePortletEventRequest(application, window, (EventRequest) request, (EventResponse) response); } else if (request instanceof ResourceRequest) { applicationContext.firePortletResourceRequest(application, window, (ResourceRequest) request, (ResourceResponse) response); } /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { applicationManager.handleFileUpload( (ResourceRequest) request, (ResourceResponse) response); return; } else if (requestType == RequestType.UIDL) { // Handles AJAX UIDL requests if (isRepaintAll(request)) { // warn if versions do not match checkWidgetsetVersion(request); } applicationManager.handleUidlRequest( (ResourceRequest) request, (ResourceResponse) response, this, window); return; } else { /* * Removes the application if it has stopped */ if (!application.isRunning()) { endApplication(request, response, application); return; } handleOtherRequest(request, response, requestType, application, window, applicationContext, applicationManager); } } catch (final SessionExpiredException e) { // TODO Figure out a better way to deal with // SessionExpiredExceptions getLogger().finest("A user session has expired"); } catch (final GeneralSecurityException e) { // TODO Figure out a better way to deal with // GeneralSecurityExceptions getLogger() .fine("General security exception, the security key was probably incorrect."); } catch (final Throwable e) { handleServiceException(request, response, application, e); } finally { // Notifies transaction end try { if (transactionStarted) { ((PortletApplicationContext2) application.getContext()) .endTransaction(application, request); } } finally { try { if (requestStarted) { ((PortletRequestListener) application) .onRequestEnd(request, response); } } finally { PortletSession session = request .getPortletSession(false); if (session != null) { requestTimer.stop(getApplicationContext(session)); } } } } } } private void handleUnknownRequest(PortletRequest request, PortletResponse response) { getLogger().warning("Unknown request type"); } /** * Handle a portlet request that is not for static files, UIDL or upload. * Also render requests are handled here. * * This method is called after starting the application and calling portlet * and transaction listeners. * * @param request * @param response * @param requestType * @param application * @param applicationContext * @param applicationManager * @throws PortletException * @throws IOException * @throws MalformedURLException */ private void handleOtherRequest(PortletRequest request, PortletResponse response, RequestType requestType, Application application, Window window, PortletApplicationContext2 applicationContext, PortletCommunicationManager applicationManager) throws PortletException, IOException, MalformedURLException { if (window == null) { throw new PortletException(ERROR_NO_WINDOW_FOUND); } /* * Sets terminal type for the window, if not already set */ if (window.getTerminal() == null) { window.setTerminal(applicationContext.getBrowser()); } /* * Handle parameters */ final Map parameters = request.getParameterMap(); if (window != null && parameters != null) { window.handleParameters(parameters); } if (requestType == RequestType.APPLICATION_RESOURCE) { handleURI(applicationManager, window, (ResourceRequest) request, (ResourceResponse) response); } else if (requestType == RequestType.RENDER) { writeAjaxPage((RenderRequest) request, (RenderResponse) response, window, application); } else if (requestType == RequestType.EVENT) { // nothing to do, listeners do all the work } else if (requestType == RequestType.ACTION) { // nothing to do, listeners do all the work } else { throw new IllegalStateException( "handleRequest() without anything to do - should never happen!"); } } private void updateBrowserProperties(WebBrowser browser, PortletRequest request) { String userAgent = getHTTPHeader(request, "user-agent"); browser.updateRequestDetails(request.getLocale(), null, request.isSecure(), userAgent); if (getHTTPRequestParameter(request, "repaintAll") != null) { browser.updateClientSideDetails( getHTTPRequestParameter(request, "sw"), getHTTPRequestParameter(request, "sh"), getHTTPRequestParameter(request, "tzo"), getHTTPRequestParameter(request, "rtzo"), getHTTPRequestParameter(request, "dstd"), getHTTPRequestParameter(request, "dstActive"), getHTTPRequestParameter(request, "curdate"), getHTTPRequestParameter(request, "td") != null); } } @Override public void processEvent(EventRequest request, EventResponse response) throws PortletException, IOException { handleRequest(request, response); } private boolean handleURI(PortletCommunicationManager applicationManager, Window window, ResourceRequest request, ResourceResponse 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; } private void handleDownload(DownloadStream stream, ResourceRequest request, ResourceResponse response) throws IOException { if (stream.getParameter("Location") != null) { response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_MOVED_TEMPORARILY)); response.setProperty("Location", stream.getParameter("Location")); return; } // Download from given stream final InputStream data = stream.getStream(); if (data != null) { OutputStream out = null; try { // Sets content type response.setContentType(stream.getContentType()); // Sets cache headers final long cacheTime = stream.getCacheTime(); if (cacheTime <= 0) { response.setProperty("Cache-Control", "no-cache"); response.setProperty("Pragma", "no-cache"); response.setProperty("Expires", "0"); } else { response.setProperty("Cache-Control", "max-age=" + cacheTime / 1000); response.setProperty("Expires", "" + System.currentTimeMillis() + cacheTime); // Required to apply caching in some Tomcats response.setProperty("Pragma", "cache"); } // Copy download stream parameters directly // to HTTP headers. final Iterator i = stream.getParameterNames(); if (i != null) { while (i.hasNext()) { final String param = i.next(); response.setProperty(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.setProperty("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; out = response.getPortletOutputStream(); while ((bytesRead = data.read(buffer)) > 0) { out.write(buffer, 0, bytesRead); out.flush(); } } finally { AbstractCommunicationManager.tryToCloseStream(data); AbstractCommunicationManager.tryToCloseStream(out); } } } @Override public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException { handleRequest(request, response); } @Override protected void doDispatch(RenderRequest request, RenderResponse response) throws PortletException, IOException { try { // try to let super handle - it'll call methods annotated for // handling, the default doXYZ(), or throw if a handler for the mode // is not found super.doDispatch(request, response); } catch (PortletException e) { if (e.getCause() == null) { // No cause interpreted as 'unknown mode' - pass that trough // so that the application can handle handleRequest(request, response); } else { // Something else failed, pass on throw e; } } } @Override public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException { handleRequest(request, response); } boolean requestCanCreateApplication(PortletRequest request, RequestType requestType) { if (requestType == RequestType.UIDL && isRepaintAll(request)) { return true; } else if (requestType == RequestType.RENDER) { // In most cases the first request is a render request that renders // the HTML fragment. This should create an application instance. return true; } else if (requestType == RequestType.EVENT) { // A portlet can also be sent an event even though it has not been // rendered, e.g. portlet on one page sends an event to a portlet on // another page and then moves the user to that page. return true; } return false; } private boolean isRepaintAll(PortletRequest request) { return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); } private void startApplication(PortletRequest request, Application application, PortletApplicationContext2 context) throws PortletException, MalformedURLException { if (!application.isRunning()) { Locale locale = request.getLocale(); application.setLocale(locale); // No application URL when running inside a portlet application.start(null, applicationProperties, context); } } private void endApplication(PortletRequest request, PortletResponse response, Application application) throws IOException { final PortletSession session = request.getPortletSession(); if (session != null) { getApplicationContext(session).removeApplication(application); } // Do not send any redirects when running inside a portlet. } private Application findApplicationInstance(PortletRequest request, RequestType requestType) throws PortletException, SessionExpiredException, MalformedURLException { 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 = (getHTTPRequestParameter( request, URL_PARAMETER_RESTART_APPLICATION) != null); final boolean closeApplication = (getHTTPRequestParameter(request, URL_PARAMETER_CLOSE_APPLICATION) != null); if (restartApplication) { closeApplication(application, request.getPortletSession(false)); return createApplication(request); } else if (closeApplication) { closeApplication(application, request.getPortletSession(false)); return null; } else { return application; } } // No existing application was found if (requestCanCreateApplication) { return createApplication(request); } else { throw new SessionExpiredException(); } } private void closeApplication(Application application, PortletSession session) { if (application == null) { return; } application.close(); if (session != null) { PortletApplicationContext2 context = getApplicationContext(session); context.removeApplication(application); } } private Application createApplication(PortletRequest request) throws PortletException, MalformedURLException { Application newApplication = getNewApplication(request); final PortletApplicationContext2 context = getApplicationContext(request .getPortletSession()); context.addApplication(newApplication, request.getWindowID()); return newApplication; } private Application getExistingApplication(PortletRequest request, boolean allowSessionCreation) throws MalformedURLException, SessionExpiredException { final PortletSession session = request .getPortletSession(allowSessionCreation); if (session == null) { throw new SessionExpiredException(); } PortletApplicationContext2 context = getApplicationContext(session); Application application = context.getApplicationForWindowId(request .getWindowID()); if (application == null) { return null; } if (application.isRunning()) { return application; } // application found but not running context.removeApplication(application); return null; } /** * Returns the URL from which the widgetset is served on the portal. * * @param request * @return */ protected String getWidgetsetURL(RenderRequest request) { String requestWidgetset = getApplicationOrSystemProperty( PARAMETER_WIDGETSET, null); String sharedWidgetset = getPortalProperty( PORTAL_PARAMETER_VAADIN_WIDGETSET, request.getPortalContext()); String widgetset; if (requestWidgetset != null) { widgetset = requestWidgetset; } else if (sharedWidgetset != null) { widgetset = sharedWidgetset; } else { widgetset = DEFAULT_WIDGETSET; } String widgetsetURL = getWidgetsetURL(widgetset, request); return widgetsetURL; } /** * Returns the URL from which the widgetset is served on the portal. * * @param widgetset * @param request * @return */ protected String getWidgetsetURL(String widgetset, PortletRequest request) { return getStaticFilesLocation(request) + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime(); } /** * Returns the theme URI for the named theme on the portal. * * Note that this is not the only location referring to the theme URI - also * e.g. PortletCommunicationManager uses its own way to access the portlet * 2.0 theme resources. * * @param themeName * @param request * @return */ protected String getThemeURI(String themeName, PortletRequest request) { return getStaticFilesLocation(request) + "/" + THEME_DIRECTORY_PATH + themeName; } /** * Writes the html host page (aka kickstart page) that starts the actual * Vaadin application. * * If one needs to override parts of the portlet HTML contents creation, it * is suggested that one overrides one of several submethods including: *
    *
  • * {@link #writeAjaxPageHtmlMainDiv(RenderRequest, RenderResponse, BufferedWriter, String)} *
  • * {@link #getVaadinConfigurationMap(RenderRequest, RenderResponse, Application, String)} *
  • * {@link #writeAjaxPageHtmlVaadinScripts(RenderRequest, RenderResponse, BufferedWriter, Application, String)} *
* * @param request * the portlet request. * @param response * the portlet response to write to. * @param window * @param application * @throws IOException * if the writing failed due to input/output error. * @throws MalformedURLException * if the application is denied access the persistent data store * represented by the given URL. * @throws PortletException */ protected void writeAjaxPage(RenderRequest request, RenderResponse response, Window window, Application application) throws IOException, MalformedURLException, PortletException { response.setContentType("text/html"); final BufferedWriter page = new BufferedWriter(new OutputStreamWriter( response.getPortletOutputStream(), "UTF-8")); // TODO Currently, we can only load widgetsets and themes from the // portal String themeName = getThemeForWindow(request, window); writeAjaxPageHtmlVaadinScripts(request, response, page, application, themeName); /*- Add classnames; * .v-app * .v-app-loading * .v-app- * .v-theme- */ String appClass = "v-app-"; try { appClass += getApplicationClass().getSimpleName(); } catch (ClassNotFoundException e) { appClass += "unknown"; getLogger() .log(Level.SEVERE, "Could not find application class", e); } String themeClass = "v-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", ""); String classNames = "v-app " + themeClass + " " + appClass; String style = getApplicationProperty(PORTLET_PARAMETER_STYLE); String divStyle = ""; if (style != null) { divStyle = "style=\"" + style + "\""; } writeAjaxPageHtmlMainDiv(request, response, page, getApplicationDomId(request), classNames, divStyle); page.close(); } /** * Creates and returns a unique ID for the DIV where the application is to * be rendered. We need to generate a unique ID because some portals already * create a DIV with the portlet's Window ID as the DOM ID. * * @param request * PortletRequest * @return the id to use in the DOM */ private String getApplicationDomId(PortletRequest request) { return "v-" + request.getWindowID(); } /** * This method writes the scripts to load the widgetset and the themes as * well as define Vaadin configuration parameters on the HTML fragment that * starts the actual Vaadin application. * * @param request * @param response * @param writer * @param application * @param themeName * @throws IOException * @throws PortletException */ protected void writeAjaxPageHtmlVaadinScripts(RenderRequest request, RenderResponse response, final BufferedWriter writer, Application application, String themeName) throws IOException, PortletException { String themeURI = getThemeURI(themeName, request); // fixed base theme to use - all portal pages with Vaadin // applications will load this exactly once String portalTheme = getPortalProperty(PORTAL_PARAMETER_VAADIN_THEME, request.getPortalContext()); writer.write("\n"); writeAjaxPageWidgetset(request, writer); writeAjaxPageHtmlTheme(request, writer, themeName, themeURI, portalTheme); // TODO Warn if widgetset has not been loaded after 15 seconds } /** * Writes the script to load the widgetset on the HTML fragment created by * the portlet if the request attribute * {@value #WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE} is set to * Boolean.TRUE. *

* Warning! Loading widgetsets in this may cause a race condition in * the browser if there are multiple portlets on the same page (see #9774). *

* This method doesn't do anything unless * {@link #usesDirectWidgetsetLoad(RenderRequest)} has been overridden to * return true. *

* * @param request * @param writer * @throws IOException * */ protected void writeAjaxPageWidgetset(RenderRequest request, BufferedWriter writer) throws IOException { /* * If the writeAjaxPageWidgetset added the code using document.write, we * shouldn't do anything here because then the iframe and script tags * have already been added. */ if (request.getAttribute(WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE) != Boolean.TRUE) { return; } String widgetsetURL = getWidgetsetURL(request); writer.write("\n"); writer.write("\n"); } /** * Writes the script to load the widgetset on the HTML fragment created by * the portlet. * * @param request * @param response * @param writer * @throws IOException * */ protected void writeAjaxPageScriptWidgetset(RenderRequest request, RenderResponse response, final BufferedWriter writer) throws IOException { if (usesDirectWidgetsetLoad(request)) { /* * Direct widgetset loading has been enabled so let * writeAjaxPageWidgetset() write the script even though it might * cause loading problems with multiple portlets. */ // But we still need to close this one block, for compatibility writer.append("\n}\n"); /* * Set a flag so writeAjaxPageWidgetset() knows that this method has * not generated the document.write code. */ request.setAttribute(WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE, Boolean.TRUE); } else { String widgetsetURL = getWidgetsetURL(request); writer.write("document.write('');\n"); writer.write("document.write(\"\n"); } /** * 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 request * @param response * @param writer * @param id * @param classNames * @param divStyle * @throws IOException */ protected void writeAjaxPageHtmlMainDiv(RenderRequest request, RenderResponse response, final BufferedWriter writer, String id, String classNames, String divStyle) throws IOException { writer.write("

"); writer.write("
"); writer.write("
\n"); writer.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 theme for given request/window * * @param request * @param window * @return */ protected String getThemeForWindow(PortletRequest request, Window window) { // Finds theme name String themeName; // theme defined for the window? themeName = window.getTheme(); if (themeName == null) { // no, is the default theme defined by the portal? themeName = getPortalProperty( Constants.PORTAL_PARAMETER_VAADIN_THEME, request.getPortalContext()); } if (themeName == null) { // no, using the default theme defined by Vaadin themeName = DEFAULT_THEME_NAME; } return themeName; } protected abstract Class getApplicationClass() throws ClassNotFoundException; protected Application getNewApplication(PortletRequest request) throws PortletException { try { final Application application = getApplicationClass().newInstance(); return application; } catch (final IllegalAccessException e) { throw new PortletException("getNewApplication failed", e); } catch (final InstantiationException e) { throw new PortletException("getNewApplication failed", e); } catch (final ClassNotFoundException e) { throw new PortletException("getNewApplication failed", e); } } protected ClassLoader getClassLoader() throws PortletException { // TODO Add support for custom class loader return getClass().getClassLoader(); } /** * Get system messages from the current application class * * @return */ protected SystemMessages getSystemMessages() { try { Class appCls = getApplicationClass(); Method m = appCls.getMethod("getSystemMessages", (Class[]) null); return (Application.SystemMessages) m.invoke(null, (Object[]) null); } catch (ClassNotFoundException e) { // This should never happen throw new SystemMessageException(e); } catch (SecurityException e) { throw new SystemMessageException( "Application.getSystemMessage() should be static public", e); } catch (NoSuchMethodException e) { // This is completely ok and should be silently ignored } catch (IllegalArgumentException e) { // This should never happen throw new SystemMessageException(e); } catch (IllegalAccessException e) { throw new SystemMessageException( "Application.getSystemMessage() should be static public", e); } catch (InvocationTargetException e) { // This should never happen throw new SystemMessageException(e); } return Application.getSystemMessages(); } private void handleServiceException(PortletRequest request, PortletResponse response, Application application, Throwable e) throws IOException, PortletException { // TODO Check that this error handler is working when running inside a // portlet // if this was an UIDL request, response UIDL back to client if (getRequestType(request) == RequestType.UIDL) { Application.SystemMessages ci = getSystemMessages(); criticalNotification(request, (ResourceResponse) response, ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), null, ci.getInternalErrorURL()); if (application != null) { application.getErrorHandler() .terminalError(new RequestError(e)); } else { throw new PortletException(e); } } else { // Re-throw other exceptions throw new PortletException(e); } } @SuppressWarnings("serial") public class RequestError implements Terminal.ErrorEvent, Serializable { private final Throwable throwable; public RequestError(Throwable throwable) { this.throwable = throwable; } public Throwable getThrowable() { return throwable; } } /** * 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 Portlet request instance. * @param response * the Portlet 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(PortletRequest request, MimeResponse 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 OutputStream out = response.getPortletOutputStream(); 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.close(); } /** * Returns a portal configuration property. * * Liferay is handled separately as * {@link PortalContext#getProperty(String)} does not return portal * properties from e.g. portal-ext.properties . * * @param name * @param context * @return */ protected static String getPortalProperty(String name, PortalContext context) { boolean isLifeRay = context.getPortalInfo().toLowerCase() .contains("liferay"); // TODO test on non-LifeRay platforms String value; if (isLifeRay) { value = getLifeRayPortalProperty(name); } else { value = context.getProperty(name); } return value; } private static String getLifeRayPortalProperty(String name) { String value; try { value = PropsUtil.get(name); } catch (Exception e) { value = null; } return value; } /** * Try to get an HTTP header value from a request using portal specific * APIs. * * @param name * HTTP header name * @return the value of the header (empty string if defined without a value, * null if the parameter is not present or retrieving it failed) */ private static String getHTTPHeader(PortletRequest request, String name) { String value = null; String portalInfo = request.getPortalContext().getPortalInfo() .toLowerCase(); if (portalInfo.contains("liferay")) { value = getLiferayHTTPHeader(request, name); } else if (portalInfo.contains("gatein")) { value = getGateInHTTPHeader(request, name); } return value; } /** * Try to get the value of a HTTP request parameter from a portlet request * using portal specific APIs. It is not possible to get the HTTP request * parameters using the official Portlet 2.0 API. * * @param name * HTTP request parameter name * @return the value of the parameter (empty string if parameter defined * without a value, null if the parameter is not present or * retrieving it failed) */ private static String getHTTPRequestParameter(PortletRequest request, String name) { String value = request.getParameter(name); if (value == null) { String portalInfo = request.getPortalContext().getPortalInfo() .toLowerCase(); if (portalInfo.contains("liferay")) { value = getLiferayHTTPRequestParameter(request, name); } else if (portalInfo.contains("gatein")) { value = getGateInHTTPRequestParameter(request, name); } } return value; } private static String getGateInHTTPRequestParameter(PortletRequest request, String name) { String value = null; try { Method getRealReq = request.getClass().getMethod("getRealRequest"); HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq .invoke(request); value = origRequest.getParameter(name); } catch (Exception e) { // do nothing - not on GateIn simple-portal } return value; } private static String getLiferayHTTPRequestParameter( PortletRequest request, String name) { try { HttpServletRequest httpRequest = getLiferayOriginalServletRequest(request); if (httpRequest != null) { return httpRequest.getParameter(name); } } catch (Exception e) { // ignore and return null - unable to get the original request } return null; } private static String getGateInHTTPHeader(PortletRequest request, String name) { String value = null; try { Method getRealReq = request.getClass().getMethod("getRealRequest"); HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq .invoke(request); value = origRequest.getHeader(name); } catch (Exception e) { // do nothing - not on GateIn simple-portal } return value; } private static String getLiferayHTTPHeader(PortletRequest request, String name) { try { HttpServletRequest httpRequest = getLiferayOriginalServletRequest(request); if (httpRequest != null) { return httpRequest.getHeader(name); } } catch (Exception e) { // ignore and return null - unable to get the original request } return null; } /** * Simplified version of what Liferay PortalClassInvoker did. This is used * because the API of PortalClassInvoker has changed in Liferay 6.2. * * This simply uses reflection with Liferay class loader. Parameters are * Strings to avoid static dependencies and to load all classes with * Liferay's own class loader. Only static utility methods are supported. * * This method is for internal use only and may change in future versions. * * @param className * name of the Liferay class to call * @param methodName * name of the method to call * @param parameterClassName * name of the parameter class of the method * @throws Exception * @return return value of the invoked method */ private static Object invokeStaticLiferayMethod(String className, String methodName, Object argument, String parameterClassName) throws Exception { Thread currentThread = Thread.currentThread(); ClassLoader contextClassLoader = currentThread.getContextClassLoader(); try { // this should be available across all Liferay versions with no // problematic static dependencies ClassLoader portalClassLoader = PortalClassLoaderUtil .getClassLoader(); // this is in case the class loading triggers code that explicitly // uses current thread class loader currentThread.setContextClassLoader(portalClassLoader); Class targetClass = portalClassLoader.loadClass(className); Class parameterClass = portalClassLoader .loadClass(parameterClassName); Method method = targetClass.getMethod(methodName, parameterClass); return method.invoke(null, new Object[] { argument }); } catch (InvocationTargetException ite) { throw (Exception) ite.getCause(); } finally { currentThread.setContextClassLoader(contextClassLoader); } } private static HttpServletRequest getLiferayOriginalServletRequest( PortletRequest request) throws Exception { // httpRequest = PortalUtil.getHttpServletRequest(request); HttpServletRequest httpRequest = (HttpServletRequest) invokeStaticLiferayMethod( "com.liferay.portal.util.PortalUtil", "getHttpServletRequest", request, "javax.portlet.PortletRequest"); // httpRequest = // PortalUtil.getOriginalServletRequest(httpRequest); httpRequest = (HttpServletRequest) invokeStaticLiferayMethod( "com.liferay.portal.util.PortalUtil", "getOriginalServletRequest", httpRequest, "javax.servlet.http.HttpServletRequest"); return httpRequest; } /** * * Gets the application context for a PortletSession. If no context is * currently stored in a session a new context is created and stored in the * session. * * @param portletSession * the portlet session. * @return the application context for the session. */ protected PortletApplicationContext2 getApplicationContext( PortletSession portletSession) { PortletApplicationContext2 ctx = PortletApplicationContext2.getApplicationContext(portletSession); ctx.setProductionMode(isProductionMode()); return ctx; } private static final Logger getLogger() { return Logger.getLogger(AbstractApplicationPortlet.class.getName()); } }