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 81KB

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