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

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