/* * Copyright 2000-2014 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.launcher; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.vaadin.launcher.CustomDeploymentConfiguration.Conf; import com.vaadin.server.DefaultDeploymentConfiguration; import com.vaadin.server.DeploymentConfiguration; import com.vaadin.server.LegacyApplication; import com.vaadin.server.LegacyVaadinServlet; import com.vaadin.server.ServiceException; import com.vaadin.server.SessionInitEvent; import com.vaadin.server.SessionInitListener; import com.vaadin.server.UIClassSelectionEvent; import com.vaadin.server.UIProvider; import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinServletRequest; import com.vaadin.server.VaadinSession; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.UI; import com.vaadin.util.CurrentInstance; @SuppressWarnings("serial") public class ApplicationRunnerServlet extends LegacyVaadinServlet { /** * The name of the application class currently used. Only valid within one * request. */ private LinkedHashSet defaultPackages = new LinkedHashSet(); private transient final ThreadLocal request = new ThreadLocal(); @Override public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); String initParameter = servletConfig .getInitParameter("defaultPackages"); if (initParameter != null) { Collections.addAll(defaultPackages, initParameter.split(",")); } String str = TestBase.class.getName().replace('.', '/') + ".class"; URL url = getService().getClassLoader().getResource(str); if ("file".equals(url.getProtocol())) { String path = url.getPath(); try { path = new URI(path).getPath(); } catch (URISyntaxException e) { getLogger().log(Level.FINE, "Failed to decode url", e); } File comVaadinTests = new File(path).getParentFile() .getParentFile(); addDirectories(comVaadinTests, defaultPackages, "com.vaadin.tests"); } } @Override protected void servletInitialized() throws ServletException { super.servletInitialized(); getService().addSessionInitListener(new SessionInitListener() { @Override public void sessionInit(SessionInitEvent event) throws ServiceException { onVaadinSessionStarted(event.getRequest(), event.getSession()); } }); } private void addDirectories(File parent, LinkedHashSet packages, String parentPackage) { packages.add(parentPackage); for (File f : parent.listFiles()) { if (f.isDirectory()) { String newPackage = parentPackage + "." + f.getName(); addDirectories(f, packages, newPackage); } } } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.request.set(request); try { super.service(request, response); } finally { this.request.set(null); } } @Override protected URL getApplicationUrl(HttpServletRequest request) throws MalformedURLException { URL url = super.getApplicationUrl(request); String path = url.toString(); path += getApplicationRunnerApplicationClassName(request); path += "/"; return new URL(path); } @Override protected Class getApplicationClass() throws ClassNotFoundException { return getClassToRun().asSubclass(LegacyApplication.class); } @Override protected boolean shouldCreateApplication(HttpServletRequest request) throws ServletException { try { return LegacyApplication.class.isAssignableFrom(getClassToRun()); } catch (ClassNotFoundException e) { throw new ServletException(e); } } protected void onVaadinSessionStarted(VaadinRequest request, VaadinSession session) throws ServiceException { try { final Class classToRun = getClassToRun(); if (UI.class.isAssignableFrom(classToRun)) { session.addUIProvider(new ApplicationRunnerUIProvider( classToRun)); } else if (LegacyApplication.class.isAssignableFrom(classToRun)) { // Avoid using own UIProvider for legacy Application } else if (UIProvider.class.isAssignableFrom(classToRun)) { session.addUIProvider((UIProvider) classToRun.newInstance()); } else { throw new ServiceException(classToRun.getCanonicalName() + " is neither an Application nor a UI"); } } catch (final IllegalAccessException e) { throw new ServiceException(e); } catch (final InstantiationException e) { throw new ServiceException(e); } catch (final ClassNotFoundException e) { throw new ServiceException( new InstantiationException( "Failed to load application class: " + getApplicationRunnerApplicationClassName((VaadinServletRequest) request))); } } private String getApplicationRunnerApplicationClassName( HttpServletRequest request) { return getApplicationRunnerURIs(request).applicationClassname; } private static final class ApplicationRunnerUIProvider extends UIProvider { private final Class classToRun; private ApplicationRunnerUIProvider(Class classToRun) { this.classToRun = classToRun; } @Override public Class getUIClass(UIClassSelectionEvent event) { return (Class) classToRun; } } // TODO Don't need to use a data object now that there's only one field private static class URIS { // String staticFilesPath; // String applicationURI; // String context; // String runner; String applicationClassname; } /** * Parses application runner URIs. * * If request URL is e.g. * http://localhost:8080/vaadin/run/com.vaadin.demo.Calc then *
    *
  • context=vaadin
  • *
  • Runner servlet=run
  • *
  • Vaadin application=com.vaadin.demo.Calc
  • *
* * @param request * @return string array containing widgetset URI, application URI and * context, runner, application classname */ private static URIS getApplicationRunnerURIs(HttpServletRequest request) { final String[] urlParts = request.getRequestURI().toString() .split("\\/"); // String runner = null; URIS uris = new URIS(); String applicationClassname = null; String contextPath = request.getContextPath(); if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) { // class name comes after web context and runner application // runner = urlParts[2]; if (urlParts.length == 3) { throw new IllegalArgumentException("No application specified"); } applicationClassname = urlParts[3]; // uris.applicationURI = "/" + context + "/" + runner + "/" // + applicationClassname; // uris.context = context; // uris.runner = runner; uris.applicationClassname = applicationClassname; } else { // no context // runner = urlParts[1]; if (urlParts.length == 2) { throw new IllegalArgumentException("No application specified"); } applicationClassname = urlParts[2]; // uris.applicationURI = "/" + runner + "/" + applicationClassname; // uris.context = context; // uris.runner = runner; uris.applicationClassname = applicationClassname; } return uris; } private Class getClassToRun() throws ClassNotFoundException { // TODO use getClassLoader() ? Class appClass = null; String baseName = getApplicationRunnerApplicationClassName(request .get()); try { appClass = getClass().getClassLoader().loadClass(baseName); return appClass; } catch (Exception e) { // for (String pkg : defaultPackages) { try { appClass = getClass().getClassLoader().loadClass( pkg + "." + baseName); } catch (ClassNotFoundException ee) { // Ignore as this is expected for many packages } catch (Exception e2) { // TODO: handle exception getLogger().log( Level.FINE, "Failed to find application class " + pkg + "." + baseName, e2); } if (appClass != null) { return appClass; } } } throw new ClassNotFoundException(baseName); } private Logger getLogger() { return Logger.getLogger(ApplicationRunnerServlet.class.getName()); } @Override protected DeploymentConfiguration createDeploymentConfiguration( Properties initParameters) { // Get the original configuration from the super class final DeploymentConfiguration originalConfiguration = super .createDeploymentConfiguration(initParameters); // And then create a proxy instance that delegates to the original // configuration or a customized version return (DeploymentConfiguration) Proxy.newProxyInstance( DeploymentConfiguration.class.getClassLoader(), new Class[] { DeploymentConfiguration.class }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == DeploymentConfiguration.class) { // Find the configuration instance to delegate to DeploymentConfiguration configuration = findDeploymentConfiguration(originalConfiguration); return method.invoke(configuration, args); } else { return method.invoke(proxy, args); } } }); } private DeploymentConfiguration findDeploymentConfiguration( DeploymentConfiguration originalConfiguration) throws Exception { // First level of cache DeploymentConfiguration configuration = CurrentInstance .get(DeploymentConfiguration.class); if (configuration == null) { // Not in cache, try to find a VaadinSession to get it from VaadinSession session = VaadinSession.getCurrent(); if (session == null) { /* * There's no current session, request or response when serving * static resources, but there's still the current request * maintained by AppliationRunnerServlet, and there's most * likely also a HttpSession containing a VaadinSession for that * request. */ HttpServletRequest currentRequest = request.get(); if (currentRequest != null) { HttpSession httpSession = currentRequest.getSession(false); if (httpSession != null) { Map, CurrentInstance> oldCurrent = CurrentInstance .setCurrent((VaadinSession) null); try { session = getService().findVaadinSession( new VaadinServletRequest(currentRequest, getService())); } finally { /* * Clear some state set by findVaadinSession to * avoid accidentally depending on it when coding on * e.g. static request handling. */ CurrentInstance.restoreInstances(oldCurrent); currentRequest.removeAttribute(VaadinSession.class .getName()); } } } } if (session != null) { String name = ApplicationRunnerServlet.class.getName() + ".deploymentConfiguration"; try { session.lock(); configuration = (DeploymentConfiguration) session .getAttribute(name); if (configuration == null) { Class classToRun; try { classToRun = getClassToRun(); } catch (ClassNotFoundException e) { /* * This happens e.g. if the UI class defined in the * URL is not found or if this servlet just serves * static resources while there's some other servlet * that serves the UI (e.g. when using /run-push/). */ return originalConfiguration; } CustomDeploymentConfiguration customDeploymentConfiguration = classToRun .getAnnotation(CustomDeploymentConfiguration.class); if (customDeploymentConfiguration != null) { Properties initParameters = new Properties( originalConfiguration.getInitParameters()); for (Conf entry : customDeploymentConfiguration .value()) { initParameters.put(entry.name(), entry.value()); } configuration = new DefaultDeploymentConfiguration( getClass(), initParameters); } else { configuration = originalConfiguration; } session.setAttribute(name, configuration); } } finally { session.unlock(); } CurrentInstance.set(DeploymentConfiguration.class, configuration); } else { configuration = originalConfiguration; } } return configuration; } }