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

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