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.

Application.java 85KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin;
  5. import java.io.IOException;
  6. import java.io.Serializable;
  7. import java.lang.annotation.Annotation;
  8. import java.lang.reflect.Method;
  9. import java.net.SocketException;
  10. import java.net.URL;
  11. import java.util.ArrayList;
  12. import java.util.Collection;
  13. import java.util.Collections;
  14. import java.util.Enumeration;
  15. import java.util.EventListener;
  16. import java.util.EventObject;
  17. import java.util.HashMap;
  18. import java.util.HashSet;
  19. import java.util.Hashtable;
  20. import java.util.LinkedList;
  21. import java.util.Locale;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import java.util.Properties;
  25. import java.util.Set;
  26. import java.util.logging.Level;
  27. import java.util.logging.Logger;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;
  30. import com.vaadin.annotations.EagerInit;
  31. import com.vaadin.annotations.Theme;
  32. import com.vaadin.annotations.Widgetset;
  33. import com.vaadin.data.util.converter.Converter;
  34. import com.vaadin.data.util.converter.ConverterFactory;
  35. import com.vaadin.data.util.converter.DefaultConverterFactory;
  36. import com.vaadin.event.EventRouter;
  37. import com.vaadin.service.ApplicationContext;
  38. import com.vaadin.shared.ApplicationConstants;
  39. import com.vaadin.terminal.AbstractErrorMessage;
  40. import com.vaadin.terminal.ApplicationResource;
  41. import com.vaadin.terminal.CombinedRequest;
  42. import com.vaadin.terminal.DeploymentConfiguration;
  43. import com.vaadin.terminal.RequestHandler;
  44. import com.vaadin.terminal.Terminal;
  45. import com.vaadin.terminal.VariableOwner;
  46. import com.vaadin.terminal.WrappedRequest;
  47. import com.vaadin.terminal.WrappedRequest.BrowserDetails;
  48. import com.vaadin.terminal.WrappedResponse;
  49. import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
  50. import com.vaadin.terminal.gwt.server.BootstrapFragmentResponse;
  51. import com.vaadin.terminal.gwt.server.BootstrapListener;
  52. import com.vaadin.terminal.gwt.server.BootstrapPageResponse;
  53. import com.vaadin.terminal.gwt.server.BootstrapResponse;
  54. import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent;
  55. import com.vaadin.terminal.gwt.server.ClientConnector;
  56. import com.vaadin.terminal.gwt.server.WebApplicationContext;
  57. import com.vaadin.tools.ReflectTools;
  58. import com.vaadin.ui.AbstractComponent;
  59. import com.vaadin.ui.AbstractField;
  60. import com.vaadin.ui.Root;
  61. import com.vaadin.ui.Table;
  62. import com.vaadin.ui.Window;
  63. /**
  64. * <p>
  65. * Base class required for all Vaadin applications. This class provides all the
  66. * basic services required by Vaadin. These services allow external discovery
  67. * and manipulation of the user, {@link com.vaadin.ui.Window windows} and
  68. * themes, and starting and stopping the application.
  69. * </p>
  70. *
  71. * <p>
  72. * As mentioned, all Vaadin applications must inherit this class. However, this
  73. * is almost all of what one needs to do to create a fully functional
  74. * application. The only thing a class inheriting the <code>Application</code>
  75. * needs to do is implement the <code>init</code> method where it creates the
  76. * windows it needs to perform its function. Note that all applications must
  77. * have at least one window: the main window. The first unnamed window
  78. * constructed by an application automatically becomes the main window which
  79. * behaves just like other windows with one exception: when accessing windows
  80. * using URLs the main window corresponds to the application URL whereas other
  81. * windows correspond to a URL gotten by catenating the window's name to the
  82. * application URL.
  83. * </p>
  84. *
  85. * <p>
  86. * See the class <code>com.vaadin.demo.HelloWorld</code> for a simple example of
  87. * a fully working application.
  88. * </p>
  89. *
  90. * <p>
  91. * <strong>Window access.</strong> <code>Application</code> provides methods to
  92. * list, add and remove the windows it contains.
  93. * </p>
  94. *
  95. * <p>
  96. * <strong>Execution control.</strong> This class includes method to start and
  97. * finish the execution of the application. Being finished means basically that
  98. * no windows will be available from the application anymore.
  99. * </p>
  100. *
  101. * <p>
  102. * <strong>Theme selection.</strong> The theme selection process allows a theme
  103. * to be specified at three different levels. When a window's theme needs to be
  104. * found out, the window itself is queried for a preferred theme. If the window
  105. * does not prefer a specific theme, the application containing the window is
  106. * queried. If neither the application prefers a theme, the default theme for
  107. * the {@link com.vaadin.terminal.Terminal terminal} is used. The terminal
  108. * always defines a default theme.
  109. * </p>
  110. *
  111. * @author Vaadin Ltd.
  112. * @since 3.0
  113. */
  114. @SuppressWarnings("serial")
  115. public class Application implements Terminal.ErrorListener, Serializable {
  116. /**
  117. * The name of the parameter that is by default used in e.g. web.xml to
  118. * define the name of the default {@link Root} class.
  119. */
  120. public static final String ROOT_PARAMETER = "root";
  121. private static final Method BOOTSTRAP_FRAGMENT_METHOD = ReflectTools
  122. .findMethod(BootstrapListener.class, "modifyBootstrapFragment",
  123. BootstrapFragmentResponse.class);
  124. private static final Method BOOTSTRAP_PAGE_METHOD = ReflectTools
  125. .findMethod(BootstrapListener.class, "modifyBootstrapPage",
  126. BootstrapPageResponse.class);
  127. /**
  128. * A special application designed to help migrating applications from Vaadin
  129. * 6 to Vaadin 7. The legacy application supports setting a main window,
  130. * adding additional browser level windows and defining the theme for the
  131. * entire application.
  132. *
  133. * @deprecated This class is only intended to ease migration and should not
  134. * be used for new projects.
  135. *
  136. * @since 7.0
  137. */
  138. @Deprecated
  139. public static class LegacyApplication extends Application {
  140. /**
  141. * Ignore initial / and then get everything up to the next /
  142. */
  143. private static final Pattern WINDOW_NAME_PATTERN = Pattern
  144. .compile("^/?([^/]+).*");
  145. private Root.LegacyWindow mainWindow;
  146. private String theme;
  147. private Map<String, Root.LegacyWindow> legacyRootNames = new HashMap<String, Root.LegacyWindow>();
  148. /**
  149. * Sets the main window of this application. Setting window as a main
  150. * window of this application also adds the window to this application.
  151. *
  152. * @param mainWindow
  153. * the root to set as the default window
  154. */
  155. public void setMainWindow(Root.LegacyWindow mainWindow) {
  156. if (this.mainWindow != null) {
  157. throw new IllegalStateException(
  158. "mainWindow has already been set");
  159. }
  160. if (mainWindow.getApplication() == null) {
  161. mainWindow.setApplication(this);
  162. } else if (mainWindow.getApplication() != this) {
  163. throw new IllegalStateException(
  164. "mainWindow is attached to another application");
  165. }
  166. this.mainWindow = mainWindow;
  167. }
  168. /**
  169. * Gets the mainWindow of the application.
  170. *
  171. * <p>
  172. * The main window is the window attached to the application URL (
  173. * {@link #getURL()}) and thus which is show by default to the user.
  174. * </p>
  175. * <p>
  176. * Note that each application must have at least one main window.
  177. * </p>
  178. *
  179. * @return the root used as the default window
  180. */
  181. public Root.LegacyWindow getMainWindow() {
  182. return mainWindow;
  183. }
  184. /**
  185. * This implementation simulates the way of finding a window for a
  186. * request by extracting a window name from the requested path and
  187. * passes that name to {@link #getWindow(String)}.
  188. *
  189. * {@inheritDoc}
  190. *
  191. * @see #getWindow(String)
  192. * @see Application#getRoot(WrappedRequest)
  193. */
  194. @Override
  195. public Root.LegacyWindow getRoot(WrappedRequest request) {
  196. String pathInfo = request.getRequestPathInfo();
  197. String name = null;
  198. if (pathInfo != null && pathInfo.length() > 0) {
  199. Matcher matcher = WINDOW_NAME_PATTERN.matcher(pathInfo);
  200. if (matcher.matches()) {
  201. // Skip the initial slash
  202. name = matcher.group(1);
  203. }
  204. }
  205. Root.LegacyWindow window = getWindow(name);
  206. if (window != null) {
  207. return window;
  208. }
  209. return mainWindow;
  210. }
  211. /**
  212. * Sets the application's theme.
  213. * <p>
  214. * Note that this theme can be overridden for a specific root with
  215. * {@link Application#getThemeForRoot(Root)}. Setting theme to be
  216. * <code>null</code> selects the default theme. For the available theme
  217. * names, see the contents of the VAADIN/themes directory.
  218. * </p>
  219. *
  220. * @param theme
  221. * the new theme for this application.
  222. */
  223. public void setTheme(String theme) {
  224. this.theme = theme;
  225. }
  226. /**
  227. * Gets the application's theme. The application's theme is the default
  228. * theme used by all the roots for which a theme is not explicitly
  229. * defined. If the application theme is not explicitly set,
  230. * <code>null</code> is returned.
  231. *
  232. * @return the name of the application's theme.
  233. */
  234. public String getTheme() {
  235. return theme;
  236. }
  237. /**
  238. * This implementation returns the theme that has been set using
  239. * {@link #setTheme(String)}
  240. * <p>
  241. * {@inheritDoc}
  242. */
  243. @Override
  244. public String getThemeForRoot(Root root) {
  245. return theme;
  246. }
  247. /**
  248. * <p>
  249. * Gets a root by name. Returns <code>null</code> if the application is
  250. * not running or it does not contain a window corresponding to the
  251. * name.
  252. * </p>
  253. *
  254. * @param name
  255. * the name of the requested window
  256. * @return a root corresponding to the name, or <code>null</code> to use
  257. * the default window
  258. */
  259. public Root.LegacyWindow getWindow(String name) {
  260. return legacyRootNames.get(name);
  261. }
  262. /**
  263. * Counter to get unique names for windows with no explicit name
  264. */
  265. private int namelessRootIndex = 0;
  266. /**
  267. * Adds a new browser level window to this application. Please note that
  268. * Root doesn't have a name that is used in the URL - to add a named
  269. * window you should instead use {@link #addWindow(Root, String)}
  270. *
  271. * @param root
  272. * the root window to add to the application
  273. * @return returns the name that has been assigned to the window
  274. *
  275. * @see #addWindow(Root, String)
  276. */
  277. public void addWindow(Root.LegacyWindow root) {
  278. if (root.getName() == null) {
  279. String name = Integer.toString(namelessRootIndex++);
  280. root.setName(name);
  281. }
  282. legacyRootNames.put(root.getName(), root);
  283. root.setApplication(this);
  284. }
  285. /**
  286. * Removes the specified window from the application. This also removes
  287. * all name mappings for the window (see
  288. * {@link #addWindow(Root, String) and #getWindowName(Root)}.
  289. *
  290. * <p>
  291. * Note that removing window from the application does not close the
  292. * browser window - the window is only removed from the server-side.
  293. * </p>
  294. *
  295. * @param root
  296. * the root to remove
  297. */
  298. public void removeWindow(Root.LegacyWindow root) {
  299. for (Entry<String, Root.LegacyWindow> entry : legacyRootNames
  300. .entrySet()) {
  301. if (entry.getValue() == root) {
  302. legacyRootNames.remove(entry.getKey());
  303. }
  304. }
  305. }
  306. /**
  307. * Gets the set of windows contained by the application.
  308. *
  309. * <p>
  310. * Note that the returned set of windows can not be modified.
  311. * </p>
  312. *
  313. * @return the unmodifiable collection of windows.
  314. */
  315. public Collection<Root.LegacyWindow> getWindows() {
  316. return Collections.unmodifiableCollection(legacyRootNames.values());
  317. }
  318. }
  319. /**
  320. * An event sent to {@link #start(ApplicationStartEvent)} when a new
  321. * Application is being started.
  322. *
  323. * @since 7.0
  324. */
  325. public static class ApplicationStartEvent implements Serializable {
  326. private final URL applicationUrl;
  327. private final Properties applicationProperties;
  328. private final ApplicationContext context;
  329. private final boolean productionMode;
  330. /**
  331. * @param applicationUrl
  332. * the URL the application should respond to.
  333. * @param applicationProperties
  334. * the Application properties as specified by the deployment
  335. * configuration.
  336. * @param context
  337. * the context application will be running in.
  338. * @param productionMode
  339. * flag indicating whether the application is running in
  340. * production mode.
  341. */
  342. public ApplicationStartEvent(URL applicationUrl,
  343. Properties applicationProperties, ApplicationContext context,
  344. boolean productionMode) {
  345. this.applicationUrl = applicationUrl;
  346. this.applicationProperties = applicationProperties;
  347. this.context = context;
  348. this.productionMode = productionMode;
  349. }
  350. /**
  351. * Gets the URL the application should respond to.
  352. *
  353. * @return the URL the application should respond to or
  354. * <code>null</code> if the URL is not defined.
  355. *
  356. * @see Application#getURL()
  357. */
  358. public URL getApplicationUrl() {
  359. return applicationUrl;
  360. }
  361. /**
  362. * Gets the Application properties as specified by the deployment
  363. * configuration.
  364. *
  365. * @return the properties configured for the applciation.
  366. *
  367. * @see Application#getProperty(String)
  368. */
  369. public Properties getApplicationProperties() {
  370. return applicationProperties;
  371. }
  372. /**
  373. * Gets the context application will be running in.
  374. *
  375. * @return the context application will be running in.
  376. *
  377. * @see Application#getContext()
  378. */
  379. public ApplicationContext getContext() {
  380. return context;
  381. }
  382. /**
  383. * Checks whether the application is running in production mode.
  384. *
  385. * @return <code>true</code> if in production mode, else
  386. * <code>false</code>
  387. *
  388. * @see Application#isProductionMode()
  389. */
  390. public boolean isProductionMode() {
  391. return productionMode;
  392. }
  393. }
  394. private final static Logger logger = Logger.getLogger(Application.class
  395. .getName());
  396. /**
  397. * Application context the application is running in.
  398. */
  399. private ApplicationContext context;
  400. /**
  401. * The current user or <code>null</code> if no user has logged in.
  402. */
  403. private Object user;
  404. /**
  405. * The application's URL.
  406. */
  407. private URL applicationUrl;
  408. /**
  409. * Application status.
  410. */
  411. private volatile boolean applicationIsRunning = false;
  412. /**
  413. * Application properties.
  414. */
  415. private Properties properties;
  416. /**
  417. * Default locale of the application.
  418. */
  419. private Locale locale;
  420. /**
  421. * List of listeners listening user changes.
  422. */
  423. private LinkedList<UserChangeListener> userChangeListeners = null;
  424. /**
  425. * Application resource mapping: key <-> resource.
  426. */
  427. private final Hashtable<ApplicationResource, String> resourceKeyMap = new Hashtable<ApplicationResource, String>();
  428. private final Hashtable<String, ApplicationResource> keyResourceMap = new Hashtable<String, ApplicationResource>();
  429. private long lastResourceKeyNumber = 0;
  430. /**
  431. * URL where the user is redirected to on application close, or null if
  432. * application is just closed without redirection.
  433. */
  434. private String logoutURL = null;
  435. /**
  436. * The default SystemMessages (read-only). Change by overriding
  437. * getSystemMessages() and returning CustomizedSystemMessages
  438. */
  439. private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages();
  440. /**
  441. * Application wide error handler which is used by default if an error is
  442. * left unhandled.
  443. */
  444. private Terminal.ErrorListener errorHandler = this;
  445. /**
  446. * The converter factory that is used to provide default converters for the
  447. * application.
  448. */
  449. private ConverterFactory converterFactory = new DefaultConverterFactory();
  450. private LinkedList<RequestHandler> requestHandlers = new LinkedList<RequestHandler>();
  451. private int nextRootId = 0;
  452. private Map<Integer, Root> roots = new HashMap<Integer, Root>();
  453. private boolean productionMode = true;
  454. private final Map<String, Integer> retainOnRefreshRoots = new HashMap<String, Integer>();
  455. private final EventRouter eventRouter = new EventRouter();
  456. /**
  457. * Keeps track of which roots have been inited.
  458. * <p>
  459. * TODO Investigate whether this might be derived from the different states
  460. * in getRootForRrequest.
  461. * </p>
  462. */
  463. private Set<Integer> initedRoots = new HashSet<Integer>();
  464. /**
  465. * Gets the user of the application.
  466. *
  467. * <p>
  468. * Vaadin doesn't define of use user object in any way - it only provides
  469. * this getter and setter methods for convenience. The user is any object
  470. * that has been stored to the application with {@link #setUser(Object)}.
  471. * </p>
  472. *
  473. * @return the User of the application.
  474. */
  475. public Object getUser() {
  476. return user;
  477. }
  478. /**
  479. * <p>
  480. * Sets the user of the application instance. An application instance may
  481. * have a user associated to it. This can be set in login procedure or
  482. * application initialization.
  483. * </p>
  484. * <p>
  485. * A component performing the user login procedure can assign the user
  486. * property of the application and make the user object available to other
  487. * components of the application.
  488. * </p>
  489. * <p>
  490. * Vaadin doesn't define of use user object in any way - it only provides
  491. * getter and setter methods for convenience. The user reference stored to
  492. * the application can be read with {@link #getUser()}.
  493. * </p>
  494. *
  495. * @param user
  496. * the new user.
  497. */
  498. public void setUser(Object user) {
  499. final Object prevUser = this.user;
  500. if (user == prevUser || (user != null && user.equals(prevUser))) {
  501. return;
  502. }
  503. this.user = user;
  504. if (userChangeListeners != null) {
  505. final Object[] listeners = userChangeListeners.toArray();
  506. final UserChangeEvent event = new UserChangeEvent(this, user,
  507. prevUser);
  508. for (int i = 0; i < listeners.length; i++) {
  509. ((UserChangeListener) listeners[i])
  510. .applicationUserChanged(event);
  511. }
  512. }
  513. }
  514. /**
  515. * Gets the URL of the application.
  516. *
  517. * <p>
  518. * This is the URL what can be entered to a browser window to start the
  519. * application. Navigating to the application URL shows the main window (
  520. * {@link #getMainWindow()}) of the application. Note that the main window
  521. * can also be shown by navigating to the window url (
  522. * {@link com.vaadin.ui.Window#getURL()}).
  523. * </p>
  524. *
  525. * @return the application's URL.
  526. */
  527. public URL getURL() {
  528. return applicationUrl;
  529. }
  530. /**
  531. * Ends the Application.
  532. *
  533. * <p>
  534. * In effect this will cause the application stop returning any windows when
  535. * asked. When the application is closed, its state is removed from the
  536. * session and the browser window is redirected to the application logout
  537. * url set with {@link #setLogoutURL(String)}. If the logout url has not
  538. * been set, the browser window is reloaded and the application is
  539. * restarted.
  540. * </p>
  541. * .
  542. */
  543. public void close() {
  544. applicationIsRunning = false;
  545. }
  546. /**
  547. * Starts the application on the given URL.
  548. *
  549. * <p>
  550. * This method is called by Vaadin framework when a user navigates to the
  551. * application. After this call the application corresponds to the given URL
  552. * and it will return windows when asked for them. There is no need to call
  553. * this method directly.
  554. * </p>
  555. *
  556. * <p>
  557. * Application properties are defined by servlet configuration object
  558. * {@link javax.servlet.ServletConfig} and they are overridden by
  559. * context-wide initialization parameters
  560. * {@link javax.servlet.ServletContext}.
  561. * </p>
  562. *
  563. * @param event
  564. * the application start event containing details required for
  565. * starting the application.
  566. *
  567. */
  568. public void start(ApplicationStartEvent event) {
  569. applicationUrl = event.getApplicationUrl();
  570. productionMode = event.isProductionMode();
  571. properties = event.getApplicationProperties();
  572. context = event.getContext();
  573. init();
  574. applicationIsRunning = true;
  575. }
  576. /**
  577. * Tests if the application is running or if it has been finished.
  578. *
  579. * <p>
  580. * Application starts running when its
  581. * {@link #start(URL, Properties, ApplicationContext)} method has been
  582. * called and stops when the {@link #close()} is called.
  583. * </p>
  584. *
  585. * @return <code>true</code> if the application is running,
  586. * <code>false</code> if not.
  587. */
  588. public boolean isRunning() {
  589. return applicationIsRunning;
  590. }
  591. /**
  592. * <p>
  593. * Main initializer of the application. The <code>init</code> method is
  594. * called by the framework when the application is started, and it should
  595. * perform whatever initialization operations the application needs.
  596. * </p>
  597. */
  598. public void init() {
  599. // Default implementation does nothing
  600. }
  601. /**
  602. * Returns an enumeration of all the names in this application.
  603. *
  604. * <p>
  605. * See {@link #start(URL, Properties, ApplicationContext)} how properties
  606. * are defined.
  607. * </p>
  608. *
  609. * @return an enumeration of all the keys in this property list, including
  610. * the keys in the default property list.
  611. *
  612. */
  613. public Enumeration<?> getPropertyNames() {
  614. return properties.propertyNames();
  615. }
  616. /**
  617. * Searches for the property with the specified name in this application.
  618. * This method returns <code>null</code> if the property is not found.
  619. *
  620. * See {@link #start(URL, Properties, ApplicationContext)} how properties
  621. * are defined.
  622. *
  623. * @param name
  624. * the name of the property.
  625. * @return the value in this property list with the specified key value.
  626. */
  627. public String getProperty(String name) {
  628. return properties.getProperty(name);
  629. }
  630. /**
  631. * Adds new resource to the application. The resource can be accessed by the
  632. * user of the application.
  633. *
  634. * @param resource
  635. * the resource to add.
  636. */
  637. public void addResource(ApplicationResource resource) {
  638. // Check if the resource is already mapped
  639. if (resourceKeyMap.containsKey(resource)) {
  640. return;
  641. }
  642. // Generate key
  643. final String key = String.valueOf(++lastResourceKeyNumber);
  644. // Add the resource to mappings
  645. resourceKeyMap.put(resource, key);
  646. keyResourceMap.put(key, resource);
  647. }
  648. /**
  649. * Removes the resource from the application.
  650. *
  651. * @param resource
  652. * the resource to remove.
  653. */
  654. public void removeResource(ApplicationResource resource) {
  655. final Object key = resourceKeyMap.get(resource);
  656. if (key != null) {
  657. resourceKeyMap.remove(resource);
  658. keyResourceMap.remove(key);
  659. }
  660. }
  661. /**
  662. * Gets the relative uri of the resource. This method is intended to be
  663. * called only be the terminal implementation.
  664. *
  665. * This method can only be called from within the processing of a UIDL
  666. * request, not from a background thread.
  667. *
  668. * @param resource
  669. * the resource to get relative location.
  670. * @return the relative uri of the resource or null if called in a
  671. * background thread
  672. *
  673. * @deprecated this method is intended to be used by the terminal only. It
  674. * may be removed or moved in the future.
  675. */
  676. @Deprecated
  677. public String getRelativeLocation(ApplicationResource resource) {
  678. // Gets the key
  679. final String key = resourceKeyMap.get(resource);
  680. // If the resource is not registered, return null
  681. if (key == null) {
  682. return null;
  683. }
  684. return context.generateApplicationResourceURL(resource, key);
  685. }
  686. /**
  687. * Gets the default locale for this application.
  688. *
  689. * By default this is the preferred locale of the user using the
  690. * application. In most cases it is read from the browser defaults.
  691. *
  692. * @return the locale of this application.
  693. */
  694. public Locale getLocale() {
  695. if (locale != null) {
  696. return locale;
  697. }
  698. return Locale.getDefault();
  699. }
  700. /**
  701. * Sets the default locale for this application.
  702. *
  703. * By default this is the preferred locale of the user using the
  704. * application. In most cases it is read from the browser defaults.
  705. *
  706. * @param locale
  707. * the Locale object.
  708. *
  709. */
  710. public void setLocale(Locale locale) {
  711. this.locale = locale;
  712. }
  713. /**
  714. * <p>
  715. * An event that characterizes a change in the current selection.
  716. * </p>
  717. * Application user change event sent when the setUser is called to change
  718. * the current user of the application.
  719. *
  720. * @version
  721. * @VERSION@
  722. * @since 3.0
  723. */
  724. public class UserChangeEvent extends java.util.EventObject {
  725. /**
  726. * New user of the application.
  727. */
  728. private final Object newUser;
  729. /**
  730. * Previous user of the application.
  731. */
  732. private final Object prevUser;
  733. /**
  734. * Constructor for user change event.
  735. *
  736. * @param source
  737. * the application source.
  738. * @param newUser
  739. * the new User.
  740. * @param prevUser
  741. * the previous User.
  742. */
  743. public UserChangeEvent(Application source, Object newUser,
  744. Object prevUser) {
  745. super(source);
  746. this.newUser = newUser;
  747. this.prevUser = prevUser;
  748. }
  749. /**
  750. * Gets the new user of the application.
  751. *
  752. * @return the new User.
  753. */
  754. public Object getNewUser() {
  755. return newUser;
  756. }
  757. /**
  758. * Gets the previous user of the application.
  759. *
  760. * @return the previous Vaadin user, if user has not changed ever on
  761. * application it returns <code>null</code>
  762. */
  763. public Object getPreviousUser() {
  764. return prevUser;
  765. }
  766. /**
  767. * Gets the application where the user change occurred.
  768. *
  769. * @return the Application.
  770. */
  771. public Application getApplication() {
  772. return (Application) getSource();
  773. }
  774. }
  775. /**
  776. * The <code>UserChangeListener</code> interface for listening application
  777. * user changes.
  778. *
  779. * @version
  780. * @VERSION@
  781. * @since 3.0
  782. */
  783. public interface UserChangeListener extends EventListener, Serializable {
  784. /**
  785. * The <code>applicationUserChanged</code> method Invoked when the
  786. * application user has changed.
  787. *
  788. * @param event
  789. * the change event.
  790. */
  791. public void applicationUserChanged(Application.UserChangeEvent event);
  792. }
  793. /**
  794. * Adds the user change listener.
  795. *
  796. * This allows one to get notification each time {@link #setUser(Object)} is
  797. * called.
  798. *
  799. * @param listener
  800. * the user change listener to add.
  801. */
  802. public void addListener(UserChangeListener listener) {
  803. if (userChangeListeners == null) {
  804. userChangeListeners = new LinkedList<UserChangeListener>();
  805. }
  806. userChangeListeners.add(listener);
  807. }
  808. /**
  809. * Removes the user change listener.
  810. *
  811. * @param listener
  812. * the user change listener to remove.
  813. */
  814. public void removeListener(UserChangeListener listener) {
  815. if (userChangeListeners == null) {
  816. return;
  817. }
  818. userChangeListeners.remove(listener);
  819. if (userChangeListeners.isEmpty()) {
  820. userChangeListeners = null;
  821. }
  822. }
  823. /**
  824. * Window detach event.
  825. *
  826. * This event is sent each time a window is removed from the application
  827. * with {@link com.vaadin.Application#removeWindow(Window)}.
  828. */
  829. public class WindowDetachEvent extends EventObject {
  830. private final Window window;
  831. /**
  832. * Creates a event.
  833. *
  834. * @param window
  835. * the Detached window.
  836. */
  837. public WindowDetachEvent(Window window) {
  838. super(Application.this);
  839. this.window = window;
  840. }
  841. /**
  842. * Gets the detached window.
  843. *
  844. * @return the detached window.
  845. */
  846. public Window getWindow() {
  847. return window;
  848. }
  849. /**
  850. * Gets the application from which the window was detached.
  851. *
  852. * @return the Application.
  853. */
  854. public Application getApplication() {
  855. return (Application) getSource();
  856. }
  857. }
  858. /**
  859. * Window attach event.
  860. *
  861. * This event is sent each time a window is attached tothe application with
  862. * {@link com.vaadin.Application#addWindow(Window)}.
  863. */
  864. public class WindowAttachEvent extends EventObject {
  865. private final Window window;
  866. /**
  867. * Creates a event.
  868. *
  869. * @param window
  870. * the Attached window.
  871. */
  872. public WindowAttachEvent(Window window) {
  873. super(Application.this);
  874. this.window = window;
  875. }
  876. /**
  877. * Gets the attached window.
  878. *
  879. * @return the attached window.
  880. */
  881. public Window getWindow() {
  882. return window;
  883. }
  884. /**
  885. * Gets the application to which the window was attached.
  886. *
  887. * @return the Application.
  888. */
  889. public Application getApplication() {
  890. return (Application) getSource();
  891. }
  892. }
  893. /**
  894. * Window attach listener interface.
  895. */
  896. public interface WindowAttachListener extends Serializable {
  897. /**
  898. * Window attached
  899. *
  900. * @param event
  901. * the window attach event.
  902. */
  903. public void windowAttached(WindowAttachEvent event);
  904. }
  905. /**
  906. * Window detach listener interface.
  907. */
  908. public interface WindowDetachListener extends Serializable {
  909. /**
  910. * Window detached.
  911. *
  912. * @param event
  913. * the window detach event.
  914. */
  915. public void windowDetached(WindowDetachEvent event);
  916. }
  917. /**
  918. * Returns the URL user is redirected to on application close. If the URL is
  919. * <code>null</code>, the application is closed normally as defined by the
  920. * application running environment.
  921. * <p>
  922. * Desktop application just closes the application window and
  923. * web-application redirects the browser to application main URL.
  924. * </p>
  925. *
  926. * @return the URL.
  927. */
  928. public String getLogoutURL() {
  929. return logoutURL;
  930. }
  931. /**
  932. * Sets the URL user is redirected to on application close. If the URL is
  933. * <code>null</code>, the application is closed normally as defined by the
  934. * application running environment: Desktop application just closes the
  935. * application window and web-application redirects the browser to
  936. * application main URL.
  937. *
  938. * @param logoutURL
  939. * the logoutURL to set.
  940. */
  941. public void setLogoutURL(String logoutURL) {
  942. this.logoutURL = logoutURL;
  943. }
  944. /**
  945. * Gets the SystemMessages for this application. SystemMessages are used to
  946. * notify the user of various critical situations that can occur, such as
  947. * session expiration, client/server out of sync, and internal server error.
  948. *
  949. * You can customize the messages by "overriding" this method and returning
  950. * {@link CustomizedSystemMessages}. To "override" this method, re-implement
  951. * this method in your application (the class that extends
  952. * {@link Application}). Even though overriding static methods is not
  953. * possible in Java, Vaadin selects to call the static method from the
  954. * subclass instead of the original {@link #getSystemMessages()} if such a
  955. * method exists.
  956. *
  957. * @return the SystemMessages for this application
  958. */
  959. public static SystemMessages getSystemMessages() {
  960. return DEFAULT_SYSTEM_MESSAGES;
  961. }
  962. /**
  963. * <p>
  964. * Invoked by the terminal on any exception that occurs in application and
  965. * is thrown by the <code>setVariable</code> to the terminal. The default
  966. * implementation sets the exceptions as <code>ComponentErrors</code> to the
  967. * component that initiated the exception and prints stack trace to standard
  968. * error stream.
  969. * </p>
  970. * <p>
  971. * You can safely override this method in your application in order to
  972. * direct the errors to some other destination (for example log).
  973. * </p>
  974. *
  975. * @param event
  976. * the change event.
  977. * @see com.vaadin.terminal.Terminal.ErrorListener#terminalError(com.vaadin.terminal.Terminal.ErrorEvent)
  978. */
  979. @Override
  980. public void terminalError(Terminal.ErrorEvent event) {
  981. final Throwable t = event.getThrowable();
  982. if (t instanceof SocketException) {
  983. // Most likely client browser closed socket
  984. getLogger().info(
  985. "SocketException in CommunicationManager."
  986. + " Most likely client (browser) closed socket.");
  987. return;
  988. }
  989. // Finds the original source of the error/exception
  990. Object owner = null;
  991. if (event instanceof VariableOwner.ErrorEvent) {
  992. owner = ((VariableOwner.ErrorEvent) event).getVariableOwner();
  993. } else if (event instanceof ChangeVariablesErrorEvent) {
  994. owner = ((ChangeVariablesErrorEvent) event).getComponent();
  995. }
  996. // Shows the error in AbstractComponent
  997. if (owner instanceof AbstractComponent) {
  998. ((AbstractComponent) owner).setComponentError(AbstractErrorMessage
  999. .getErrorMessageForException(t));
  1000. }
  1001. // also print the error on console
  1002. getLogger().log(Level.SEVERE, "Terminal error:", t);
  1003. }
  1004. /**
  1005. * Gets the application context.
  1006. * <p>
  1007. * The application context is the environment where the application is
  1008. * running in. The actual implementation class of may contains quite a lot
  1009. * more functionality than defined in the {@link ApplicationContext}
  1010. * interface.
  1011. * </p>
  1012. * <p>
  1013. * By default, when you are deploying your application to a servlet
  1014. * container, the implementation class is {@link WebApplicationContext} -
  1015. * you can safely cast to this class and use the methods from there. When
  1016. * you are deploying your application as a portlet, context implementation
  1017. * is {@link PortletApplicationContext}.
  1018. * </p>
  1019. *
  1020. * @return the application context.
  1021. */
  1022. public ApplicationContext getContext() {
  1023. return context;
  1024. }
  1025. /**
  1026. * Override this method to return correct version number of your
  1027. * Application. Version information is delivered for example to Testing
  1028. * Tools test results. By default this returns a string "NONVERSIONED".
  1029. *
  1030. * @return version string
  1031. */
  1032. public String getVersion() {
  1033. return "NONVERSIONED";
  1034. }
  1035. /**
  1036. * Gets the application error handler.
  1037. *
  1038. * The default error handler is the application itself.
  1039. *
  1040. * @return Application error handler
  1041. */
  1042. public Terminal.ErrorListener getErrorHandler() {
  1043. return errorHandler;
  1044. }
  1045. /**
  1046. * Sets the application error handler.
  1047. *
  1048. * The default error handler is the application itself. By overriding this,
  1049. * you can redirect the error messages to your selected target (log for
  1050. * example).
  1051. *
  1052. * @param errorHandler
  1053. */
  1054. public void setErrorHandler(Terminal.ErrorListener errorHandler) {
  1055. this.errorHandler = errorHandler;
  1056. }
  1057. /**
  1058. * Gets the {@link ConverterFactory} used to locate a suitable
  1059. * {@link Converter} for fields in the application.
  1060. *
  1061. * See {@link #setConverterFactory(ConverterFactory)} for more details
  1062. *
  1063. * @return The converter factory used in the application
  1064. */
  1065. public ConverterFactory getConverterFactory() {
  1066. return converterFactory;
  1067. }
  1068. /**
  1069. * Sets the {@link ConverterFactory} used to locate a suitable
  1070. * {@link Converter} for fields in the application.
  1071. * <p>
  1072. * The {@link ConverterFactory} is used to find a suitable converter when
  1073. * binding data to a UI component and the data type does not match the UI
  1074. * component type, e.g. binding a Double to a TextField (which is based on a
  1075. * String).
  1076. * </p>
  1077. * <p>
  1078. * The {@link Converter} for an individual field can be overridden using
  1079. * {@link AbstractField#setConverter(Converter)} and for individual property
  1080. * ids in a {@link Table} using
  1081. * {@link Table#setConverter(Object, Converter)}.
  1082. * </p>
  1083. * <p>
  1084. * The converter factory must never be set to null.
  1085. *
  1086. * @param converterFactory
  1087. * The converter factory used in the application
  1088. */
  1089. public void setConverterFactory(ConverterFactory converterFactory) {
  1090. this.converterFactory = converterFactory;
  1091. }
  1092. /**
  1093. * Contains the system messages used to notify the user about various
  1094. * critical situations that can occur.
  1095. * <p>
  1096. * Customize by overriding the static
  1097. * {@link Application#getSystemMessages()} and returning
  1098. * {@link CustomizedSystemMessages}.
  1099. * </p>
  1100. * <p>
  1101. * The defaults defined in this class are:
  1102. * <ul>
  1103. * <li><b>sessionExpiredURL</b> = null</li>
  1104. * <li><b>sessionExpiredNotificationEnabled</b> = true</li>
  1105. * <li><b>sessionExpiredCaption</b> = ""</li>
  1106. * <li><b>sessionExpiredMessage</b> =
  1107. * "Take note of any unsaved data, and <u>click here</u> to continue."</li>
  1108. * <li><b>communicationErrorURL</b> = null</li>
  1109. * <li><b>communicationErrorNotificationEnabled</b> = true</li>
  1110. * <li><b>communicationErrorCaption</b> = "Communication problem"</li>
  1111. * <li><b>communicationErrorMessage</b> =
  1112. * "Take note of any unsaved data, and <u>click here</u> to continue."</li>
  1113. * <li><b>internalErrorURL</b> = null</li>
  1114. * <li><b>internalErrorNotificationEnabled</b> = true</li>
  1115. * <li><b>internalErrorCaption</b> = "Internal error"</li>
  1116. * <li><b>internalErrorMessage</b> = "Please notify the administrator.<br/>
  1117. * Take note of any unsaved data, and <u>click here</u> to continue."</li>
  1118. * <li><b>outOfSyncURL</b> = null</li>
  1119. * <li><b>outOfSyncNotificationEnabled</b> = true</li>
  1120. * <li><b>outOfSyncCaption</b> = "Out of sync"</li>
  1121. * <li><b>outOfSyncMessage</b> = "Something has caused us to be out of sync
  1122. * with the server.<br/>
  1123. * Take note of any unsaved data, and <u>click here</u> to re-sync."</li>
  1124. * <li><b>cookiesDisabledURL</b> = null</li>
  1125. * <li><b>cookiesDisabledNotificationEnabled</b> = true</li>
  1126. * <li><b>cookiesDisabledCaption</b> = "Cookies disabled"</li>
  1127. * <li><b>cookiesDisabledMessage</b> = "This application requires cookies to
  1128. * function.<br/>
  1129. * Please enable cookies in your browser and <u>click here</u> to try again.
  1130. * </li>
  1131. * </ul>
  1132. * </p>
  1133. *
  1134. */
  1135. public static class SystemMessages implements Serializable {
  1136. protected String sessionExpiredURL = null;
  1137. protected boolean sessionExpiredNotificationEnabled = true;
  1138. protected String sessionExpiredCaption = "Session Expired";
  1139. protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
  1140. protected String communicationErrorURL = null;
  1141. protected boolean communicationErrorNotificationEnabled = true;
  1142. protected String communicationErrorCaption = "Communication problem";
  1143. protected String communicationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
  1144. protected String authenticationErrorURL = null;
  1145. protected boolean authenticationErrorNotificationEnabled = true;
  1146. protected String authenticationErrorCaption = "Authentication problem";
  1147. protected String authenticationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
  1148. protected String internalErrorURL = null;
  1149. protected boolean internalErrorNotificationEnabled = true;
  1150. protected String internalErrorCaption = "Internal error";
  1151. protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> to continue.";
  1152. protected String outOfSyncURL = null;
  1153. protected boolean outOfSyncNotificationEnabled = true;
  1154. protected String outOfSyncCaption = "Out of sync";
  1155. protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> to re-sync.";
  1156. protected String cookiesDisabledURL = null;
  1157. protected boolean cookiesDisabledNotificationEnabled = true;
  1158. protected String cookiesDisabledCaption = "Cookies disabled";
  1159. protected String cookiesDisabledMessage = "This application requires cookies to function.<br/>Please enable cookies in your browser and <u>click here</u> to try again.";
  1160. /**
  1161. * Use {@link CustomizedSystemMessages} to customize
  1162. */
  1163. private SystemMessages() {
  1164. }
  1165. /**
  1166. * @return null to indicate that the application will be restarted after
  1167. * session expired message has been shown.
  1168. */
  1169. public String getSessionExpiredURL() {
  1170. return sessionExpiredURL;
  1171. }
  1172. /**
  1173. * @return true to show session expiration message.
  1174. */
  1175. public boolean isSessionExpiredNotificationEnabled() {
  1176. return sessionExpiredNotificationEnabled;
  1177. }
  1178. /**
  1179. * @return "" to show no caption.
  1180. */
  1181. public String getSessionExpiredCaption() {
  1182. return (sessionExpiredNotificationEnabled ? sessionExpiredCaption
  1183. : null);
  1184. }
  1185. /**
  1186. * @return
  1187. * "Take note of any unsaved data, and <u>click here</u> to continue."
  1188. */
  1189. public String getSessionExpiredMessage() {
  1190. return (sessionExpiredNotificationEnabled ? sessionExpiredMessage
  1191. : null);
  1192. }
  1193. /**
  1194. * @return null to reload the application after communication error
  1195. * message.
  1196. */
  1197. public String getCommunicationErrorURL() {
  1198. return communicationErrorURL;
  1199. }
  1200. /**
  1201. * @return true to show the communication error message.
  1202. */
  1203. public boolean isCommunicationErrorNotificationEnabled() {
  1204. return communicationErrorNotificationEnabled;
  1205. }
  1206. /**
  1207. * @return "Communication problem"
  1208. */
  1209. public String getCommunicationErrorCaption() {
  1210. return (communicationErrorNotificationEnabled ? communicationErrorCaption
  1211. : null);
  1212. }
  1213. /**
  1214. * @return
  1215. * "Take note of any unsaved data, and <u>click here</u> to continue."
  1216. */
  1217. public String getCommunicationErrorMessage() {
  1218. return (communicationErrorNotificationEnabled ? communicationErrorMessage
  1219. : null);
  1220. }
  1221. /**
  1222. * @return null to reload the application after authentication error
  1223. * message.
  1224. */
  1225. public String getAuthenticationErrorURL() {
  1226. return authenticationErrorURL;
  1227. }
  1228. /**
  1229. * @return true to show the authentication error message.
  1230. */
  1231. public boolean isAuthenticationErrorNotificationEnabled() {
  1232. return authenticationErrorNotificationEnabled;
  1233. }
  1234. /**
  1235. * @return "Authentication problem"
  1236. */
  1237. public String getAuthenticationErrorCaption() {
  1238. return (authenticationErrorNotificationEnabled ? authenticationErrorCaption
  1239. : null);
  1240. }
  1241. /**
  1242. * @return
  1243. * "Take note of any unsaved data, and <u>click here</u> to continue."
  1244. */
  1245. public String getAuthenticationErrorMessage() {
  1246. return (authenticationErrorNotificationEnabled ? authenticationErrorMessage
  1247. : null);
  1248. }
  1249. /**
  1250. * @return null to reload the current URL after internal error message
  1251. * has been shown.
  1252. */
  1253. public String getInternalErrorURL() {
  1254. return internalErrorURL;
  1255. }
  1256. /**
  1257. * @return true to enable showing of internal error message.
  1258. */
  1259. public boolean isInternalErrorNotificationEnabled() {
  1260. return internalErrorNotificationEnabled;
  1261. }
  1262. /**
  1263. * @return "Internal error"
  1264. */
  1265. public String getInternalErrorCaption() {
  1266. return (internalErrorNotificationEnabled ? internalErrorCaption
  1267. : null);
  1268. }
  1269. /**
  1270. * @return "Please notify the administrator.<br/>
  1271. * Take note of any unsaved data, and <u>click here</u> to
  1272. * continue."
  1273. */
  1274. public String getInternalErrorMessage() {
  1275. return (internalErrorNotificationEnabled ? internalErrorMessage
  1276. : null);
  1277. }
  1278. /**
  1279. * @return null to reload the application after out of sync message.
  1280. */
  1281. public String getOutOfSyncURL() {
  1282. return outOfSyncURL;
  1283. }
  1284. /**
  1285. * @return true to enable showing out of sync message
  1286. */
  1287. public boolean isOutOfSyncNotificationEnabled() {
  1288. return outOfSyncNotificationEnabled;
  1289. }
  1290. /**
  1291. * @return "Out of sync"
  1292. */
  1293. public String getOutOfSyncCaption() {
  1294. return (outOfSyncNotificationEnabled ? outOfSyncCaption : null);
  1295. }
  1296. /**
  1297. * @return "Something has caused us to be out of sync with the server.<br/>
  1298. * Take note of any unsaved data, and <u>click here</u> to
  1299. * re-sync."
  1300. */
  1301. public String getOutOfSyncMessage() {
  1302. return (outOfSyncNotificationEnabled ? outOfSyncMessage : null);
  1303. }
  1304. /**
  1305. * Returns the URL the user should be redirected to after dismissing the
  1306. * "you have to enable your cookies" message. Typically null.
  1307. *
  1308. * @return A URL the user should be redirected to after dismissing the
  1309. * message or null to reload the current URL.
  1310. */
  1311. public String getCookiesDisabledURL() {
  1312. return cookiesDisabledURL;
  1313. }
  1314. /**
  1315. * Determines if "cookies disabled" messages should be shown to the end
  1316. * user or not. If the notification is disabled the user will be
  1317. * immediately redirected to the URL returned by
  1318. * {@link #getCookiesDisabledURL()}.
  1319. *
  1320. * @return true to show "cookies disabled" messages to the end user,
  1321. * false to redirect to the given URL directly
  1322. */
  1323. public boolean isCookiesDisabledNotificationEnabled() {
  1324. return cookiesDisabledNotificationEnabled;
  1325. }
  1326. /**
  1327. * Returns the caption of the message shown to the user when cookies are
  1328. * disabled in the browser.
  1329. *
  1330. * @return The caption of the "cookies disabled" message
  1331. */
  1332. public String getCookiesDisabledCaption() {
  1333. return (cookiesDisabledNotificationEnabled ? cookiesDisabledCaption
  1334. : null);
  1335. }
  1336. /**
  1337. * Returns the message shown to the user when cookies are disabled in
  1338. * the browser.
  1339. *
  1340. * @return The "cookies disabled" message
  1341. */
  1342. public String getCookiesDisabledMessage() {
  1343. return (cookiesDisabledNotificationEnabled ? cookiesDisabledMessage
  1344. : null);
  1345. }
  1346. }
  1347. /**
  1348. * Contains the system messages used to notify the user about various
  1349. * critical situations that can occur.
  1350. * <p>
  1351. * Vaadin gets the SystemMessages from your application by calling a static
  1352. * getSystemMessages() method. By default the
  1353. * Application.getSystemMessages() is used. You can customize this by
  1354. * defining a static MyApplication.getSystemMessages() and returning
  1355. * CustomizedSystemMessages. Note that getSystemMessages() is static -
  1356. * changing the system messages will by default change the message for all
  1357. * users of the application.
  1358. * </p>
  1359. * <p>
  1360. * The default behavior is to show a notification, and restart the
  1361. * application the the user clicks the message. <br/>
  1362. * Instead of restarting the application, you can set a specific URL that
  1363. * the user is taken to.<br/>
  1364. * Setting both caption and message to null will restart the application (or
  1365. * go to the specified URL) without displaying a notification.
  1366. * set*NotificationEnabled(false) will achieve the same thing.
  1367. * </p>
  1368. * <p>
  1369. * The situations are:
  1370. * <li>Session expired: the user session has expired, usually due to
  1371. * inactivity.</li>
  1372. * <li>Communication error: the client failed to contact the server, or the
  1373. * server returned and invalid response.</li>
  1374. * <li>Internal error: unhandled critical server error (e.g out of memory,
  1375. * database crash)
  1376. * <li>Out of sync: the client is not in sync with the server. E.g the user
  1377. * opens two windows showing the same application, but the application does
  1378. * not support this and uses the same Window instance. When the user makes
  1379. * changes in one of the windows - the other window is no longer in sync,
  1380. * and (for instance) pressing a button that is no longer present in the UI
  1381. * will cause a out-of-sync -situation.
  1382. * </p>
  1383. */
  1384. public static class CustomizedSystemMessages extends SystemMessages
  1385. implements Serializable {
  1386. /**
  1387. * Sets the URL to go to when the session has expired.
  1388. *
  1389. * @param sessionExpiredURL
  1390. * the URL to go to, or null to reload current
  1391. */
  1392. public void setSessionExpiredURL(String sessionExpiredURL) {
  1393. this.sessionExpiredURL = sessionExpiredURL;
  1394. }
  1395. /**
  1396. * Enables or disables the notification. If disabled, the set URL (or
  1397. * current) is loaded directly when next transaction between server and
  1398. * client happens.
  1399. *
  1400. * @param sessionExpiredNotificationEnabled
  1401. * true = enabled, false = disabled
  1402. */
  1403. public void setSessionExpiredNotificationEnabled(
  1404. boolean sessionExpiredNotificationEnabled) {
  1405. this.sessionExpiredNotificationEnabled = sessionExpiredNotificationEnabled;
  1406. }
  1407. /**
  1408. * Sets the caption of the notification. Set to null for no caption. If
  1409. * both caption and message are null, client automatically forwards to
  1410. * sessionExpiredUrl after timeout timer expires. Timer uses value read
  1411. * from HTTPSession.getMaxInactiveInterval()
  1412. *
  1413. * @param sessionExpiredCaption
  1414. * the caption
  1415. */
  1416. public void setSessionExpiredCaption(String sessionExpiredCaption) {
  1417. this.sessionExpiredCaption = sessionExpiredCaption;
  1418. }
  1419. /**
  1420. * Sets the message of the notification. Set to null for no message. If
  1421. * both caption and message are null, client automatically forwards to
  1422. * sessionExpiredUrl after timeout timer expires. Timer uses value read
  1423. * from HTTPSession.getMaxInactiveInterval()
  1424. *
  1425. * @param sessionExpiredMessage
  1426. * the message
  1427. */
  1428. public void setSessionExpiredMessage(String sessionExpiredMessage) {
  1429. this.sessionExpiredMessage = sessionExpiredMessage;
  1430. }
  1431. /**
  1432. * Sets the URL to go to when there is a authentication error.
  1433. *
  1434. * @param authenticationErrorURL
  1435. * the URL to go to, or null to reload current
  1436. */
  1437. public void setAuthenticationErrorURL(String authenticationErrorURL) {
  1438. this.authenticationErrorURL = authenticationErrorURL;
  1439. }
  1440. /**
  1441. * Enables or disables the notification. If disabled, the set URL (or
  1442. * current) is loaded directly.
  1443. *
  1444. * @param authenticationErrorNotificationEnabled
  1445. * true = enabled, false = disabled
  1446. */
  1447. public void setAuthenticationErrorNotificationEnabled(
  1448. boolean authenticationErrorNotificationEnabled) {
  1449. this.authenticationErrorNotificationEnabled = authenticationErrorNotificationEnabled;
  1450. }
  1451. /**
  1452. * Sets the caption of the notification. Set to null for no caption. If
  1453. * both caption and message is null, the notification is disabled;
  1454. *
  1455. * @param authenticationErrorCaption
  1456. * the caption
  1457. */
  1458. public void setAuthenticationErrorCaption(
  1459. String authenticationErrorCaption) {
  1460. this.authenticationErrorCaption = authenticationErrorCaption;
  1461. }
  1462. /**
  1463. * Sets the message of the notification. Set to null for no message. If
  1464. * both caption and message is null, the notification is disabled;
  1465. *
  1466. * @param authenticationErrorMessage
  1467. * the message
  1468. */
  1469. public void setAuthenticationErrorMessage(
  1470. String authenticationErrorMessage) {
  1471. this.authenticationErrorMessage = authenticationErrorMessage;
  1472. }
  1473. /**
  1474. * Sets the URL to go to when there is a communication error.
  1475. *
  1476. * @param communicationErrorURL
  1477. * the URL to go to, or null to reload current
  1478. */
  1479. public void setCommunicationErrorURL(String communicationErrorURL) {
  1480. this.communicationErrorURL = communicationErrorURL;
  1481. }
  1482. /**
  1483. * Enables or disables the notification. If disabled, the set URL (or
  1484. * current) is loaded directly.
  1485. *
  1486. * @param communicationErrorNotificationEnabled
  1487. * true = enabled, false = disabled
  1488. */
  1489. public void setCommunicationErrorNotificationEnabled(
  1490. boolean communicationErrorNotificationEnabled) {
  1491. this.communicationErrorNotificationEnabled = communicationErrorNotificationEnabled;
  1492. }
  1493. /**
  1494. * Sets the caption of the notification. Set to null for no caption. If
  1495. * both caption and message is null, the notification is disabled;
  1496. *
  1497. * @param communicationErrorCaption
  1498. * the caption
  1499. */
  1500. public void setCommunicationErrorCaption(
  1501. String communicationErrorCaption) {
  1502. this.communicationErrorCaption = communicationErrorCaption;
  1503. }
  1504. /**
  1505. * Sets the message of the notification. Set to null for no message. If
  1506. * both caption and message is null, the notification is disabled;
  1507. *
  1508. * @param communicationErrorMessage
  1509. * the message
  1510. */
  1511. public void setCommunicationErrorMessage(
  1512. String communicationErrorMessage) {
  1513. this.communicationErrorMessage = communicationErrorMessage;
  1514. }
  1515. /**
  1516. * Sets the URL to go to when an internal error occurs.
  1517. *
  1518. * @param internalErrorURL
  1519. * the URL to go to, or null to reload current
  1520. */
  1521. public void setInternalErrorURL(String internalErrorURL) {
  1522. this.internalErrorURL = internalErrorURL;
  1523. }
  1524. /**
  1525. * Enables or disables the notification. If disabled, the set URL (or
  1526. * current) is loaded directly.
  1527. *
  1528. * @param internalErrorNotificationEnabled
  1529. * true = enabled, false = disabled
  1530. */
  1531. public void setInternalErrorNotificationEnabled(
  1532. boolean internalErrorNotificationEnabled) {
  1533. this.internalErrorNotificationEnabled = internalErrorNotificationEnabled;
  1534. }
  1535. /**
  1536. * Sets the caption of the notification. Set to null for no caption. If
  1537. * both caption and message is null, the notification is disabled;
  1538. *
  1539. * @param internalErrorCaption
  1540. * the caption
  1541. */
  1542. public void setInternalErrorCaption(String internalErrorCaption) {
  1543. this.internalErrorCaption = internalErrorCaption;
  1544. }
  1545. /**
  1546. * Sets the message of the notification. Set to null for no message. If
  1547. * both caption and message is null, the notification is disabled;
  1548. *
  1549. * @param internalErrorMessage
  1550. * the message
  1551. */
  1552. public void setInternalErrorMessage(String internalErrorMessage) {
  1553. this.internalErrorMessage = internalErrorMessage;
  1554. }
  1555. /**
  1556. * Sets the URL to go to when the client is out-of-sync.
  1557. *
  1558. * @param outOfSyncURL
  1559. * the URL to go to, or null to reload current
  1560. */
  1561. public void setOutOfSyncURL(String outOfSyncURL) {
  1562. this.outOfSyncURL = outOfSyncURL;
  1563. }
  1564. /**
  1565. * Enables or disables the notification. If disabled, the set URL (or
  1566. * current) is loaded directly.
  1567. *
  1568. * @param outOfSyncNotificationEnabled
  1569. * true = enabled, false = disabled
  1570. */
  1571. public void setOutOfSyncNotificationEnabled(
  1572. boolean outOfSyncNotificationEnabled) {
  1573. this.outOfSyncNotificationEnabled = outOfSyncNotificationEnabled;
  1574. }
  1575. /**
  1576. * Sets the caption of the notification. Set to null for no caption. If
  1577. * both caption and message is null, the notification is disabled;
  1578. *
  1579. * @param outOfSyncCaption
  1580. * the caption
  1581. */
  1582. public void setOutOfSyncCaption(String outOfSyncCaption) {
  1583. this.outOfSyncCaption = outOfSyncCaption;
  1584. }
  1585. /**
  1586. * Sets the message of the notification. Set to null for no message. If
  1587. * both caption and message is null, the notification is disabled;
  1588. *
  1589. * @param outOfSyncMessage
  1590. * the message
  1591. */
  1592. public void setOutOfSyncMessage(String outOfSyncMessage) {
  1593. this.outOfSyncMessage = outOfSyncMessage;
  1594. }
  1595. /**
  1596. * Sets the URL to redirect to when the browser has cookies disabled.
  1597. *
  1598. * @param cookiesDisabledURL
  1599. * the URL to redirect to, or null to reload the current URL
  1600. */
  1601. public void setCookiesDisabledURL(String cookiesDisabledURL) {
  1602. this.cookiesDisabledURL = cookiesDisabledURL;
  1603. }
  1604. /**
  1605. * Enables or disables the notification for "cookies disabled" messages.
  1606. * If disabled, the URL returned by {@link #getCookiesDisabledURL()} is
  1607. * loaded directly.
  1608. *
  1609. * @param cookiesDisabledNotificationEnabled
  1610. * true to enable "cookies disabled" messages, false
  1611. * otherwise
  1612. */
  1613. public void setCookiesDisabledNotificationEnabled(
  1614. boolean cookiesDisabledNotificationEnabled) {
  1615. this.cookiesDisabledNotificationEnabled = cookiesDisabledNotificationEnabled;
  1616. }
  1617. /**
  1618. * Sets the caption of the "cookies disabled" notification. Set to null
  1619. * for no caption. If both caption and message is null, the notification
  1620. * is disabled.
  1621. *
  1622. * @param cookiesDisabledCaption
  1623. * the caption for the "cookies disabled" notification
  1624. */
  1625. public void setCookiesDisabledCaption(String cookiesDisabledCaption) {
  1626. this.cookiesDisabledCaption = cookiesDisabledCaption;
  1627. }
  1628. /**
  1629. * Sets the message of the "cookies disabled" notification. Set to null
  1630. * for no message. If both caption and message is null, the notification
  1631. * is disabled.
  1632. *
  1633. * @param cookiesDisabledMessage
  1634. * the message for the "cookies disabled" notification
  1635. */
  1636. public void setCookiesDisabledMessage(String cookiesDisabledMessage) {
  1637. this.cookiesDisabledMessage = cookiesDisabledMessage;
  1638. }
  1639. }
  1640. /**
  1641. * Application error is an error message defined on the application level.
  1642. *
  1643. * When an error occurs on the application level, this error message type
  1644. * should be used. This indicates that the problem is caused by the
  1645. * application - not by the user.
  1646. */
  1647. public class ApplicationError implements Terminal.ErrorEvent {
  1648. private final Throwable throwable;
  1649. public ApplicationError(Throwable throwable) {
  1650. this.throwable = throwable;
  1651. }
  1652. @Override
  1653. public Throwable getThrowable() {
  1654. return throwable;
  1655. }
  1656. }
  1657. /**
  1658. * Gets a root for a request for which no root is already known. This method
  1659. * is called when the framework processes a request that does not originate
  1660. * from an existing root instance. This typically happens when a host page
  1661. * is requested.
  1662. *
  1663. * <p>
  1664. * Subclasses of Application may override this method to provide custom
  1665. * logic for choosing how to create a suitable root or for picking an
  1666. * already created root. If an existing root is picked, care should be taken
  1667. * to avoid keeping the same root open in multiple browser windows, as that
  1668. * will cause the states to go out of sync.
  1669. * </p>
  1670. *
  1671. * <p>
  1672. * If {@link BrowserDetails} are required to create a Root, the
  1673. * implementation can throw a {@link RootRequiresMoreInformationException}
  1674. * exception. In this case, the framework will instruct the browser to send
  1675. * the additional details, whereupon this method is invoked again with the
  1676. * browser details present in the wrapped request. Throwing the exception if
  1677. * the browser details are already available is not supported.
  1678. * </p>
  1679. *
  1680. * <p>
  1681. * The default implementation in {@link Application} creates a new instance
  1682. * of the Root class returned by {@link #getRootClassName(WrappedRequest)},
  1683. * which in turn uses the {@value #ROOT_PARAMETER} parameter from web.xml.
  1684. * If {@link DeploymentConfiguration#getClassLoader()} for the request
  1685. * returns a {@link ClassLoader}, it is used for loading the Root class.
  1686. * Otherwise the {@link ClassLoader} used to load this class is used.
  1687. * </p>
  1688. *
  1689. * @param request
  1690. * the wrapped request for which a root is needed
  1691. * @return a root instance to use for the request
  1692. * @throws RootRequiresMoreInformationException
  1693. * may be thrown by an implementation to indicate that
  1694. * {@link BrowserDetails} are required to create a root
  1695. *
  1696. * @see #getRootClassName(WrappedRequest)
  1697. * @see Root
  1698. * @see RootRequiresMoreInformationException
  1699. * @see WrappedRequest#getBrowserDetails()
  1700. *
  1701. * @since 7.0
  1702. */
  1703. protected Root getRoot(WrappedRequest request)
  1704. throws RootRequiresMoreInformationException {
  1705. String rootClassName = getRootClassName(request);
  1706. try {
  1707. ClassLoader classLoader = request.getDeploymentConfiguration()
  1708. .getClassLoader();
  1709. if (classLoader == null) {
  1710. classLoader = getClass().getClassLoader();
  1711. }
  1712. Class<? extends Root> rootClass = Class.forName(rootClassName,
  1713. true, classLoader).asSubclass(Root.class);
  1714. try {
  1715. Root root = rootClass.newInstance();
  1716. return root;
  1717. } catch (Exception e) {
  1718. throw new RuntimeException("Could not instantiate root class "
  1719. + rootClassName, e);
  1720. }
  1721. } catch (ClassNotFoundException e) {
  1722. throw new RuntimeException("Could not load root class "
  1723. + rootClassName, e);
  1724. }
  1725. }
  1726. /**
  1727. * Provides the name of the <code>Root</code> class that should be used for
  1728. * a request. The class must have an accessible no-args constructor.
  1729. * <p>
  1730. * The default implementation uses the {@value #ROOT_PARAMETER} parameter
  1731. * from web.xml.
  1732. * </p>
  1733. * <p>
  1734. * This method is mainly used by the default implementation of
  1735. * {@link #getRoot(WrappedRequest)}. If you override that method with your
  1736. * own functionality, the results of this method might not be used.
  1737. * </p>
  1738. *
  1739. * @param request
  1740. * the request for which a new root is required
  1741. * @return the name of the root class to use
  1742. *
  1743. * @since 7.0
  1744. */
  1745. protected String getRootClassName(WrappedRequest request) {
  1746. Object rootClassNameObj = properties.get(ROOT_PARAMETER);
  1747. if (rootClassNameObj instanceof String) {
  1748. return (String) rootClassNameObj;
  1749. } else {
  1750. throw new RuntimeException("No " + ROOT_PARAMETER
  1751. + " defined in web.xml");
  1752. }
  1753. }
  1754. /**
  1755. * Finds the theme to use for a specific root. If no specific theme is
  1756. * required, <code>null</code> is returned.
  1757. *
  1758. * TODO Tell what the default implementation does once it does something.
  1759. *
  1760. * @param root
  1761. * the root to get a theme for
  1762. * @return the name of the theme, or <code>null</code> if the default theme
  1763. * should be used
  1764. *
  1765. * @since 7.0
  1766. */
  1767. public String getThemeForRoot(Root root) {
  1768. Theme rootTheme = getAnnotationFor(root.getClass(), Theme.class);
  1769. if (rootTheme != null) {
  1770. return rootTheme.value();
  1771. } else {
  1772. return null;
  1773. }
  1774. }
  1775. /**
  1776. * Finds the widgetset to use for a specific root. If no specific widgetset
  1777. * is required, <code>null</code> is returned.
  1778. *
  1779. * TODO Tell what the default implementation does once it does something.
  1780. *
  1781. * @param root
  1782. * the root to get a widgetset for
  1783. * @return the name of the widgetset, or <code>null</code> if the default
  1784. * widgetset should be used
  1785. *
  1786. * @since 7.0
  1787. */
  1788. public String getWidgetsetForRoot(Root root) {
  1789. Widgetset rootWidgetset = getAnnotationFor(root.getClass(),
  1790. Widgetset.class);
  1791. if (rootWidgetset != null) {
  1792. return rootWidgetset.value();
  1793. } else {
  1794. return null;
  1795. }
  1796. }
  1797. /**
  1798. * Helper to get an annotation for a class. If the annotation is not present
  1799. * on the target class, it's superclasses and implemented interfaces are
  1800. * also searched for the annotation.
  1801. *
  1802. * @param type
  1803. * the target class from which the annotation should be found
  1804. * @param annotationType
  1805. * the annotation type to look for
  1806. * @return an annotation of the given type, or <code>null</code> if the
  1807. * annotation is not present on the class
  1808. */
  1809. private static <T extends Annotation> T getAnnotationFor(Class<?> type,
  1810. Class<T> annotationType) {
  1811. // Find from the class hierarchy
  1812. Class<?> currentType = type;
  1813. while (currentType != Object.class) {
  1814. T annotation = currentType.getAnnotation(annotationType);
  1815. if (annotation != null) {
  1816. return annotation;
  1817. } else {
  1818. currentType = currentType.getSuperclass();
  1819. }
  1820. }
  1821. // Find from an implemented interface
  1822. for (Class<?> iface : type.getInterfaces()) {
  1823. T annotation = iface.getAnnotation(annotationType);
  1824. if (annotation != null) {
  1825. return annotation;
  1826. }
  1827. }
  1828. return null;
  1829. }
  1830. /**
  1831. * Handles a request by passing it to each registered {@link RequestHandler}
  1832. * in turn until one produces a response. This method is used for requests
  1833. * that have not been handled by any specific functionality in the terminal
  1834. * implementation (e.g. {@link AbstractApplicationServlet}).
  1835. * <p>
  1836. * The request handlers are invoked in the revere order in which they were
  1837. * added to the application until a response has been produced. This means
  1838. * that the most recently added handler is used first and the first request
  1839. * handler that was added to the application is invoked towards the end
  1840. * unless any previous handler has already produced a response.
  1841. * </p>
  1842. *
  1843. * @param request
  1844. * the wrapped request to get information from
  1845. * @param response
  1846. * the response to which data can be written
  1847. * @return returns <code>true</code> if a {@link RequestHandler} has
  1848. * produced a response and <code>false</code> if no response has
  1849. * been written.
  1850. * @throws IOException
  1851. *
  1852. * @see #addRequestHandler(RequestHandler)
  1853. * @see RequestHandler
  1854. *
  1855. * @since 7.0
  1856. */
  1857. public boolean handleRequest(WrappedRequest request,
  1858. WrappedResponse response) throws IOException {
  1859. // Use a copy to avoid ConcurrentModificationException
  1860. for (RequestHandler handler : new ArrayList<RequestHandler>(
  1861. requestHandlers)) {
  1862. if (handler.handleRequest(this, request, response)) {
  1863. return true;
  1864. }
  1865. }
  1866. // If not handled
  1867. return false;
  1868. }
  1869. /**
  1870. * Adds a request handler to this application. Request handlers can be added
  1871. * to provide responses to requests that are not handled by the default
  1872. * functionality of the framework.
  1873. * <p>
  1874. * Handlers are called in reverse order of addition, so the most recently
  1875. * added handler will be called first.
  1876. * </p>
  1877. *
  1878. * @param handler
  1879. * the request handler to add
  1880. *
  1881. * @see #handleRequest(WrappedRequest, WrappedResponse)
  1882. * @see #removeRequestHandler(RequestHandler)
  1883. *
  1884. * @since 7.0
  1885. */
  1886. public void addRequestHandler(RequestHandler handler) {
  1887. requestHandlers.addFirst(handler);
  1888. }
  1889. /**
  1890. * Removes a request handler from the application.
  1891. *
  1892. * @param handler
  1893. * the request handler to remove
  1894. *
  1895. * @since 7.0
  1896. */
  1897. public void removeRequestHandler(RequestHandler handler) {
  1898. requestHandlers.remove(handler);
  1899. }
  1900. /**
  1901. * Gets the request handlers that are registered to the application. The
  1902. * iteration order of the returned collection is the same as the order in
  1903. * which the request handlers will be invoked when a request is handled.
  1904. *
  1905. * @return a collection of request handlers, with the iteration order
  1906. * according to the order they would be invoked
  1907. *
  1908. * @see #handleRequest(WrappedRequest, WrappedResponse)
  1909. * @see #addRequestHandler(RequestHandler)
  1910. * @see #removeRequestHandler(RequestHandler)
  1911. *
  1912. * @since 7.0
  1913. */
  1914. public Collection<RequestHandler> getRequestHandlers() {
  1915. return Collections.unmodifiableCollection(requestHandlers);
  1916. }
  1917. /**
  1918. * Find an application resource with a given key.
  1919. *
  1920. * @param key
  1921. * The key of the resource
  1922. * @return The application resource corresponding to the provided key, or
  1923. * <code>null</code> if no resource is registered for the key
  1924. *
  1925. * @since 7.0
  1926. */
  1927. public ApplicationResource getResource(String key) {
  1928. return keyResourceMap.get(key);
  1929. }
  1930. /**
  1931. * Thread local for keeping track of currently used application instance
  1932. *
  1933. * @since 7.0
  1934. */
  1935. private static final ThreadLocal<Application> currentApplication = new ThreadLocal<Application>();
  1936. private boolean rootPreserved = false;
  1937. /**
  1938. * Gets the currently used application. The current application is
  1939. * automatically defined when processing requests to the server. In other
  1940. * cases, (e.g. from background threads), the current application is not
  1941. * automatically defined.
  1942. *
  1943. * @return the current application instance if available, otherwise
  1944. * <code>null</code>
  1945. *
  1946. * @see #setCurrent(Application)
  1947. *
  1948. * @since 7.0
  1949. */
  1950. public static Application getCurrent() {
  1951. return currentApplication.get();
  1952. }
  1953. /**
  1954. * Sets the thread local for the current application. This method is used by
  1955. * the framework to set the current application whenever a new request is
  1956. * processed and it is cleared when the request has been processed.
  1957. * <p>
  1958. * The application developer can also use this method to define the current
  1959. * application outside the normal request handling, e.g. when initiating
  1960. * custom background threads.
  1961. * </p>
  1962. *
  1963. * @param application
  1964. *
  1965. * @see #getCurrent()
  1966. * @see ThreadLocal
  1967. *
  1968. * @since 7.0
  1969. */
  1970. public static void setCurrent(Application application) {
  1971. currentApplication.set(application);
  1972. }
  1973. /**
  1974. * Check whether this application is in production mode. If an application
  1975. * is in production mode, certain debugging facilities are not available.
  1976. *
  1977. * @return the status of the production mode flag
  1978. *
  1979. * @since 7.0
  1980. */
  1981. public boolean isProductionMode() {
  1982. return productionMode;
  1983. }
  1984. /**
  1985. * Finds the {@link Root} to which a particular request belongs. If the
  1986. * request originates from an existing Root, that root is returned. In other
  1987. * cases, the method attempts to create and initialize a new root and might
  1988. * throw a {@link RootRequiresMoreInformationException} if all required
  1989. * information is not available.
  1990. * <p>
  1991. * Please note that this method can also return a newly created
  1992. * <code>Root</code> which has not yet been initialized. You can use
  1993. * {@link #isRootInitPending(int)} with the root's id (
  1994. * {@link Root#getRootId()} to check whether the initialization is still
  1995. * pending.
  1996. * </p>
  1997. *
  1998. * @param request
  1999. * the request for which a root is desired
  2000. * @return a root belonging to the request
  2001. * @throws RootRequiresMoreInformationException
  2002. * if no existing root could be found and creating a new root
  2003. * requires additional information from the browser
  2004. *
  2005. * @see #getRoot(WrappedRequest)
  2006. * @see RootRequiresMoreInformationException
  2007. *
  2008. * @since 7.0
  2009. */
  2010. public Root getRootForRequest(WrappedRequest request)
  2011. throws RootRequiresMoreInformationException {
  2012. Root root = Root.getCurrent();
  2013. if (root != null) {
  2014. return root;
  2015. }
  2016. Integer rootId = getRootId(request);
  2017. synchronized (this) {
  2018. BrowserDetails browserDetails = request.getBrowserDetails();
  2019. boolean hasBrowserDetails = browserDetails != null
  2020. && browserDetails.getUriFragment() != null;
  2021. root = roots.get(rootId);
  2022. if (root == null && isRootPreserved()) {
  2023. // Check for a known root
  2024. if (!retainOnRefreshRoots.isEmpty()) {
  2025. Integer retainedRootId;
  2026. if (!hasBrowserDetails) {
  2027. throw new RootRequiresMoreInformationException();
  2028. } else {
  2029. String windowName = browserDetails.getWindowName();
  2030. retainedRootId = retainOnRefreshRoots.get(windowName);
  2031. }
  2032. if (retainedRootId != null) {
  2033. rootId = retainedRootId;
  2034. root = roots.get(rootId);
  2035. }
  2036. }
  2037. }
  2038. if (root == null) {
  2039. // Throws exception if root can not yet be created
  2040. root = getRoot(request);
  2041. // Initialize some fields for a newly created root
  2042. if (root.getApplication() == null) {
  2043. root.setApplication(this);
  2044. }
  2045. if (root.getRootId() < 0) {
  2046. if (rootId == null) {
  2047. // Get the next id if none defined
  2048. rootId = Integer.valueOf(nextRootId++);
  2049. }
  2050. root.setRootId(rootId.intValue());
  2051. roots.put(rootId, root);
  2052. }
  2053. }
  2054. // Set thread local here so it is available in init
  2055. Root.setCurrent(root);
  2056. if (!initedRoots.contains(rootId)) {
  2057. boolean initRequiresBrowserDetails = isRootPreserved()
  2058. || !root.getClass()
  2059. .isAnnotationPresent(EagerInit.class);
  2060. if (!initRequiresBrowserDetails || hasBrowserDetails) {
  2061. root.doInit(request);
  2062. // Remember that this root has been initialized
  2063. initedRoots.add(rootId);
  2064. // init() might turn on preserve so do this afterwards
  2065. if (isRootPreserved()) {
  2066. // Remember this root
  2067. String windowName = request.getBrowserDetails()
  2068. .getWindowName();
  2069. retainOnRefreshRoots.put(windowName, rootId);
  2070. }
  2071. }
  2072. }
  2073. } // end synchronized block
  2074. return root;
  2075. }
  2076. /**
  2077. * Internal helper to finds the root id for a request.
  2078. *
  2079. * @param request
  2080. * the request to get the root id for
  2081. * @return a root id, or <code>null</code> if no root id is defined
  2082. *
  2083. * @since 7.0
  2084. */
  2085. private static Integer getRootId(WrappedRequest request) {
  2086. if (request instanceof CombinedRequest) {
  2087. // Combined requests has the rootid parameter in the second request
  2088. CombinedRequest combinedRequest = (CombinedRequest) request;
  2089. request = combinedRequest.getSecondRequest();
  2090. }
  2091. String rootIdString = request
  2092. .getParameter(ApplicationConstants.ROOT_ID_PARAMETER);
  2093. Integer rootId = rootIdString == null ? null
  2094. : new Integer(rootIdString);
  2095. return rootId;
  2096. }
  2097. /**
  2098. * Sets whether the same Root state should be reused if the framework can
  2099. * detect that the application is opened in a browser window where it has
  2100. * previously been open. The framework attempts to discover this by checking
  2101. * the value of window.name in the browser.
  2102. * <p>
  2103. * NOTE that you should avoid turning this feature on/off on-the-fly when
  2104. * the UI is already shown, as it might not be retained as intended.
  2105. * </p>
  2106. *
  2107. * @param rootPreserved
  2108. * <code>true</code>if the same Root instance should be reused
  2109. * e.g. when the browser window is refreshed.
  2110. */
  2111. public void setRootPreserved(boolean rootPreserved) {
  2112. this.rootPreserved = rootPreserved;
  2113. if (!rootPreserved) {
  2114. retainOnRefreshRoots.clear();
  2115. }
  2116. }
  2117. /**
  2118. * Checks whether the same Root state should be reused if the framework can
  2119. * detect that the application is opened in a browser window where it has
  2120. * previously been open. The framework attempts to discover this by checking
  2121. * the value of window.name in the browser.
  2122. *
  2123. * @return <code>true</code>if the same Root instance should be reused e.g.
  2124. * when the browser window is refreshed.
  2125. */
  2126. public boolean isRootPreserved() {
  2127. return rootPreserved;
  2128. }
  2129. /**
  2130. * Checks whether there's a pending initialization for the root with the
  2131. * given id.
  2132. *
  2133. * @param rootId
  2134. * root id to check for
  2135. * @return <code>true</code> of the initialization is pending,
  2136. * <code>false</code> if the root id is not registered or if the
  2137. * root has already been initialized
  2138. *
  2139. * @see #getRootForRequest(WrappedRequest)
  2140. */
  2141. public boolean isRootInitPending(int rootId) {
  2142. return !initedRoots.contains(Integer.valueOf(rootId));
  2143. }
  2144. /**
  2145. * Gets all the roots of this application. This includes roots that have
  2146. * been requested but not yet initialized. Please note, that roots are not
  2147. * automatically removed e.g. if the browser window is closed and that there
  2148. * is no way to manually remove a root. Inactive roots will thus not be
  2149. * released for GC until the entire application is released when the session
  2150. * has timed out (unless there are dangling references). Improved support
  2151. * for releasing unused roots is planned for an upcoming alpha release of
  2152. * Vaadin 7.
  2153. *
  2154. * @return a collection of roots belonging to this application
  2155. *
  2156. * @since 7.0
  2157. */
  2158. public Collection<Root> getRoots() {
  2159. return Collections.unmodifiableCollection(roots.values());
  2160. }
  2161. private int connectorIdSequence = 0;
  2162. /**
  2163. * Generate an id for the given Connector. Connectors must not call this
  2164. * method more than once, the first time they need an id.
  2165. *
  2166. * @param connector
  2167. * A connector that has not yet been assigned an id.
  2168. * @return A new id for the connector
  2169. */
  2170. public String createConnectorId(ClientConnector connector) {
  2171. return String.valueOf(connectorIdSequence++);
  2172. }
  2173. private static final Logger getLogger() {
  2174. return Logger.getLogger(Application.class.getName());
  2175. }
  2176. /**
  2177. * Returns a Root with the given id.
  2178. * <p>
  2179. * This is meant for framework internal use.
  2180. * </p>
  2181. *
  2182. * @param rootId
  2183. * The root id
  2184. * @return The root with the given id or null if not found
  2185. */
  2186. public Root getRootById(int rootId) {
  2187. return roots.get(rootId);
  2188. }
  2189. /**
  2190. * Adds a listener that will be invoked when the bootstrap HTML is about to
  2191. * be generated. This can be used to modify the contents of the HTML that
  2192. * loads the Vaadin application in the browser and the HTTP headers that are
  2193. * included in the response serving the HTML.
  2194. *
  2195. * @see BootstrapListener#modifyBootstrapFragment(BootstrapFragmentResponse)
  2196. * @see BootstrapListener#modifyBootstrapPage(BootstrapPageResponse)
  2197. *
  2198. * @param listener
  2199. * the bootstrap listener to add
  2200. */
  2201. public void addBootstrapListener(BootstrapListener listener) {
  2202. eventRouter.addListener(BootstrapFragmentResponse.class, listener,
  2203. BOOTSTRAP_FRAGMENT_METHOD);
  2204. eventRouter.addListener(BootstrapPageResponse.class, listener,
  2205. BOOTSTRAP_PAGE_METHOD);
  2206. }
  2207. /**
  2208. * Remove a bootstrap listener that was previously added.
  2209. *
  2210. * @see #addBootstrapListener(BootstrapListener)
  2211. *
  2212. * @param listener
  2213. * the bootstrap listener to remove
  2214. */
  2215. public void removeBootstrapListener(BootstrapListener listener) {
  2216. eventRouter.removeListener(BootstrapFragmentResponse.class, listener,
  2217. BOOTSTRAP_FRAGMENT_METHOD);
  2218. eventRouter.removeListener(BootstrapPageResponse.class, listener,
  2219. BOOTSTRAP_PAGE_METHOD);
  2220. }
  2221. /**
  2222. * Fires a bootstrap event to all registered listeners. There are currently
  2223. * two supported events, both inheriting from {@link BootstrapResponse}:
  2224. * {@link BootstrapFragmentResponse} and {@link BootstrapPageResponse}.
  2225. *
  2226. * @param response
  2227. * the bootstrap response event for which listeners should be
  2228. * fired
  2229. */
  2230. public void modifyBootstrapResponse(BootstrapResponse response) {
  2231. eventRouter.fireEvent(response);
  2232. }
  2233. }