選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

ApplicationRunnerServlet.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.launcher;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.io.Serializable;
  20. import java.lang.reflect.InvocationHandler;
  21. import java.lang.reflect.Method;
  22. import java.lang.reflect.Proxy;
  23. import java.net.MalformedURLException;
  24. import java.net.URI;
  25. import java.net.URISyntaxException;
  26. import java.net.URL;
  27. import java.util.Collections;
  28. import java.util.LinkedHashSet;
  29. import java.util.Map;
  30. import java.util.Properties;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;
  33. import javax.servlet.ServletConfig;
  34. import javax.servlet.ServletException;
  35. import javax.servlet.http.HttpServletRequest;
  36. import javax.servlet.http.HttpServletResponse;
  37. import javax.servlet.http.HttpSession;
  38. import com.vaadin.launcher.CustomDeploymentConfiguration.Conf;
  39. import com.vaadin.server.DefaultDeploymentConfiguration;
  40. import com.vaadin.server.DeploymentConfiguration;
  41. import com.vaadin.server.LegacyApplication;
  42. import com.vaadin.server.LegacyVaadinServlet;
  43. import com.vaadin.server.ServiceException;
  44. import com.vaadin.server.SessionInitEvent;
  45. import com.vaadin.server.SessionInitListener;
  46. import com.vaadin.server.SystemMessages;
  47. import com.vaadin.server.SystemMessagesInfo;
  48. import com.vaadin.server.SystemMessagesProvider;
  49. import com.vaadin.server.UIClassSelectionEvent;
  50. import com.vaadin.server.UIProvider;
  51. import com.vaadin.server.VaadinRequest;
  52. import com.vaadin.server.VaadinService;
  53. import com.vaadin.server.VaadinServlet;
  54. import com.vaadin.server.VaadinServletRequest;
  55. import com.vaadin.server.VaadinServletService;
  56. import com.vaadin.server.VaadinSession;
  57. import com.vaadin.tests.components.TestBase;
  58. import com.vaadin.ui.UI;
  59. import com.vaadin.util.CurrentInstance;
  60. @SuppressWarnings("serial")
  61. public class ApplicationRunnerServlet extends LegacyVaadinServlet {
  62. public static String CUSTOM_SYSTEM_MESSAGES_PROPERTY = "custom-"
  63. + SystemMessages.class.getName();
  64. /**
  65. * The name of the application class currently used. Only valid within one
  66. * request.
  67. */
  68. private LinkedHashSet<String> defaultPackages = new LinkedHashSet<String>();
  69. private transient final ThreadLocal<HttpServletRequest> request = new ThreadLocal<HttpServletRequest>();
  70. @Override
  71. public void init(ServletConfig servletConfig) throws ServletException {
  72. super.init(servletConfig);
  73. String initParameter = servletConfig
  74. .getInitParameter("defaultPackages");
  75. if (initParameter != null) {
  76. Collections.addAll(defaultPackages, initParameter.split(","));
  77. }
  78. String str = TestBase.class.getName().replace('.', '/') + ".class";
  79. URL url = getService().getClassLoader().getResource(str);
  80. if ("file".equals(url.getProtocol())) {
  81. String path = url.getPath();
  82. try {
  83. path = new URI(path).getPath();
  84. } catch (URISyntaxException e) {
  85. getLogger().log(Level.FINE, "Failed to decode url", e);
  86. }
  87. File comVaadinTests = new File(path).getParentFile()
  88. .getParentFile();
  89. addDirectories(comVaadinTests, defaultPackages, "com.vaadin.tests");
  90. }
  91. }
  92. @Override
  93. protected void servletInitialized() throws ServletException {
  94. super.servletInitialized();
  95. getService().addSessionInitListener(new SessionInitListener() {
  96. @Override
  97. public void sessionInit(SessionInitEvent event)
  98. throws ServiceException {
  99. onVaadinSessionStarted(event.getRequest(), event.getSession());
  100. }
  101. });
  102. }
  103. private void addDirectories(File parent, LinkedHashSet<String> packages,
  104. String parentPackage) {
  105. packages.add(parentPackage);
  106. for (File f : parent.listFiles()) {
  107. if (f.isDirectory()) {
  108. String newPackage = parentPackage + "." + f.getName();
  109. addDirectories(f, packages, newPackage);
  110. }
  111. }
  112. }
  113. @Override
  114. protected void service(HttpServletRequest request,
  115. HttpServletResponse response) throws ServletException, IOException {
  116. this.request.set(request);
  117. try {
  118. super.service(request, response);
  119. } finally {
  120. this.request.set(null);
  121. }
  122. }
  123. @Override
  124. protected URL getApplicationUrl(HttpServletRequest request)
  125. throws MalformedURLException {
  126. URL url = super.getApplicationUrl(request);
  127. String path = url.toString();
  128. path += getApplicationRunnerApplicationClassName(request);
  129. path += "/";
  130. return new URL(path);
  131. }
  132. @Override
  133. protected Class<? extends LegacyApplication> getApplicationClass()
  134. throws ClassNotFoundException {
  135. return getClassToRun().asSubclass(LegacyApplication.class);
  136. }
  137. @Override
  138. protected boolean shouldCreateApplication(HttpServletRequest request)
  139. throws ServletException {
  140. try {
  141. return LegacyApplication.class.isAssignableFrom(getClassToRun());
  142. } catch (ClassNotFoundException e) {
  143. throw new ServletException(e);
  144. }
  145. }
  146. protected void onVaadinSessionStarted(VaadinRequest request,
  147. VaadinSession session) throws ServiceException {
  148. try {
  149. final Class<?> classToRun = getClassToRun();
  150. if (UI.class.isAssignableFrom(classToRun)) {
  151. session.addUIProvider(new ApplicationRunnerUIProvider(
  152. classToRun));
  153. } else if (LegacyApplication.class.isAssignableFrom(classToRun)) {
  154. // Avoid using own UIProvider for legacy Application
  155. } else if (UIProvider.class.isAssignableFrom(classToRun)) {
  156. session.addUIProvider((UIProvider) classToRun.newInstance());
  157. } else {
  158. throw new ServiceException(classToRun.getCanonicalName()
  159. + " is neither an Application nor a UI");
  160. }
  161. } catch (final IllegalAccessException e) {
  162. throw new ServiceException(e);
  163. } catch (final InstantiationException e) {
  164. throw new ServiceException(e);
  165. } catch (final ClassNotFoundException e) {
  166. throw new ServiceException(
  167. new InstantiationException(
  168. "Failed to load application class: "
  169. + getApplicationRunnerApplicationClassName((VaadinServletRequest) request)));
  170. }
  171. }
  172. private String getApplicationRunnerApplicationClassName(
  173. HttpServletRequest request) {
  174. return getApplicationRunnerURIs(request).applicationClassname;
  175. }
  176. private final static class ProxyDeploymentConfiguration implements
  177. InvocationHandler, Serializable {
  178. private final DeploymentConfiguration originalConfiguration;
  179. private ProxyDeploymentConfiguration(
  180. DeploymentConfiguration originalConfiguration) {
  181. this.originalConfiguration = originalConfiguration;
  182. }
  183. @Override
  184. public Object invoke(Object proxy, Method method, Object[] args)
  185. throws Throwable {
  186. if (method.getDeclaringClass() == DeploymentConfiguration.class) {
  187. // Find the configuration instance to delegate to
  188. DeploymentConfiguration configuration = findDeploymentConfiguration(originalConfiguration);
  189. return method.invoke(configuration, args);
  190. } else {
  191. return method.invoke(proxy, args);
  192. }
  193. }
  194. }
  195. private static final class ApplicationRunnerUIProvider extends UIProvider {
  196. private final Class<?> classToRun;
  197. private ApplicationRunnerUIProvider(Class<?> classToRun) {
  198. this.classToRun = classToRun;
  199. }
  200. @Override
  201. public Class<? extends UI> getUIClass(UIClassSelectionEvent event) {
  202. return (Class<? extends UI>) classToRun;
  203. }
  204. }
  205. // TODO Don't need to use a data object now that there's only one field
  206. private static class URIS {
  207. // String staticFilesPath;
  208. // String applicationURI;
  209. // String context;
  210. // String runner;
  211. String applicationClassname;
  212. }
  213. /**
  214. * Parses application runner URIs.
  215. *
  216. * If request URL is e.g.
  217. * http://localhost:8080/vaadin/run/com.vaadin.demo.Calc then
  218. * <ul>
  219. * <li>context=vaadin</li>
  220. * <li>Runner servlet=run</li>
  221. * <li>Vaadin application=com.vaadin.demo.Calc</li>
  222. * </ul>
  223. *
  224. * @param request
  225. * @return string array containing widgetset URI, application URI and
  226. * context, runner, application classname
  227. */
  228. private static URIS getApplicationRunnerURIs(HttpServletRequest request) {
  229. final String[] urlParts = request.getRequestURI().toString()
  230. .split("\\/");
  231. // String runner = null;
  232. URIS uris = new URIS();
  233. String applicationClassname = null;
  234. String contextPath = request.getContextPath();
  235. if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) {
  236. // class name comes after web context and runner application
  237. // runner = urlParts[2];
  238. if (urlParts.length == 3) {
  239. throw new IllegalArgumentException("No application specified");
  240. }
  241. applicationClassname = urlParts[3];
  242. // uris.applicationURI = "/" + context + "/" + runner + "/"
  243. // + applicationClassname;
  244. // uris.context = context;
  245. // uris.runner = runner;
  246. uris.applicationClassname = applicationClassname;
  247. } else {
  248. // no context
  249. // runner = urlParts[1];
  250. if (urlParts.length == 2) {
  251. throw new IllegalArgumentException("No application specified");
  252. }
  253. applicationClassname = urlParts[2];
  254. // uris.applicationURI = "/" + runner + "/" + applicationClassname;
  255. // uris.context = context;
  256. // uris.runner = runner;
  257. uris.applicationClassname = applicationClassname;
  258. }
  259. return uris;
  260. }
  261. private Class<?> getClassToRun() throws ClassNotFoundException {
  262. // TODO use getClassLoader() ?
  263. Class<?> appClass = null;
  264. String baseName = getApplicationRunnerApplicationClassName(request
  265. .get());
  266. try {
  267. appClass = getClass().getClassLoader().loadClass(baseName);
  268. return appClass;
  269. } catch (Exception e) {
  270. //
  271. for (String pkg : defaultPackages) {
  272. try {
  273. appClass = getClass().getClassLoader().loadClass(
  274. pkg + "." + baseName);
  275. } catch (ClassNotFoundException ee) {
  276. // Ignore as this is expected for many packages
  277. } catch (Exception e2) {
  278. // TODO: handle exception
  279. getLogger().log(
  280. Level.FINE,
  281. "Failed to find application class " + pkg + "."
  282. + baseName, e2);
  283. }
  284. if (appClass != null) {
  285. return appClass;
  286. }
  287. }
  288. }
  289. throw new ClassNotFoundException(baseName);
  290. }
  291. private Logger getLogger() {
  292. return Logger.getLogger(ApplicationRunnerServlet.class.getName());
  293. }
  294. @Override
  295. protected DeploymentConfiguration createDeploymentConfiguration(
  296. Properties initParameters) {
  297. // Get the original configuration from the super class
  298. final DeploymentConfiguration originalConfiguration = super
  299. .createDeploymentConfiguration(initParameters);
  300. // And then create a proxy instance that delegates to the original
  301. // configuration or a customized version
  302. return (DeploymentConfiguration) Proxy.newProxyInstance(
  303. DeploymentConfiguration.class.getClassLoader(),
  304. new Class[] { DeploymentConfiguration.class },
  305. new ProxyDeploymentConfiguration(originalConfiguration));
  306. }
  307. @Override
  308. protected VaadinServletService createServletService(
  309. DeploymentConfiguration deploymentConfiguration)
  310. throws ServiceException {
  311. VaadinServletService service = super
  312. .createServletService(deploymentConfiguration);
  313. final SystemMessagesProvider provider = service
  314. .getSystemMessagesProvider();
  315. service.setSystemMessagesProvider(new SystemMessagesProvider() {
  316. @Override
  317. public SystemMessages getSystemMessages(
  318. SystemMessagesInfo systemMessagesInfo) {
  319. if (systemMessagesInfo.getRequest() == null) {
  320. return provider.getSystemMessages(systemMessagesInfo);
  321. }
  322. Object messages = systemMessagesInfo.getRequest().getAttribute(
  323. CUSTOM_SYSTEM_MESSAGES_PROPERTY);
  324. if (messages instanceof SystemMessages) {
  325. return (SystemMessages) messages;
  326. }
  327. return provider.getSystemMessages(systemMessagesInfo);
  328. }
  329. });
  330. return service;
  331. }
  332. private static DeploymentConfiguration findDeploymentConfiguration(
  333. DeploymentConfiguration originalConfiguration) throws Exception {
  334. // First level of cache
  335. DeploymentConfiguration configuration = CurrentInstance
  336. .get(DeploymentConfiguration.class);
  337. if (configuration == null) {
  338. // Not in cache, try to find a VaadinSession to get it from
  339. VaadinSession session = VaadinSession.getCurrent();
  340. if (session == null) {
  341. /*
  342. * There's no current session, request or response when serving
  343. * static resources, but there's still the current request
  344. * maintained by AppliationRunnerServlet, and there's most
  345. * likely also a HttpSession containing a VaadinSession for that
  346. * request.
  347. */
  348. HttpServletRequest currentRequest = VaadinServletService
  349. .getCurrentServletRequest();
  350. if (currentRequest != null) {
  351. HttpSession httpSession = currentRequest.getSession(false);
  352. if (httpSession != null) {
  353. Map<Class<?>, CurrentInstance> oldCurrent = CurrentInstance
  354. .setCurrent((VaadinSession) null);
  355. try {
  356. VaadinServletService service = (VaadinServletService) VaadinService
  357. .getCurrent();
  358. session = service
  359. .findVaadinSession(new VaadinServletRequest(
  360. currentRequest, service));
  361. } finally {
  362. /*
  363. * Clear some state set by findVaadinSession to
  364. * avoid accidentally depending on it when coding on
  365. * e.g. static request handling.
  366. */
  367. CurrentInstance.restoreInstances(oldCurrent);
  368. currentRequest.removeAttribute(VaadinSession.class
  369. .getName());
  370. }
  371. }
  372. }
  373. }
  374. if (session != null) {
  375. String name = ApplicationRunnerServlet.class.getName()
  376. + ".deploymentConfiguration";
  377. try {
  378. session.lock();
  379. configuration = (DeploymentConfiguration) session
  380. .getAttribute(name);
  381. if (configuration == null) {
  382. ApplicationRunnerServlet servlet = (ApplicationRunnerServlet) VaadinServlet
  383. .getCurrent();
  384. Class<?> classToRun;
  385. try {
  386. classToRun = servlet.getClassToRun();
  387. } catch (ClassNotFoundException e) {
  388. /*
  389. * This happens e.g. if the UI class defined in the
  390. * URL is not found or if this servlet just serves
  391. * static resources while there's some other servlet
  392. * that serves the UI (e.g. when using /run-push/).
  393. */
  394. return originalConfiguration;
  395. }
  396. CustomDeploymentConfiguration customDeploymentConfiguration = classToRun
  397. .getAnnotation(CustomDeploymentConfiguration.class);
  398. if (customDeploymentConfiguration != null) {
  399. Properties initParameters = new Properties(
  400. originalConfiguration.getInitParameters());
  401. for (Conf entry : customDeploymentConfiguration
  402. .value()) {
  403. initParameters.put(entry.name(), entry.value());
  404. }
  405. configuration = new DefaultDeploymentConfiguration(
  406. servlet.getClass(), initParameters);
  407. } else {
  408. configuration = originalConfiguration;
  409. }
  410. session.setAttribute(name, configuration);
  411. }
  412. } finally {
  413. session.unlock();
  414. }
  415. CurrentInstance.set(DeploymentConfiguration.class,
  416. configuration);
  417. } else {
  418. configuration = originalConfiguration;
  419. }
  420. }
  421. return configuration;
  422. }
  423. }