You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VaadinService.java 68KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860
  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.server;
  17. import java.io.BufferedWriter;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.io.OutputStreamWriter;
  23. import java.io.PrintWriter;
  24. import java.io.Serializable;
  25. import java.lang.reflect.Constructor;
  26. import java.lang.reflect.Method;
  27. import java.net.MalformedURLException;
  28. import java.net.URL;
  29. import java.util.ArrayList;
  30. import java.util.Collections;
  31. import java.util.HashMap;
  32. import java.util.List;
  33. import java.util.Locale;
  34. import java.util.Map;
  35. import java.util.Set;
  36. import java.util.concurrent.Future;
  37. import java.util.concurrent.TimeUnit;
  38. import java.util.concurrent.locks.Lock;
  39. import java.util.concurrent.locks.ReentrantLock;
  40. import java.util.logging.Level;
  41. import java.util.logging.Logger;
  42. import javax.portlet.Portlet;
  43. import javax.portlet.PortletContext;
  44. import javax.servlet.Servlet;
  45. import javax.servlet.ServletContext;
  46. import javax.servlet.http.HttpServletResponse;
  47. import com.vaadin.annotations.PreserveOnRefresh;
  48. import com.vaadin.event.EventRouter;
  49. import com.vaadin.server.VaadinSession.FutureAccess;
  50. import com.vaadin.server.VaadinSession.State;
  51. import com.vaadin.server.communication.FileUploadHandler;
  52. import com.vaadin.server.communication.HeartbeatHandler;
  53. import com.vaadin.server.communication.PublishedFileHandler;
  54. import com.vaadin.server.communication.SessionRequestHandler;
  55. import com.vaadin.server.communication.UidlRequestHandler;
  56. import com.vaadin.shared.ApplicationConstants;
  57. import com.vaadin.shared.JsonConstants;
  58. import com.vaadin.shared.ui.ui.UIConstants;
  59. import com.vaadin.ui.UI;
  60. import com.vaadin.util.CurrentInstance;
  61. import com.vaadin.util.ReflectTools;
  62. import elemental.json.Json;
  63. import elemental.json.JsonException;
  64. import elemental.json.JsonObject;
  65. import elemental.json.impl.JsonUtil;
  66. /**
  67. * Provide deployment specific settings that are required outside terminal
  68. * specific code.
  69. *
  70. * @author Vaadin Ltd.
  71. *
  72. * @since 7.0
  73. */
  74. public abstract class VaadinService implements Serializable {
  75. /**
  76. * Attribute name for telling
  77. * {@link VaadinSession#valueUnbound(javax.servlet.http.HttpSessionBindingEvent)}
  78. * that it should not close a {@link VaadinSession} even though it gets
  79. * unbound. If a {@code VaadinSession} has an attribute with this name and
  80. * the attribute value is {@link Boolean#TRUE}, that session will not be
  81. * closed when it is unbound from the underlying session.
  82. */
  83. // Use the old name.reinitializing value for backwards compatibility
  84. static final String PRESERVE_UNBOUND_SESSION_ATTRIBUTE = VaadinService.class
  85. .getName() + ".reinitializing";
  86. /**
  87. * @deprecated As of 7.1.1, use {@link #PRESERVE_UNBOUND_SESSION_ATTRIBUTE}
  88. * instead
  89. */
  90. @Deprecated
  91. static final String REINITIALIZING_SESSION_MARKER = PRESERVE_UNBOUND_SESSION_ATTRIBUTE;
  92. private static final Method SESSION_INIT_METHOD = ReflectTools.findMethod(
  93. SessionInitListener.class, "sessionInit", SessionInitEvent.class);
  94. private static final Method SESSION_DESTROY_METHOD = ReflectTools
  95. .findMethod(SessionDestroyListener.class, "sessionDestroy",
  96. SessionDestroyEvent.class);
  97. private static final Method SERVICE_DESTROY_METHOD = ReflectTools
  98. .findMethod(ServiceDestroyListener.class, "serviceDestroy",
  99. ServiceDestroyEvent.class);
  100. /**
  101. * @deprecated As of 7.0. Only supported for {@link LegacyApplication}.
  102. */
  103. @Deprecated
  104. public static final String URL_PARAMETER_RESTART_APPLICATION = "restartApplication";
  105. /**
  106. * @deprecated As of 7.0. Only supported for {@link LegacyApplication}.
  107. */
  108. @Deprecated
  109. public static final String URL_PARAMETER_CLOSE_APPLICATION = "closeApplication";
  110. private static final String REQUEST_START_TIME_ATTRIBUTE = "requestStartTime";
  111. private final DeploymentConfiguration deploymentConfiguration;
  112. private final EventRouter eventRouter = new EventRouter();
  113. private SystemMessagesProvider systemMessagesProvider = DefaultSystemMessagesProvider
  114. .get();
  115. private ClassLoader classLoader;
  116. private Iterable<RequestHandler> requestHandlers;
  117. /**
  118. * Keeps track of whether a warning about missing push support has already
  119. * been logged. This is used to avoid spamming the log with the same message
  120. * every time a new UI is bootstrapped.
  121. */
  122. private boolean pushWarningEmitted = false;
  123. /**
  124. * Has {@link #init()} been run?
  125. */
  126. private boolean initialized = false;
  127. /**
  128. * Creates a new vaadin service based on a deployment configuration
  129. *
  130. * @param deploymentConfiguration
  131. * the deployment configuration for the service
  132. */
  133. public VaadinService(DeploymentConfiguration deploymentConfiguration) {
  134. this.deploymentConfiguration = deploymentConfiguration;
  135. final String classLoaderName = getDeploymentConfiguration()
  136. .getApplicationOrSystemProperty("ClassLoader", null);
  137. if (classLoaderName != null) {
  138. try {
  139. final Class<?> classLoaderClass = getClass().getClassLoader()
  140. .loadClass(classLoaderName);
  141. final Constructor<?> c = classLoaderClass
  142. .getConstructor(new Class[] { ClassLoader.class });
  143. setClassLoader((ClassLoader) c
  144. .newInstance(new Object[] { getClass().getClassLoader() }));
  145. } catch (final Exception e) {
  146. throw new RuntimeException(
  147. "Could not find specified class loader: "
  148. + classLoaderName, e);
  149. }
  150. }
  151. }
  152. /**
  153. * Initializes this service. The service should be initialized before it is
  154. * used.
  155. *
  156. * @since 7.1
  157. * @throws ServiceException
  158. * if a problem occurs when creating the service
  159. */
  160. public void init() throws ServiceException {
  161. List<RequestHandler> handlers = createRequestHandlers();
  162. Collections.reverse(handlers);
  163. requestHandlers = Collections.unmodifiableCollection(handlers);
  164. initialized = true;
  165. }
  166. /**
  167. * Called during initialization to add the request handlers for the service.
  168. * Note that the returned list will be reversed so the last handler will be
  169. * called first. This enables overriding this method and using add on the
  170. * returned list to add a custom request handler which overrides any
  171. * predefined handler.
  172. *
  173. * @return The list of request handlers used by this service.
  174. * @throws ServiceException
  175. * if a problem occurs when creating the request handlers
  176. */
  177. protected List<RequestHandler> createRequestHandlers()
  178. throws ServiceException {
  179. ArrayList<RequestHandler> handlers = new ArrayList<RequestHandler>();
  180. handlers.add(new SessionRequestHandler());
  181. handlers.add(new PublishedFileHandler());
  182. handlers.add(new HeartbeatHandler());
  183. handlers.add(new FileUploadHandler());
  184. handlers.add(new UidlRequestHandler());
  185. handlers.add(new UnsupportedBrowserHandler());
  186. handlers.add(new ConnectorResourceHandler());
  187. return handlers;
  188. }
  189. /**
  190. * Return the URL from where static files, e.g. the widgetset and the theme,
  191. * are served. In a standard configuration the VAADIN folder inside the
  192. * returned folder is what is used for widgetsets and themes.
  193. *
  194. * The returned folder is usually the same as the context path and
  195. * independent of e.g. the servlet mapping.
  196. *
  197. * @param request
  198. * the request for which the location should be determined
  199. *
  200. * @return The location of static resources (should contain the VAADIN
  201. * directory). Never ends with a slash (/).
  202. */
  203. public abstract String getStaticFileLocation(VaadinRequest request);
  204. /**
  205. * Gets the widgetset that is configured for this deployment, e.g. from a
  206. * parameter in web.xml.
  207. *
  208. * @param request
  209. * the request for which a widgetset is required
  210. * @return the name of the widgetset
  211. */
  212. public abstract String getConfiguredWidgetset(VaadinRequest request);
  213. /**
  214. * Gets the theme that is configured for this deployment, e.g. from a portal
  215. * parameter or just some sensible default value.
  216. *
  217. * @param request
  218. * the request for which a theme is required
  219. * @return the name of the theme
  220. */
  221. public abstract String getConfiguredTheme(VaadinRequest request);
  222. /**
  223. * Checks whether the UI will be rendered on its own in the browser or
  224. * whether it will be included into some other context. A standalone UI may
  225. * do things that might interfere with other parts of a page, e.g. changing
  226. * the page title and requesting focus upon loading.
  227. *
  228. * @param request
  229. * the request for which the UI is loaded
  230. * @return a boolean indicating whether the UI should be standalone
  231. */
  232. public abstract boolean isStandalone(VaadinRequest request);
  233. /**
  234. * Gets the class loader to use for loading classes loaded by name, e.g.
  235. * custom UI classes. This is by default the class loader that was used to
  236. * load the Servlet or Portlet class to which this service belongs.
  237. *
  238. * @return the class loader to use, or <code>null</code>
  239. *
  240. * @see #setClassLoader(ClassLoader)
  241. */
  242. public ClassLoader getClassLoader() {
  243. return classLoader;
  244. }
  245. /**
  246. * Sets the class loader to use for loading classes loaded by name, e.g.
  247. * custom UI classes. Invokers of this method should be careful to not break
  248. * any existing class loader hierarchy, e.g. by ensuring that a class loader
  249. * set for this service delegates to the previously set class loader if the
  250. * class is not found.
  251. *
  252. * @param classLoader
  253. * the new class loader to set, not <code>null</code>.
  254. *
  255. * @see #getClassLoader()
  256. */
  257. public void setClassLoader(ClassLoader classLoader) {
  258. if (classLoader == null) {
  259. throw new IllegalArgumentException(
  260. "Can not set class loader to null");
  261. }
  262. this.classLoader = classLoader;
  263. }
  264. /**
  265. * Returns the MIME type of the specified file, or null if the MIME type is
  266. * not known. The MIME type is determined by the configuration of the
  267. * container, and may be specified in a deployment descriptor. Common MIME
  268. * types are "text/html" and "image/gif".
  269. *
  270. * @param resourceName
  271. * a String specifying the name of a file
  272. * @return a String specifying the file's MIME type
  273. *
  274. * @see ServletContext#getMimeType(String)
  275. * @see PortletContext#getMimeType(String)
  276. */
  277. public abstract String getMimeType(String resourceName);
  278. /**
  279. * Gets the deployment configuration.
  280. *
  281. * @return the deployment configuration
  282. */
  283. public DeploymentConfiguration getDeploymentConfiguration() {
  284. return deploymentConfiguration;
  285. }
  286. /**
  287. * Sets the system messages provider to use for getting system messages to
  288. * display to users of this service.
  289. *
  290. * @see #getSystemMessagesProvider()
  291. *
  292. * @param systemMessagesProvider
  293. * the system messages provider; <code>null</code> is not
  294. * allowed.
  295. */
  296. public void setSystemMessagesProvider(
  297. SystemMessagesProvider systemMessagesProvider) {
  298. if (systemMessagesProvider == null) {
  299. throw new IllegalArgumentException(
  300. "SystemMessagesProvider can not be null.");
  301. }
  302. this.systemMessagesProvider = systemMessagesProvider;
  303. }
  304. /**
  305. * Gets the system messages provider currently defined for this service.
  306. * <p>
  307. * By default, the {@link DefaultSystemMessagesProvider} which always
  308. * provides the built-in default {@link SystemMessages} is used.
  309. * </p>
  310. *
  311. * @see #setSystemMessagesProvider(SystemMessagesProvider)
  312. * @see SystemMessagesProvider
  313. * @see SystemMessages
  314. *
  315. * @return the system messages provider; not <code>null</code>
  316. */
  317. public SystemMessagesProvider getSystemMessagesProvider() {
  318. return systemMessagesProvider;
  319. }
  320. /**
  321. * Gets the system message to use for a specific locale. This method may
  322. * also be implemented to use information from current instances of various
  323. * objects, which means that this method might return different values for
  324. * the same locale under different circumstances.
  325. *
  326. * @param locale
  327. * the desired locale for the system messages
  328. * @param request
  329. * @return the system messages to use
  330. */
  331. public SystemMessages getSystemMessages(Locale locale, VaadinRequest request) {
  332. SystemMessagesInfo systemMessagesInfo = new SystemMessagesInfo();
  333. systemMessagesInfo.setLocale(locale);
  334. systemMessagesInfo.setService(this);
  335. systemMessagesInfo.setRequest(request);
  336. return getSystemMessagesProvider()
  337. .getSystemMessages(systemMessagesInfo);
  338. }
  339. /**
  340. * Returns the context base directory.
  341. *
  342. * Typically an application is deployed in a such way that is has an
  343. * application directory. For web applications this directory is the root
  344. * directory of the web applications. In some cases applications might not
  345. * have an application directory (for example web applications running
  346. * inside a war).
  347. *
  348. * @return The application base directory or null if the application has no
  349. * base directory.
  350. */
  351. public abstract File getBaseDirectory();
  352. /**
  353. * Adds a listener that gets notified when a new Vaadin service session is
  354. * initialized for this service.
  355. * <p>
  356. * Because of the way different service instances share the same session,
  357. * the listener is not necessarily notified immediately when the session is
  358. * created but only when the first request for that session is handled by
  359. * this service.
  360. *
  361. * @see #removeSessionInitListener(SessionInitListener)
  362. * @see SessionInitListener
  363. *
  364. * @param listener
  365. * the Vaadin service session initialization listener
  366. */
  367. public void addSessionInitListener(SessionInitListener listener) {
  368. eventRouter.addListener(SessionInitEvent.class, listener,
  369. SESSION_INIT_METHOD);
  370. }
  371. /**
  372. * Removes a Vaadin service session initialization listener from this
  373. * service.
  374. *
  375. * @see #addSessionInitListener(SessionInitListener)
  376. *
  377. * @param listener
  378. * the Vaadin service session initialization listener to remove.
  379. */
  380. public void removeSessionInitListener(SessionInitListener listener) {
  381. eventRouter.removeListener(SessionInitEvent.class, listener,
  382. SESSION_INIT_METHOD);
  383. }
  384. /**
  385. * Adds a listener that gets notified when a Vaadin service session that has
  386. * been initialized for this service is destroyed.
  387. * <p>
  388. * The session being destroyed is locked and its UIs have been removed when
  389. * the listeners are called.
  390. *
  391. * @see #addSessionInitListener(SessionInitListener)
  392. *
  393. * @param listener
  394. * the vaadin service session destroy listener
  395. */
  396. public void addSessionDestroyListener(SessionDestroyListener listener) {
  397. eventRouter.addListener(SessionDestroyEvent.class, listener,
  398. SESSION_DESTROY_METHOD);
  399. }
  400. /**
  401. * Handles destruction of the given session. Internally ensures proper
  402. * locking is done.
  403. *
  404. * @param vaadinSession
  405. * The session to destroy
  406. */
  407. public void fireSessionDestroy(VaadinSession vaadinSession) {
  408. final VaadinSession session = vaadinSession;
  409. session.accessSynchronously(new Runnable() {
  410. @Override
  411. public void run() {
  412. if (session.getState() == State.CLOSED) {
  413. return;
  414. }
  415. if (session.getState() == State.OPEN) {
  416. closeSession(session);
  417. }
  418. ArrayList<UI> uis = new ArrayList<UI>(session.getUIs());
  419. for (final UI ui : uis) {
  420. ui.accessSynchronously(new Runnable() {
  421. @Override
  422. public void run() {
  423. /*
  424. * close() called here for consistency so that it is
  425. * always called before a UI is removed.
  426. * UI.isClosing() is thus always true in UI.detach()
  427. * and associated detach listeners.
  428. */
  429. if (!ui.isClosing()) {
  430. ui.close();
  431. }
  432. session.removeUI(ui);
  433. }
  434. });
  435. }
  436. // for now, use the session error handler; in the future, could
  437. // have an API for using some other handler for session init and
  438. // destroy listeners
  439. eventRouter.fireEvent(new SessionDestroyEvent(
  440. VaadinService.this, session), session.getErrorHandler());
  441. session.setState(State.CLOSED);
  442. }
  443. });
  444. }
  445. /**
  446. * Removes a Vaadin service session destroy listener from this service.
  447. *
  448. * @see #addSessionDestroyListener(SessionDestroyListener)
  449. *
  450. * @param listener
  451. * the vaadin service session destroy listener
  452. */
  453. public void removeSessionDestroyListener(SessionDestroyListener listener) {
  454. eventRouter.removeListener(SessionDestroyEvent.class, listener,
  455. SESSION_DESTROY_METHOD);
  456. }
  457. /**
  458. * Attempts to find a Vaadin service session associated with this request.
  459. * <p>
  460. * Handles locking of the session internally to avoid creation of duplicate
  461. * sessions by two threads simultaneously.
  462. * </p>
  463. *
  464. * @param request
  465. * the request to get a vaadin service session for.
  466. *
  467. * @see VaadinSession
  468. *
  469. * @return the vaadin service session for the request, or <code>null</code>
  470. * if no session is found and this is a request for which a new
  471. * session shouldn't be created.
  472. */
  473. public VaadinSession findVaadinSession(VaadinRequest request)
  474. throws ServiceException, SessionExpiredException {
  475. VaadinSession vaadinSession = findOrCreateVaadinSession(request);
  476. if (vaadinSession == null) {
  477. return null;
  478. }
  479. VaadinSession.setCurrent(vaadinSession);
  480. request.setAttribute(VaadinSession.class.getName(), vaadinSession);
  481. return vaadinSession;
  482. }
  483. /**
  484. * Associates the given lock with this service and the given wrapped
  485. * session. This method should not be called more than once when the lock is
  486. * initialized for the session.
  487. *
  488. * @see #getSessionLock(WrappedSession)
  489. * @param wrappedSession
  490. * The wrapped session the lock is associated with
  491. * @param lock
  492. * The lock object
  493. */
  494. private void setSessionLock(WrappedSession wrappedSession, Lock lock) {
  495. if (wrappedSession == null) {
  496. throw new IllegalArgumentException(
  497. "Can't set a lock for a null session");
  498. }
  499. Object currentSessionLock = wrappedSession
  500. .getAttribute(getLockAttributeName());
  501. assert (currentSessionLock == null || currentSessionLock == lock) : "Changing the lock for a session is not allowed";
  502. wrappedSession.setAttribute(getLockAttributeName(), lock);
  503. }
  504. /**
  505. * Returns the name used to store the lock in the HTTP session.
  506. *
  507. * @return The attribute name for the lock
  508. */
  509. private String getLockAttributeName() {
  510. return getServiceName() + ".lock";
  511. }
  512. /**
  513. * Gets the lock instance used to lock the VaadinSession associated with the
  514. * given wrapped session.
  515. * <p>
  516. * This method uses the wrapped session instead of VaadinSession to be able
  517. * to lock even before the VaadinSession has been initialized.
  518. * </p>
  519. *
  520. * @param wrappedSession
  521. * The wrapped session
  522. * @return A lock instance used for locking access to the wrapped session
  523. */
  524. protected Lock getSessionLock(WrappedSession wrappedSession) {
  525. Object lock = wrappedSession.getAttribute(getLockAttributeName());
  526. if (lock instanceof ReentrantLock) {
  527. return (ReentrantLock) lock;
  528. }
  529. if (lock == null) {
  530. return null;
  531. }
  532. throw new RuntimeException(
  533. "Something else than a ReentrantLock was stored in the "
  534. + getLockAttributeName() + " in the session");
  535. }
  536. /**
  537. * Locks the given session for this service instance. Typically you want to
  538. * call {@link VaadinSession#lock()} instead of this method.
  539. *
  540. * @param wrappedSession
  541. * The session to lock
  542. *
  543. * @throws IllegalStateException
  544. * if the session is invalidated before it can be locked
  545. */
  546. protected void lockSession(WrappedSession wrappedSession) {
  547. Lock lock = getSessionLock(wrappedSession);
  548. if (lock == null) {
  549. /*
  550. * No lock found in the session attribute. Ensure only one lock is
  551. * created and used by everybody by doing double checked locking.
  552. * Assumes there is a memory barrier for the attribute (i.e. that
  553. * the CPU flushes its caches and reads the value directly from main
  554. * memory).
  555. */
  556. synchronized (VaadinService.class) {
  557. lock = getSessionLock(wrappedSession);
  558. if (lock == null) {
  559. lock = new ReentrantLock();
  560. setSessionLock(wrappedSession, lock);
  561. }
  562. }
  563. }
  564. lock.lock();
  565. try {
  566. // Someone might have invalidated the session between fetching the
  567. // lock and acquiring it. Guard for this by calling a method that's
  568. // specified to throw IllegalStateException if invalidated
  569. // (#12282)
  570. wrappedSession.getAttribute(getLockAttributeName());
  571. } catch (IllegalStateException e) {
  572. lock.unlock();
  573. throw e;
  574. }
  575. }
  576. /**
  577. * Releases the lock for the given session for this service instance.
  578. * Typically you want to call {@link VaadinSession#unlock()} instead of this
  579. * method.
  580. *
  581. * @param wrappedSession
  582. * The session to unlock
  583. */
  584. protected void unlockSession(WrappedSession wrappedSession) {
  585. assert getSessionLock(wrappedSession) != null;
  586. assert ((ReentrantLock) getSessionLock(wrappedSession))
  587. .isHeldByCurrentThread() : "Trying to unlock the session but it has not been locked by this thread";
  588. getSessionLock(wrappedSession).unlock();
  589. }
  590. private VaadinSession findOrCreateVaadinSession(VaadinRequest request)
  591. throws SessionExpiredException, ServiceException {
  592. boolean requestCanCreateSession = requestCanCreateSession(request);
  593. WrappedSession wrappedSession = getWrappedSession(request,
  594. requestCanCreateSession);
  595. try {
  596. lockSession(wrappedSession);
  597. } catch (IllegalStateException e) {
  598. throw new SessionExpiredException();
  599. }
  600. try {
  601. return doFindOrCreateVaadinSession(request, requestCanCreateSession);
  602. } finally {
  603. unlockSession(wrappedSession);
  604. }
  605. }
  606. /**
  607. * Finds or creates a Vaadin session. Assumes necessary synchronization has
  608. * been done by the caller to ensure this is not called simultaneously by
  609. * several threads.
  610. *
  611. * @param request
  612. * @param requestCanCreateSession
  613. * @return
  614. * @throws SessionExpiredException
  615. * @throws ServiceException
  616. */
  617. private VaadinSession doFindOrCreateVaadinSession(VaadinRequest request,
  618. boolean requestCanCreateSession) throws SessionExpiredException,
  619. ServiceException {
  620. assert ((ReentrantLock) getSessionLock(request.getWrappedSession()))
  621. .isHeldByCurrentThread() : "Session has not been locked by this thread";
  622. /* Find an existing session for this request. */
  623. VaadinSession session = getExistingSession(request,
  624. requestCanCreateSession);
  625. if (session != null) {
  626. /*
  627. * There is an existing session. We can use this as long as the user
  628. * not specifically requested to close or restart it.
  629. */
  630. final boolean restartApplication = hasParameter(request,
  631. URL_PARAMETER_RESTART_APPLICATION)
  632. && !hasParameter(request,
  633. BootstrapHandler.IGNORE_RESTART_PARAM);
  634. final boolean closeApplication = hasParameter(request,
  635. URL_PARAMETER_CLOSE_APPLICATION);
  636. if (restartApplication) {
  637. closeSession(session, request.getWrappedSession(false));
  638. return createAndRegisterSession(request);
  639. } else if (closeApplication) {
  640. closeSession(session, request.getWrappedSession(false));
  641. return null;
  642. } else {
  643. return session;
  644. }
  645. }
  646. // No existing session was found
  647. if (requestCanCreateSession) {
  648. /*
  649. * If the request is such that it should create a new session if one
  650. * as not found, we do that.
  651. */
  652. return createAndRegisterSession(request);
  653. } else {
  654. /*
  655. * The session was not found and a new one should not be created.
  656. * Assume the session has expired.
  657. */
  658. throw new SessionExpiredException();
  659. }
  660. }
  661. private static boolean hasParameter(VaadinRequest request,
  662. String parameterName) {
  663. return request.getParameter(parameterName) != null;
  664. }
  665. /**
  666. * Creates and registers a new VaadinSession for this service. Assumes
  667. * proper locking has been taken care of by the caller.
  668. *
  669. *
  670. * @param request
  671. * The request which triggered session creation.
  672. * @return A new VaadinSession instance
  673. * @throws ServiceException
  674. */
  675. private VaadinSession createAndRegisterSession(VaadinRequest request)
  676. throws ServiceException {
  677. assert ((ReentrantLock) getSessionLock(request.getWrappedSession()))
  678. .isHeldByCurrentThread() : "Session has not been locked by this thread";
  679. VaadinSession session = createVaadinSession(request);
  680. VaadinSession.setCurrent(session);
  681. session.storeInSession(this, request.getWrappedSession());
  682. // Initial WebBrowser data comes from the request
  683. session.getBrowser().updateRequestDetails(request);
  684. // Initial locale comes from the request
  685. Locale locale = request.getLocale();
  686. session.setLocale(locale);
  687. session.setConfiguration(getDeploymentConfiguration());
  688. session.setCommunicationManager(new LegacyCommunicationManager(session));
  689. ServletPortletHelper.initDefaultUIProvider(session, this);
  690. onVaadinSessionStarted(request, session);
  691. return session;
  692. }
  693. /**
  694. * Get the base URL that should be used for sending requests back to this
  695. * service.
  696. * <p>
  697. * This is only used to support legacy cases.
  698. *
  699. * @param request
  700. * @return
  701. * @throws MalformedURLException
  702. *
  703. * @deprecated As of 7.0. Only used to support {@link LegacyApplication}.
  704. */
  705. @Deprecated
  706. protected URL getApplicationUrl(VaadinRequest request)
  707. throws MalformedURLException {
  708. return null;
  709. }
  710. /**
  711. * Creates a new Vaadin session for this service and request
  712. *
  713. * @param request
  714. * The request for which to create a VaadinSession
  715. * @return A new VaadinSession
  716. * @throws ServiceException
  717. *
  718. */
  719. protected VaadinSession createVaadinSession(VaadinRequest request)
  720. throws ServiceException {
  721. return new VaadinSession(this);
  722. }
  723. private void onVaadinSessionStarted(VaadinRequest request,
  724. VaadinSession session) throws ServiceException {
  725. // for now, use the session error handler; in the future, could have an
  726. // API for using some other handler for session init and destroy
  727. // listeners
  728. eventRouter.fireEvent(new SessionInitEvent(this, session, request),
  729. session.getErrorHandler());
  730. ServletPortletHelper.checkUiProviders(session, this);
  731. }
  732. private void closeSession(VaadinSession vaadinSession,
  733. WrappedSession session) {
  734. if (vaadinSession == null) {
  735. return;
  736. }
  737. if (session != null) {
  738. vaadinSession.removeFromSession(this);
  739. }
  740. }
  741. protected VaadinSession getExistingSession(VaadinRequest request,
  742. boolean allowSessionCreation) throws SessionExpiredException {
  743. final WrappedSession session = getWrappedSession(request,
  744. allowSessionCreation);
  745. VaadinSession vaadinSession = VaadinSession
  746. .getForSession(this, session);
  747. if (vaadinSession == null) {
  748. return null;
  749. }
  750. return vaadinSession;
  751. }
  752. /**
  753. * Retrieves the wrapped session for the request.
  754. *
  755. * @param request
  756. * The request for which to retrieve a session
  757. * @param requestCanCreateSession
  758. * true to create a new session if one currently does not exist
  759. * @return The retrieved (or created) wrapped session
  760. * @throws SessionExpiredException
  761. * If the request is not associated to a session and new session
  762. * creation is not allowed
  763. */
  764. private WrappedSession getWrappedSession(VaadinRequest request,
  765. boolean requestCanCreateSession) throws SessionExpiredException {
  766. final WrappedSession session = request
  767. .getWrappedSession(requestCanCreateSession);
  768. if (session == null) {
  769. throw new SessionExpiredException();
  770. }
  771. return session;
  772. }
  773. /**
  774. * Checks whether it's valid to create a new service session as a result of
  775. * the given request.
  776. *
  777. * @param request
  778. * the request
  779. * @return <code>true</code> if it's valid to create a new service session
  780. * for the request; else <code>false</code>
  781. */
  782. protected abstract boolean requestCanCreateSession(VaadinRequest request);
  783. /**
  784. * Gets the currently used Vaadin service. The current service is
  785. * automatically defined when processing requests related to the service and
  786. * in threads started at a point when the current service is defined (see
  787. * {@link InheritableThreadLocal}). In other cases, (e.g. from background
  788. * threads started in some other way), the current service is not
  789. * automatically defined.
  790. *
  791. * @return the current Vaadin service instance if available, otherwise
  792. * <code>null</code>
  793. *
  794. * @see #setCurrentInstances(VaadinRequest, VaadinResponse)
  795. */
  796. public static VaadinService getCurrent() {
  797. return CurrentInstance.get(VaadinService.class);
  798. }
  799. /**
  800. * Sets the this Vaadin service as the current service and also sets the
  801. * current Vaadin request and Vaadin response. This method is used by the
  802. * framework to set the current instances when a request related to the
  803. * service is processed and they are cleared when the request has been
  804. * processed.
  805. * <p>
  806. * The application developer can also use this method to define the current
  807. * instances outside the normal request handling, e.g. when initiating
  808. * custom background threads.
  809. * </p>
  810. *
  811. * @param request
  812. * the Vaadin request to set as the current request, or
  813. * <code>null</code> if no request should be set.
  814. * @param response
  815. * the Vaadin response to set as the current response, or
  816. * <code>null</code> if no response should be set.
  817. *
  818. * @see #getCurrent()
  819. * @see #getCurrentRequest()
  820. * @see #getCurrentResponse()
  821. */
  822. public void setCurrentInstances(VaadinRequest request,
  823. VaadinResponse response) {
  824. setCurrent(this);
  825. CurrentInstance.set(VaadinRequest.class, request);
  826. CurrentInstance.set(VaadinResponse.class, response);
  827. }
  828. /**
  829. * Sets the given Vaadin service as the current service.
  830. *
  831. * @param service
  832. */
  833. public static void setCurrent(VaadinService service) {
  834. CurrentInstance.setInheritable(VaadinService.class, service);
  835. }
  836. /**
  837. * Gets the currently processed Vaadin request. The current request is
  838. * automatically defined when the request is started. The current request
  839. * can not be used in e.g. background threads because of the way server
  840. * implementations reuse request instances.
  841. *
  842. * @return the current Vaadin request instance if available, otherwise
  843. * <code>null</code>
  844. *
  845. * @see #setCurrentInstances(VaadinRequest, VaadinResponse)
  846. */
  847. public static VaadinRequest getCurrentRequest() {
  848. return CurrentInstance.get(VaadinRequest.class);
  849. }
  850. /**
  851. * Gets the currently processed Vaadin response. The current response is
  852. * automatically defined when the request is started. The current response
  853. * can not be used in e.g. background threads because of the way server
  854. * implementations reuse response instances.
  855. *
  856. * @return the current Vaadin response instance if available, otherwise
  857. * <code>null</code>
  858. *
  859. * @see #setCurrentInstances(VaadinRequest, VaadinResponse)
  860. */
  861. public static VaadinResponse getCurrentResponse() {
  862. return CurrentInstance.get(VaadinResponse.class);
  863. }
  864. /**
  865. * Gets a unique name for this service. The name should be unique among
  866. * different services of the same type but the same for corresponding
  867. * instances running in different JVMs in a cluster. This is typically based
  868. * on e.g. the configured servlet's or portlet's name.
  869. *
  870. * @return the unique name of this service instance.
  871. */
  872. public abstract String getServiceName();
  873. /**
  874. * Finds the {@link UI} that belongs to the provided request. This is
  875. * generally only supported for UIDL requests as other request types are not
  876. * related to any particular UI or have the UI information encoded in a
  877. * non-standard way. The returned UI is also set as the current UI (
  878. * {@link UI#setCurrent(UI)}).
  879. *
  880. * @param request
  881. * the request for which a UI is desired
  882. * @return the UI belonging to the request or null if no UI is found
  883. *
  884. */
  885. public UI findUI(VaadinRequest request) {
  886. // getForSession asserts that the lock is held
  887. VaadinSession session = VaadinSession.getForSession(this,
  888. request.getWrappedSession());
  889. // Get UI id from the request
  890. String uiIdString = request.getParameter(UIConstants.UI_ID_PARAMETER);
  891. UI ui = null;
  892. if (uiIdString != null && session != null) {
  893. int uiId = Integer.parseInt(uiIdString);
  894. ui = session.getUIById(uiId);
  895. }
  896. UI.setCurrent(ui);
  897. return ui;
  898. }
  899. /**
  900. * Check if the given UI should be associated with the
  901. * <code>window.name</code> so that it can be re-used if the browser window
  902. * is reloaded. This is typically determined by the UI provider which
  903. * typically checks the @{@link PreserveOnRefresh} annotation but UI
  904. * providers and ultimately VaadinService implementations may choose to
  905. * override the defaults.
  906. *
  907. * @param provider
  908. * the UI provider responsible for the UI
  909. * @param event
  910. * the UI create event with details about the UI
  911. *
  912. * @return <code>true</code> if the UI should be preserved on refresh;
  913. * <code>false</code> if a new UI instance should be initialized on
  914. * refreshed.
  915. */
  916. public boolean preserveUIOnRefresh(UIProvider provider, UICreateEvent event) {
  917. return provider.isPreservedOnRefresh(event);
  918. }
  919. /**
  920. * Discards the current session and creates a new session with the same
  921. * contents. The purpose of this is to introduce a new session key in order
  922. * to avoid session fixation attacks.
  923. * <p>
  924. * Please note that this method makes certain assumptions about how data is
  925. * stored in the underlying session and may thus not be compatible with some
  926. * environments.
  927. *
  928. * @param request
  929. * The Vaadin request for which the session should be
  930. * reinitialized
  931. */
  932. public static void reinitializeSession(VaadinRequest request) {
  933. WrappedSession oldSession = request.getWrappedSession();
  934. // Stores all attributes (security key, reference to this context
  935. // instance) so they can be added to the new session
  936. Set<String> attributeNames = oldSession.getAttributeNames();
  937. HashMap<String, Object> attrs = new HashMap<String, Object>(
  938. attributeNames.size() * 2);
  939. for (String name : attributeNames) {
  940. Object value = oldSession.getAttribute(name);
  941. if (value instanceof VaadinSession) {
  942. // set flag to avoid cleanup
  943. VaadinSession serviceSession = (VaadinSession) value;
  944. serviceSession.setAttribute(PRESERVE_UNBOUND_SESSION_ATTRIBUTE,
  945. Boolean.TRUE);
  946. }
  947. attrs.put(name, value);
  948. }
  949. // Invalidate the current session
  950. oldSession.invalidate();
  951. // Create a new session
  952. WrappedSession newSession = request.getWrappedSession();
  953. // Restores all attributes (security key, reference to this context
  954. // instance)
  955. for (String name : attrs.keySet()) {
  956. Object value = attrs.get(name);
  957. newSession.setAttribute(name, value);
  958. // Ensure VaadinServiceSession knows where it's stored
  959. if (value instanceof VaadinSession) {
  960. VaadinSession serviceSession = (VaadinSession) value;
  961. VaadinService service = serviceSession.getService();
  962. // Use the same lock instance in the new session
  963. service.setSessionLock(newSession,
  964. serviceSession.getLockInstance());
  965. serviceSession.storeInSession(service, newSession);
  966. serviceSession.setAttribute(PRESERVE_UNBOUND_SESSION_ATTRIBUTE,
  967. null);
  968. }
  969. }
  970. }
  971. /**
  972. * TODO PUSH Document
  973. *
  974. * TODO Pass UI or VaadinSession?
  975. *
  976. * @param uI
  977. * @param themeName
  978. * @param resource
  979. * @return
  980. */
  981. public abstract InputStream getThemeResourceAsStream(UI uI,
  982. String themeName, String resource);
  983. /**
  984. * Creates and returns a unique ID for the DIV where the UI is to be
  985. * rendered.
  986. *
  987. * @param session
  988. * The service session to which the bootstrapped UI will belong.
  989. * @param request
  990. * The request for which a div id is needed
  991. * @param uiClass
  992. * The class of the UI that will be bootstrapped
  993. *
  994. * @return the id to use in the DOM
  995. */
  996. public abstract String getMainDivId(VaadinSession session,
  997. VaadinRequest request, Class<? extends UI> uiClass);
  998. /**
  999. * Sets the given session to be closed and all its UI state to be discarded
  1000. * at the end of the current request, or at the end of the next request if
  1001. * there is no ongoing one.
  1002. * <p>
  1003. * After the session has been discarded, any UIs that have been left open
  1004. * will give a Session Expired error and a new session will be created for
  1005. * serving new UIs.
  1006. * <p>
  1007. * To avoid causing out of sync errors, you should typically redirect to
  1008. * some other page using {@link Page#setLocation(String)} to make the
  1009. * browser unload the invalidated UI.
  1010. *
  1011. * @see SystemMessages#getSessionExpiredCaption()
  1012. *
  1013. * @param session
  1014. * the session to close
  1015. */
  1016. public void closeSession(VaadinSession session) {
  1017. session.close();
  1018. }
  1019. /**
  1020. * Called at the end of a request, after sending the response. Closes
  1021. * inactive UIs in the given session, removes closed UIs from the session,
  1022. * and closes the session if it is itself inactive.
  1023. *
  1024. * @param session
  1025. */
  1026. void cleanupSession(VaadinSession session) {
  1027. if (isSessionActive(session)) {
  1028. closeInactiveUIs(session);
  1029. removeClosedUIs(session);
  1030. } else {
  1031. if (session.getState() == State.OPEN) {
  1032. closeSession(session);
  1033. if (session.getSession() != null) {
  1034. getLogger().log(Level.FINE, "Closing inactive session {0}",
  1035. session.getSession().getId());
  1036. }
  1037. }
  1038. if (session.getSession() != null) {
  1039. /*
  1040. * If the VaadinSession has no WrappedSession then it has
  1041. * already been removed from the HttpSession and we do not have
  1042. * to do it again
  1043. */
  1044. session.removeFromSession(this);
  1045. }
  1046. /*
  1047. * The session was destroyed during this request and therefore no
  1048. * destroy event has yet been sent
  1049. */
  1050. fireSessionDestroy(session);
  1051. }
  1052. }
  1053. /**
  1054. * Removes those UIs from the given session for which {@link UI#isClosing()
  1055. * isClosing} yields true.
  1056. *
  1057. * @param session
  1058. */
  1059. private void removeClosedUIs(final VaadinSession session) {
  1060. ArrayList<UI> uis = new ArrayList<UI>(session.getUIs());
  1061. for (final UI ui : uis) {
  1062. if (ui.isClosing()) {
  1063. ui.accessSynchronously(new Runnable() {
  1064. @Override
  1065. public void run() {
  1066. getLogger().log(Level.FINER, "Removing closed UI {0}",
  1067. ui.getUIId());
  1068. session.removeUI(ui);
  1069. }
  1070. });
  1071. }
  1072. }
  1073. }
  1074. /**
  1075. * Closes those UIs in the given session for which {@link #isUIActive}
  1076. * yields false.
  1077. *
  1078. * @since 7.0.0
  1079. */
  1080. private void closeInactiveUIs(VaadinSession session) {
  1081. final String sessionId = session.getSession().getId();
  1082. for (final UI ui : session.getUIs()) {
  1083. if (!isUIActive(ui) && !ui.isClosing()) {
  1084. ui.accessSynchronously(new Runnable() {
  1085. @Override
  1086. public void run() {
  1087. getLogger().log(Level.FINE,
  1088. "Closing inactive UI #{0} in session {1}",
  1089. new Object[] { ui.getUIId(), sessionId });
  1090. ui.close();
  1091. }
  1092. });
  1093. }
  1094. }
  1095. }
  1096. /**
  1097. * Returns the number of seconds that must pass without a valid heartbeat or
  1098. * UIDL request being received from a UI before that UI is removed from its
  1099. * session. This is a lower bound; it might take longer to close an inactive
  1100. * UI. Returns a negative number if heartbeat is disabled and timeout never
  1101. * occurs.
  1102. *
  1103. * @see DeploymentConfiguration#getHeartbeatInterval()
  1104. *
  1105. * @since 7.0.0
  1106. *
  1107. * @return The heartbeat timeout in seconds or a negative number if timeout
  1108. * never occurs.
  1109. */
  1110. private int getHeartbeatTimeout() {
  1111. // Permit three missed heartbeats before closing the UI
  1112. return (int) (getDeploymentConfiguration().getHeartbeatInterval() * (3.1));
  1113. }
  1114. /**
  1115. * Returns the number of seconds that must pass without a valid UIDL request
  1116. * being received for the given session before the session is closed, even
  1117. * though heartbeat requests are received. This is a lower bound; it might
  1118. * take longer to close an inactive session.
  1119. * <p>
  1120. * Returns a negative number if there is no timeout. In this case heartbeat
  1121. * requests suffice to keep the session alive, but it will still eventually
  1122. * expire in the regular manner if there are no requests at all (see
  1123. * {@link WrappedSession#getMaxInactiveInterval()}).
  1124. *
  1125. * @see DeploymentConfiguration#isCloseIdleSessions()
  1126. * @see #getHeartbeatTimeout()
  1127. *
  1128. * @since 7.0.0
  1129. *
  1130. * @return The UIDL request timeout in seconds, or a negative number if
  1131. * timeout never occurs.
  1132. */
  1133. private int getUidlRequestTimeout(VaadinSession session) {
  1134. return getDeploymentConfiguration().isCloseIdleSessions() ? session
  1135. .getSession().getMaxInactiveInterval() : -1;
  1136. }
  1137. /**
  1138. * Returns whether the given UI is active (the client-side actively
  1139. * communicates with the server) or whether it can be removed from the
  1140. * session and eventually collected.
  1141. * <p>
  1142. * A UI is active if and only if its {@link UI#isClosing() isClosing}
  1143. * returns false and {@link #getHeartbeatTimeout() getHeartbeatTimeout} is
  1144. * negative or has not yet expired.
  1145. *
  1146. * @since 7.0.0
  1147. *
  1148. * @param ui
  1149. * The UI whose status to check
  1150. *
  1151. * @return true if the UI is active, false if it could be removed.
  1152. */
  1153. private boolean isUIActive(UI ui) {
  1154. if (ui.isClosing()) {
  1155. return false;
  1156. } else {
  1157. long now = System.currentTimeMillis();
  1158. int timeout = 1000 * getHeartbeatTimeout();
  1159. return timeout < 0
  1160. || now - ui.getLastHeartbeatTimestamp() < timeout;
  1161. }
  1162. }
  1163. /**
  1164. * Returns whether the given session is active or whether it can be closed.
  1165. * <p>
  1166. * A session is active if and only if its {@link #isClosing} returns false
  1167. * and {@link #getUidlRequestTimeout(VaadinSession) getUidlRequestTimeout}
  1168. * is negative or has not yet expired.
  1169. *
  1170. * @param session
  1171. * The session whose status to check
  1172. *
  1173. * @return true if the session is active, false if it could be closed.
  1174. */
  1175. private boolean isSessionActive(VaadinSession session) {
  1176. if (session.getState() != State.OPEN || session.getSession() == null) {
  1177. return false;
  1178. } else {
  1179. long now = System.currentTimeMillis();
  1180. int timeout = 1000 * getUidlRequestTimeout(session);
  1181. return timeout < 0
  1182. || now - session.getLastRequestTimestamp() < timeout;
  1183. }
  1184. }
  1185. private static final Logger getLogger() {
  1186. return Logger.getLogger(VaadinService.class.getName());
  1187. }
  1188. /**
  1189. * Called before the framework starts handling a request
  1190. *
  1191. * @param request
  1192. * The request
  1193. * @param response
  1194. * The response
  1195. */
  1196. public void requestStart(VaadinRequest request, VaadinResponse response) {
  1197. if (!initialized) {
  1198. throw new IllegalStateException(
  1199. "Can not process requests before init() has been called");
  1200. }
  1201. setCurrentInstances(request, response);
  1202. request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime());
  1203. }
  1204. /**
  1205. * Called after the framework has handled a request and the response has
  1206. * been written.
  1207. *
  1208. * @param request
  1209. * The request object
  1210. * @param response
  1211. * The response object
  1212. * @param session
  1213. * The session which was used during the request or null if the
  1214. * request did not use a session
  1215. */
  1216. public void requestEnd(VaadinRequest request, VaadinResponse response,
  1217. VaadinSession session) {
  1218. if (session != null) {
  1219. assert VaadinSession.getCurrent() == session;
  1220. session.lock();
  1221. try {
  1222. cleanupSession(session);
  1223. final long duration = (System.nanoTime() - (Long) request
  1224. .getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000;
  1225. session.setLastRequestDuration(duration);
  1226. } finally {
  1227. session.unlock();
  1228. }
  1229. }
  1230. CurrentInstance.clearAll();
  1231. }
  1232. /**
  1233. * Returns the request handlers that are registered with this service. The
  1234. * iteration order of the returned collection is the same as the order in
  1235. * which the request handlers will be invoked when a request is handled.
  1236. *
  1237. * @return a collection of request handlers in the order they are invoked
  1238. *
  1239. * @see #createRequestHandlers()
  1240. *
  1241. * @since 7.1
  1242. */
  1243. public Iterable<RequestHandler> getRequestHandlers() {
  1244. return requestHandlers;
  1245. }
  1246. /**
  1247. * Handles the incoming request and writes the response into the response
  1248. * object. Uses {@link #getRequestHandlers()} for handling the request.
  1249. * <p>
  1250. * If a session expiration is detected during request handling then each
  1251. * {@link RequestHandler request handler} has an opportunity to handle the
  1252. * expiration event if it implements {@link SessionExpiredHandler}. If no
  1253. * request handler handles session expiration a default expiration message
  1254. * will be written.
  1255. * </p>
  1256. *
  1257. * @param request
  1258. * The incoming request
  1259. * @param response
  1260. * The outgoing response
  1261. * @throws ServiceException
  1262. * Any exception that occurs during response handling will be
  1263. * wrapped in a ServiceException
  1264. */
  1265. public void handleRequest(VaadinRequest request, VaadinResponse response)
  1266. throws ServiceException {
  1267. requestStart(request, response);
  1268. VaadinSession vaadinSession = null;
  1269. try {
  1270. // Find out the service session this request is related to
  1271. vaadinSession = findVaadinSession(request);
  1272. if (vaadinSession == null) {
  1273. return;
  1274. }
  1275. for (RequestHandler handler : getRequestHandlers()) {
  1276. if (handler.handleRequest(vaadinSession, request, response)) {
  1277. return;
  1278. }
  1279. }
  1280. // Request not handled by any RequestHandler
  1281. response.sendError(HttpServletResponse.SC_NOT_FOUND,
  1282. "Request was not handled by any registered handler.");
  1283. } catch (final SessionExpiredException e) {
  1284. handleSessionExpired(request, response);
  1285. } catch (final Throwable e) {
  1286. handleExceptionDuringRequest(request, response, vaadinSession, e);
  1287. } finally {
  1288. requestEnd(request, response, vaadinSession);
  1289. }
  1290. }
  1291. private void handleExceptionDuringRequest(VaadinRequest request,
  1292. VaadinResponse response, VaadinSession vaadinSession, Throwable t)
  1293. throws ServiceException {
  1294. if (vaadinSession != null) {
  1295. vaadinSession.lock();
  1296. }
  1297. try {
  1298. ErrorHandler errorHandler = ErrorEvent
  1299. .findErrorHandler(vaadinSession);
  1300. // if this was an UIDL request, send UIDL back to the client
  1301. if (ServletPortletHelper.isUIDLRequest(request)) {
  1302. SystemMessages ci = getSystemMessages(
  1303. ServletPortletHelper.findLocale(null, vaadinSession,
  1304. request), request);
  1305. try {
  1306. writeStringResponse(
  1307. response,
  1308. JsonConstants.JSON_CONTENT_TYPE,
  1309. createCriticalNotificationJSON(
  1310. ci.getInternalErrorCaption(),
  1311. ci.getInternalErrorMessage(), null,
  1312. ci.getInternalErrorURL()));
  1313. } catch (IOException e) {
  1314. // An exception occured while writing the response. Log
  1315. // it and continue handling only the original error.
  1316. getLogger()
  1317. .log(Level.WARNING,
  1318. "Failed to write critical notification response to the client",
  1319. e);
  1320. }
  1321. if (errorHandler != null) {
  1322. errorHandler.error(new ErrorEvent(t));
  1323. }
  1324. } else {
  1325. if (errorHandler != null) {
  1326. errorHandler.error(new ErrorEvent(t));
  1327. }
  1328. // Re-throw other exceptions
  1329. throw new ServiceException(t);
  1330. }
  1331. } finally {
  1332. if (vaadinSession != null) {
  1333. vaadinSession.unlock();
  1334. }
  1335. }
  1336. }
  1337. /**
  1338. * Writes the given string as a response using the given content type.
  1339. *
  1340. * @param response
  1341. * The response reference
  1342. * @param contentType
  1343. * The content type of the response
  1344. * @param reponseString
  1345. * The actual response
  1346. * @throws IOException
  1347. * If an error occured while writing the response
  1348. */
  1349. public void writeStringResponse(VaadinResponse response,
  1350. String contentType, String reponseString) throws IOException {
  1351. response.setContentType(contentType);
  1352. final OutputStream out = response.getOutputStream();
  1353. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  1354. new OutputStreamWriter(out, "UTF-8")));
  1355. outWriter.print(reponseString);
  1356. outWriter.close();
  1357. }
  1358. /**
  1359. * Called when the session has expired and the request handling is therefore
  1360. * aborted.
  1361. *
  1362. * @param request
  1363. * The request
  1364. * @param response
  1365. * The response
  1366. * @throws ServiceException
  1367. * Thrown if there was any problem handling the expiration of
  1368. * the session
  1369. */
  1370. protected void handleSessionExpired(VaadinRequest request,
  1371. VaadinResponse response) throws ServiceException {
  1372. for (RequestHandler handler : getRequestHandlers()) {
  1373. if (handler instanceof SessionExpiredHandler) {
  1374. try {
  1375. if (((SessionExpiredHandler) handler).handleSessionExpired(
  1376. request, response)) {
  1377. return;
  1378. }
  1379. } catch (IOException e) {
  1380. throw new ServiceException(
  1381. "Handling of session expired failed", e);
  1382. }
  1383. }
  1384. }
  1385. // No request handlers handled the request. Write a normal HTTP response
  1386. try {
  1387. // If there is a URL, try to redirect there
  1388. SystemMessages systemMessages = getSystemMessages(
  1389. ServletPortletHelper.findLocale(null, null, request),
  1390. request);
  1391. String sessionExpiredURL = systemMessages.getSessionExpiredURL();
  1392. if (sessionExpiredURL != null
  1393. && (response instanceof VaadinServletResponse)) {
  1394. ((VaadinServletResponse) response)
  1395. .sendRedirect(sessionExpiredURL);
  1396. } else {
  1397. /*
  1398. * Session expired as a result of a standard http request and we
  1399. * have nowhere to redirect. Reloading would likely cause an
  1400. * endless loop. This can at least happen if refreshing a
  1401. * resource when the session has expired.
  1402. */
  1403. response.sendError(HttpServletResponse.SC_GONE,
  1404. "Session expired");
  1405. }
  1406. } catch (IOException e) {
  1407. throw new ServiceException(e);
  1408. }
  1409. }
  1410. /**
  1411. * Creates a JSON message which, when sent to client as-is, will cause a
  1412. * critical error to be shown with the given details.
  1413. *
  1414. * @param caption
  1415. * The caption of the error or null to omit
  1416. * @param message
  1417. * The error message or null to omit
  1418. * @param details
  1419. * Additional error details or null to omit
  1420. * @param url
  1421. * A url to redirect to. If no other details are given then the
  1422. * user will be immediately redirected to this URL. Otherwise the
  1423. * message will be shown and the browser will redirect to the
  1424. * given URL only after the user acknowledges the message. If
  1425. * null then the browser will refresh the current page.
  1426. * @return A JSON string to be sent to the client
  1427. */
  1428. public static String createCriticalNotificationJSON(String caption,
  1429. String message, String details, String url) {
  1430. String returnString = "";
  1431. try {
  1432. if (message == null) {
  1433. message = details;
  1434. } else if (details != null) {
  1435. message += "<br/><br/>" + details;
  1436. }
  1437. JsonObject appError = Json.createObject();
  1438. appError.put("caption", caption);
  1439. appError.put("message", message);
  1440. appError.put("url", url);
  1441. JsonObject meta = Json.createObject();
  1442. meta.put("appError", appError);
  1443. JsonObject json = Json.createObject();
  1444. json.put("changes", Json.createObject());
  1445. json.put("resources", Json.createObject());
  1446. json.put("locales", Json.createObject());
  1447. json.put("meta", meta);
  1448. json.put(ApplicationConstants.SERVER_SYNC_ID, -1);
  1449. returnString = JsonUtil.stringify(json);
  1450. } catch (JsonException e) {
  1451. getLogger().log(Level.WARNING,
  1452. "Error creating critical notification JSON message", e);
  1453. }
  1454. return "for(;;);[" + returnString + "]";
  1455. }
  1456. /**
  1457. * @deprecated As of 7.0. Will likely change or be removed in a future
  1458. * version
  1459. */
  1460. @Deprecated
  1461. public void criticalNotification(VaadinRequest request,
  1462. VaadinResponse response, String caption, String message,
  1463. String details, String url) throws IOException {
  1464. writeStringResponse(response, JsonConstants.JSON_CONTENT_TYPE,
  1465. createCriticalNotificationJSON(caption, message, details, url));
  1466. }
  1467. /**
  1468. * Enables push if push support is available and push has not yet been
  1469. * enabled.
  1470. *
  1471. * If push support is not available, a warning explaining the situation will
  1472. * be logged at least the first time this method is invoked.
  1473. *
  1474. * @return <code>true</code> if push can be used; <code>false</code> if push
  1475. * is not available.
  1476. */
  1477. public boolean ensurePushAvailable() {
  1478. if (!pushWarningEmitted) {
  1479. pushWarningEmitted = true;
  1480. getLogger().log(Level.WARNING, Constants.PUSH_NOT_SUPPORTED_ERROR,
  1481. getClass().getSimpleName());
  1482. }
  1483. // Not supported by default for now, sublcasses may override
  1484. return false;
  1485. }
  1486. /**
  1487. * Checks that another {@link VaadinSession} instance is not locked. This is
  1488. * internally used by {@link VaadinSession#accessSynchronously(Runnable)}
  1489. * and {@link UI#accessSynchronously(Runnable)} to help avoid causing
  1490. * deadlocks.
  1491. *
  1492. * @since 7.1
  1493. * @param session
  1494. * the session that is being locked
  1495. * @throws IllegalStateException
  1496. * if the current thread holds the lock for another session
  1497. */
  1498. public static void verifyNoOtherSessionLocked(VaadinSession session) {
  1499. if (isOtherSessionLocked(session)) {
  1500. throw new IllegalStateException(
  1501. "Can't access session while another session is locked by the same thread. This restriction is intended to help avoid deadlocks.");
  1502. }
  1503. }
  1504. /**
  1505. * Checks whether there might be some {@link VaadinSession} other than the
  1506. * provided one for which the current thread holds a lock. This method might
  1507. * not detect all cases where some other session is locked, but it should
  1508. * cover the most typical situations.
  1509. *
  1510. * @since 7.2
  1511. * @param session
  1512. * the session that is expected to be locked
  1513. * @return <code>true</code> if another session is also locked by the
  1514. * current thread; <code>false</code> if no such session was found
  1515. */
  1516. public static boolean isOtherSessionLocked(VaadinSession session) {
  1517. VaadinSession otherSession = VaadinSession.getCurrent();
  1518. if (otherSession == null || otherSession == session) {
  1519. return false;
  1520. }
  1521. return otherSession.hasLock();
  1522. }
  1523. /**
  1524. * Verifies that the given CSRF token (aka double submit cookie) is valid
  1525. * for the given session. This is used to protect against Cross Site Request
  1526. * Forgery attacks.
  1527. * <p>
  1528. * This protection is enabled by default, but it might need to be disabled
  1529. * to allow a certain type of testing. For these cases, the check can be
  1530. * disabled by setting the init parameter
  1531. * <code>disable-xsrf-protection</code> to <code>true</code>.
  1532. *
  1533. * @see DeploymentConfiguration#isXsrfProtectionEnabled()
  1534. *
  1535. * @since 7.1
  1536. *
  1537. * @param session
  1538. * the vaadin session for which the check should be done
  1539. * @param requestToken
  1540. * the CSRF token provided in the request
  1541. * @return <code>true</code> if the token is valid or if the protection is
  1542. * disabled; <code>false</code> if protection is enabled and the
  1543. * token is invalid
  1544. */
  1545. public static boolean isCsrfTokenValid(VaadinSession session,
  1546. String requestToken) {
  1547. if (session.getService().getDeploymentConfiguration()
  1548. .isXsrfProtectionEnabled()) {
  1549. String sessionToken = session.getCsrfToken();
  1550. if (sessionToken == null || !sessionToken.equals(requestToken)) {
  1551. return false;
  1552. }
  1553. }
  1554. return true;
  1555. }
  1556. /**
  1557. * Implementation for {@link VaadinSession#access(Runnable)}. This method is
  1558. * implemented here instead of in {@link VaadinSession} to enable overriding
  1559. * the implementation without using a custom subclass of VaadinSession.
  1560. *
  1561. * @since 7.1
  1562. * @see VaadinSession#access(Runnable)
  1563. *
  1564. * @param session
  1565. * the vaadin session to access
  1566. * @param runnable
  1567. * the runnable to run with the session locked
  1568. *
  1569. * @return a future that can be used to check for task completion and to
  1570. * cancel the task
  1571. */
  1572. public Future<Void> accessSession(VaadinSession session, Runnable runnable) {
  1573. FutureAccess future = new FutureAccess(session, runnable);
  1574. session.getPendingAccessQueue().add(future);
  1575. ensureAccessQueuePurged(session);
  1576. return future;
  1577. }
  1578. /**
  1579. * Makes sure the pending access queue is purged for the provided session.
  1580. * If the session is currently locked by the current thread or some other
  1581. * thread, the queue will be purged when the session is unlocked. If the
  1582. * lock is not held by any thread, it is acquired and the queue is purged
  1583. * right away.
  1584. *
  1585. * @since 7.1.2
  1586. * @param session
  1587. * the session for which the access queue should be purged
  1588. */
  1589. public void ensureAccessQueuePurged(VaadinSession session) {
  1590. /*
  1591. * If no thread is currently holding the lock, pending changes for UIs
  1592. * with automatic push would not be processed and pushed until the next
  1593. * time there is a request or someone does an explicit push call.
  1594. *
  1595. * To remedy this, we try to get the lock at this point. If the lock is
  1596. * currently held by another thread, we just back out as the queue will
  1597. * get purged once it is released. If the lock is held by the current
  1598. * thread, we just release it knowing that the queue gets purged once
  1599. * the lock is ultimately released. If the lock is not held by any
  1600. * thread and we acquire it, we just release it again to purge the queue
  1601. * right away.
  1602. */
  1603. try {
  1604. // tryLock() would be shorter, but it does not guarantee fairness
  1605. if (session.getLockInstance().tryLock(0, TimeUnit.SECONDS)) {
  1606. // unlock triggers runPendingAccessTasks
  1607. session.unlock();
  1608. }
  1609. } catch (InterruptedException e) {
  1610. // Just ignore
  1611. }
  1612. }
  1613. /**
  1614. * Purges the queue of pending access invocations enqueued with
  1615. * {@link VaadinSession#access(Runnable)}.
  1616. * <p>
  1617. * This method is automatically run by the framework at appropriate
  1618. * situations and is not intended to be used by application developers.
  1619. *
  1620. * @param session
  1621. * the vaadin session to purge the queue for
  1622. * @since 7.1
  1623. */
  1624. public void runPendingAccessTasks(VaadinSession session) {
  1625. assert session.hasLock();
  1626. if (session.getPendingAccessQueue().isEmpty()) {
  1627. return;
  1628. }
  1629. Map<Class<?>, CurrentInstance> oldInstances = CurrentInstance
  1630. .getInstances(false);
  1631. FutureAccess pendingAccess;
  1632. try {
  1633. while ((pendingAccess = session.getPendingAccessQueue().poll()) != null) {
  1634. if (!pendingAccess.isCancelled()) {
  1635. CurrentInstance.clearAll();
  1636. CurrentInstance.restoreInstances(pendingAccess
  1637. .getCurrentInstances());
  1638. CurrentInstance.setCurrent(session);
  1639. pendingAccess.run();
  1640. try {
  1641. pendingAccess.get();
  1642. } catch (Exception exception) {
  1643. pendingAccess.handleError(exception);
  1644. }
  1645. }
  1646. }
  1647. } finally {
  1648. CurrentInstance.clearAll();
  1649. CurrentInstance.restoreInstances(oldInstances);
  1650. }
  1651. }
  1652. /**
  1653. * Adds a service destroy listener that gets notified when this service is
  1654. * destroyed.
  1655. *
  1656. * @since 7.2
  1657. * @param listener
  1658. * the service destroy listener to add
  1659. *
  1660. * @see #destroy()
  1661. * @see #removeServiceDestroyListener(ServiceDestroyListener)
  1662. * @see ServiceDestroyListener
  1663. */
  1664. public void addServiceDestroyListener(ServiceDestroyListener listener) {
  1665. eventRouter.addListener(ServiceDestroyEvent.class, listener,
  1666. SERVICE_DESTROY_METHOD);
  1667. }
  1668. /**
  1669. * Removes a service destroy listener that was previously added with
  1670. * {@link #addServiceDestroyListener(ServiceDestroyListener)}.
  1671. *
  1672. * @since 7.2
  1673. * @param listener
  1674. * the service destroy listener to remove
  1675. */
  1676. public void removeServiceDestroyListener(ServiceDestroyListener listener) {
  1677. eventRouter.removeListener(ServiceDestroyEvent.class, listener,
  1678. SERVICE_DESTROY_METHOD);
  1679. }
  1680. /**
  1681. * Called when the servlet, portlet or similar for this service is being
  1682. * destroyed. After this method has been called, no more requests will be
  1683. * handled by this service.
  1684. *
  1685. * @see #addServiceDestroyListener(ServiceDestroyListener)
  1686. * @see Servlet#destroy()
  1687. * @see Portlet#destroy()
  1688. *
  1689. * @since 7.2
  1690. */
  1691. public void destroy() {
  1692. eventRouter.fireEvent(new ServiceDestroyEvent(this));
  1693. }
  1694. }