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.

преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
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. }