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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit;
  5. import java.net.MalformedURLException;
  6. import java.net.SocketException;
  7. import java.net.URL;
  8. import java.util.Collection;
  9. import java.util.Collections;
  10. import java.util.Enumeration;
  11. import java.util.EventListener;
  12. import java.util.EventObject;
  13. import java.util.Hashtable;
  14. import java.util.Iterator;
  15. import java.util.LinkedList;
  16. import java.util.Locale;
  17. import java.util.Properties;
  18. import java.util.Random;
  19. import com.itmill.toolkit.service.ApplicationContext;
  20. import com.itmill.toolkit.terminal.ApplicationResource;
  21. import com.itmill.toolkit.terminal.DownloadStream;
  22. import com.itmill.toolkit.terminal.ErrorMessage;
  23. import com.itmill.toolkit.terminal.ParameterHandler;
  24. import com.itmill.toolkit.terminal.SystemError;
  25. import com.itmill.toolkit.terminal.Terminal;
  26. import com.itmill.toolkit.terminal.URIHandler;
  27. import com.itmill.toolkit.terminal.VariableOwner;
  28. import com.itmill.toolkit.terminal.gwt.server.ChangeVariablesErrorEvent;
  29. import com.itmill.toolkit.ui.AbstractComponent;
  30. import com.itmill.toolkit.ui.Component;
  31. import com.itmill.toolkit.ui.Window;
  32. import com.itmill.toolkit.ui.Component.Focusable;
  33. /**
  34. * <p>
  35. * Base class required for all IT Mill Toolkit applications. This class provides
  36. * all the basic services required by the toolkit. These services allow external
  37. * discovery and manipulation of the user, {@link com.itmill.toolkit.ui.Window
  38. * windows} and themes, and starting and stopping the application.
  39. * </p>
  40. *
  41. * <p>
  42. * As mentioned, all IT Mill Toolkit applications must inherit this class.
  43. * However, this is almost all of what one needs to do to create a fully
  44. * functional application. The only thing a class inheriting the
  45. * <code>Application</code> needs to do is implement the <code>init</code>
  46. * method where it creates the windows it needs to perform its function. Note
  47. * that all applications must have at least one window: the main window. The
  48. * first unnamed window constructed by an application automatically becomes the
  49. * main window which behaves just like other windows with one exception: when
  50. * accessing windows using URLs the main window corresponds to the application
  51. * URL whereas other windows correspond to a URL gotten by catenating the
  52. * window's name to the application URL.
  53. * </p>
  54. *
  55. * <p>
  56. * See the class <code>com.itmill.toolkit.demo.HelloWorld</code> for a simple
  57. * example of a fully working application.
  58. * </p>
  59. *
  60. * <p>
  61. * <strong>Window access.</strong> <code>Application</code> provides methods to
  62. * list, add and remove the windows it contains.
  63. * </p>
  64. *
  65. * <p>
  66. * <strong>Execution control.</strong> This class includes method to start and
  67. * finish the execution of the application. Being finished means basically that
  68. * no windows will be available from the application anymore.
  69. * </p>
  70. *
  71. * <p>
  72. * <strong>Theme selection.</strong> The theme selection process allows a theme
  73. * to be specified at three different levels. When a window's theme needs to be
  74. * found out, the window itself is queried for a preferred theme. If the window
  75. * does not prefer a specific theme, the application containing the window is
  76. * queried. If neither the application prefers a theme, the default theme for
  77. * the {@link com.itmill.toolkit.terminal.Terminal terminal} is used. The
  78. * terminal always defines a default theme.
  79. * </p>
  80. *
  81. * @author IT Mill Ltd.
  82. * @version
  83. * @VERSION@
  84. * @since 3.0
  85. */
  86. public abstract class Application implements URIHandler, Terminal.ErrorListener {
  87. /**
  88. * Random window name generator.
  89. */
  90. private static Random nameGenerator = new Random();
  91. /**
  92. * Application context the application is running in.
  93. */
  94. private ApplicationContext context;
  95. /**
  96. * The current user or <code>null</code> if no user has logged in.
  97. */
  98. private Object user;
  99. /**
  100. * Mapping from window name to window instance.
  101. */
  102. private final Hashtable windows = new Hashtable();
  103. /**
  104. * Main window of the application.
  105. */
  106. private Window mainWindow = null;
  107. /**
  108. * The application's URL.
  109. */
  110. private URL applicationUrl;
  111. /**
  112. * Name of the theme currently used by the application.
  113. */
  114. private String theme = null;
  115. /**
  116. * Application status.
  117. */
  118. private boolean applicationIsRunning = false;
  119. /**
  120. * Application properties.
  121. */
  122. private Properties properties;
  123. /**
  124. * Default locale of the application.
  125. */
  126. private Locale locale;
  127. /**
  128. * List of listeners listening user changes.
  129. */
  130. private LinkedList userChangeListeners = null;
  131. /**
  132. * Window attach listeners.
  133. */
  134. private LinkedList windowAttachListeners = null;
  135. /**
  136. * Window detach listeners.
  137. */
  138. private LinkedList windowDetachListeners = null;
  139. /**
  140. * Application resource mapping: key <-> resource.
  141. */
  142. private final Hashtable resourceKeyMap = new Hashtable();
  143. private final Hashtable keyResourceMap = new Hashtable();
  144. private long lastResourceKeyNumber = 0;
  145. /**
  146. * URL where the user is redirected to on application close, or null if
  147. * application is just closed without redirection.
  148. */
  149. private String logoutURL = null;
  150. /**
  151. * Experimental API, not finalized. The default SystemMessages (read-only).
  152. * Change by overriding getSystemMessages() and returning
  153. * CustomizedSystemMessages
  154. */
  155. private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages();
  156. private Focusable pendingFocus;
  157. /**
  158. * Application wide error handler which is used by default if an error is
  159. * left unhandled.
  160. */
  161. private Terminal.ErrorListener errorHandler = this;
  162. /**
  163. * <p>
  164. * Gets a window by name. Returns <code>null</code> if the application is
  165. * not running or it does not contain a window corresponding to the name.
  166. * </p>
  167. *
  168. * <p>
  169. * Since version 5.0 all windows can be referenced by their names in url
  170. * <code>http://host:port/foo/bar/</code> where
  171. * <code>http://host:port/foo/</code> is the application url as returned by
  172. * getURL() and <code>bar</code> is the name of the window.
  173. * </p>
  174. *
  175. * <p>
  176. * One should note that this method can, as a side effect create new windows
  177. * if needed by the application. This can be achieved by overriding the
  178. * default implementation.
  179. * </p>
  180. *
  181. * <p>
  182. * The method should return null if the window does not exists (and is not
  183. * created as a side-effect) or if the application is not running anymore
  184. * </p>
  185. * .
  186. *
  187. * @param name
  188. * the name of the window.
  189. * @return the window associated with the given URI or <code>null</code>
  190. */
  191. public Window getWindow(String name) {
  192. // For closed app, do not give any windows
  193. if (!isRunning()) {
  194. return null;
  195. }
  196. // Gets the window by name
  197. final Window window = (Window) windows.get(name);
  198. return window;
  199. }
  200. /**
  201. * Adds a new window to the application.
  202. *
  203. * <p>
  204. * This implicitly invokes the
  205. * {@link com.itmill.toolkit.ui.Window#setApplication(Application)} method.
  206. * </p>
  207. *
  208. * <p>
  209. * Note that all application-level windows can be accessed by their names in
  210. * url <code>http://host:port/foo/bar/</code> where
  211. * <code>http://host:port/foo/</code> is the application url as returned by
  212. * getURL() and <code>bar</code> is the name of the window. Also note that
  213. * not all windows should be added to application - one can also add windows
  214. * inside other windows - these windows show as smaller windows inside those
  215. * windows.
  216. * </p>
  217. *
  218. * @param window
  219. * the new <code>Window</code> to add. If the name of the window
  220. * is <code>null</code>, an unique name is automatically given
  221. * for the window.
  222. * @throws IllegalArgumentException
  223. * if a window with the same name as the new window already
  224. * exists in the application.
  225. * @throws NullPointerException
  226. * if the given <code>Window</code> is <code>null</code>.
  227. */
  228. public void addWindow(Window window) throws IllegalArgumentException,
  229. NullPointerException {
  230. // Nulls can not be added to application
  231. if (window == null) {
  232. return;
  233. }
  234. // Gets the naming proposal from window
  235. String name = window.getName();
  236. // Checks that the application does not already contain
  237. // window having the same name
  238. if (name != null && windows.containsKey(name)) {
  239. // If the window is already added
  240. if (window == windows.get(name)) {
  241. return;
  242. }
  243. // Otherwise complain
  244. throw new IllegalArgumentException("Window with name '"
  245. + window.getName()
  246. + "' is already present in the application");
  247. }
  248. // If the name of the window is null, the window is automatically named
  249. if (name == null) {
  250. boolean accepted = false;
  251. while (!accepted) {
  252. // Try another name
  253. name = String.valueOf(Math.abs(nameGenerator.nextInt()));
  254. if (!windows.containsKey(name)) {
  255. accepted = true;
  256. }
  257. }
  258. window.setName(name);
  259. }
  260. // Adds the window to application
  261. windows.put(name, window);
  262. window.setApplication(this);
  263. fireWindowAttachEvent(window);
  264. // If no main window is set, declare the window to be main window
  265. if (getMainWindow() == null) {
  266. mainWindow = window;
  267. }
  268. }
  269. /**
  270. * Send information to all listeners about new Windows associated with this
  271. * application.
  272. *
  273. * @param window
  274. */
  275. private void fireWindowAttachEvent(Window window) {
  276. // Fires the window attach event
  277. if (windowAttachListeners != null) {
  278. final Object[] listeners = windowAttachListeners.toArray();
  279. final WindowAttachEvent event = new WindowAttachEvent(window);
  280. for (int i = 0; i < listeners.length; i++) {
  281. ((WindowAttachListener) listeners[i]).windowAttached(event);
  282. }
  283. }
  284. }
  285. /**
  286. * Removes the specified window from the application.
  287. *
  288. * @param window
  289. * the window to be removed.
  290. */
  291. public void removeWindow(Window window) {
  292. if (window != null && windows.contains(window)) {
  293. // Removes the window from application
  294. windows.remove(window.getName());
  295. // If the window was main window, clear it
  296. if (getMainWindow() == window) {
  297. setMainWindow(null);
  298. }
  299. // Removes the application from window
  300. if (window.getApplication() == this) {
  301. window.setApplication(null);
  302. }
  303. fireWindowDetachEvent(window);
  304. }
  305. }
  306. private void fireWindowDetachEvent(Window window) {
  307. // Fires the window detach event
  308. if (windowDetachListeners != null) {
  309. final Object[] listeners = windowDetachListeners.toArray();
  310. final WindowDetachEvent event = new WindowDetachEvent(window);
  311. for (int i = 0; i < listeners.length; i++) {
  312. ((WindowDetachListener) listeners[i]).windowDetached(event);
  313. }
  314. }
  315. }
  316. /**
  317. * Gets the user of the application.
  318. *
  319. * @return the User of the application.
  320. */
  321. public Object getUser() {
  322. return user;
  323. }
  324. /**
  325. * <p>
  326. * Sets the user of the application instance. An application instance may
  327. * have a user associated to it. This can be set in login procedure or
  328. * application initialization.
  329. * </p>
  330. * <p>
  331. * A component performing the user login procedure can assign the user
  332. * property of the application and make the user object available to other
  333. * components of the application.
  334. * </p>
  335. *
  336. * @param user
  337. * the new user.
  338. */
  339. public void setUser(Object user) {
  340. final Object prevUser = this.user;
  341. if (user != prevUser && (user == null || !user.equals(prevUser))) {
  342. this.user = user;
  343. if (userChangeListeners != null) {
  344. final Object[] listeners = userChangeListeners.toArray();
  345. final UserChangeEvent event = new UserChangeEvent(this, user,
  346. prevUser);
  347. for (int i = 0; i < listeners.length; i++) {
  348. ((UserChangeListener) listeners[i])
  349. .applicationUserChanged(event);
  350. }
  351. }
  352. }
  353. }
  354. /**
  355. * Gets the URL of the application.
  356. *
  357. * @return the application's URL.
  358. */
  359. public URL getURL() {
  360. return applicationUrl;
  361. }
  362. /**
  363. * Ends the Application. In effect this will cause the application stop
  364. * returning any windows when asked.
  365. */
  366. public void close() {
  367. applicationIsRunning = false;
  368. }
  369. /**
  370. * Starts the application on the given URL.After this call the application
  371. * corresponds to the given URL and it will return windows when asked for
  372. * them.
  373. *
  374. * Application properties are defined by servlet configuration object
  375. * {@link javax.servlet.ServletConfig} and they are overridden by
  376. * context-wide initialization parameters
  377. * {@link javax.servlet.ServletContext}.
  378. *
  379. * @param applicationUrl
  380. * the URL the application should respond to.
  381. * @param applicationProperties
  382. * the Application properties as specified by the servlet
  383. * configuration.
  384. * @param context
  385. * the context application will be running in.
  386. *
  387. */
  388. public void start(URL applicationUrl, Properties applicationProperties,
  389. ApplicationContext context) {
  390. this.applicationUrl = applicationUrl;
  391. properties = applicationProperties;
  392. this.context = context;
  393. init();
  394. applicationIsRunning = true;
  395. }
  396. /**
  397. * Tests if the application is running or if it has been finished.
  398. *
  399. * @return <code>true</code> if the application is running,
  400. * <code>false</code> if not.
  401. */
  402. public boolean isRunning() {
  403. return applicationIsRunning;
  404. }
  405. /**
  406. * Gets the set of windows contained by the application.
  407. *
  408. * @return the Unmodifiable collection of windows.
  409. */
  410. public Collection getWindows() {
  411. return Collections.unmodifiableCollection(windows.values());
  412. }
  413. /**
  414. * <p>
  415. * Main initializer of the application. The <code>init</code> method is
  416. * called by the framework when the application is started, and it should
  417. * perform whatever initialization operations the application needs, such as
  418. * creating windows and adding components to them.
  419. * </p>
  420. */
  421. public abstract void init();
  422. /**
  423. * Gets the application's theme. The application's theme is the default
  424. * theme used by all the windows in it that do not explicitly specify a
  425. * theme. If the application theme is not explicitly set, the
  426. * <code>null</code> is returned.
  427. *
  428. * @return the name of the application's theme.
  429. */
  430. public String getTheme() {
  431. return theme;
  432. }
  433. /**
  434. * Sets the application's theme.
  435. * <p>
  436. * Note that this theme can be overridden by the windows. <code>null</code>
  437. * implies the default terminal theme.
  438. * </p>
  439. *
  440. * @param theme
  441. * the new theme for this application.
  442. */
  443. public void setTheme(String theme) {
  444. // Collect list of windows not having the current or future theme
  445. final LinkedList toBeUpdated = new LinkedList();
  446. final String myTheme = getTheme();
  447. for (final Iterator i = getWindows().iterator(); i.hasNext();) {
  448. final Window w = (Window) i.next();
  449. final String windowTheme = w.getTheme();
  450. if ((windowTheme == null)
  451. || (!theme.equals(windowTheme) && windowTheme
  452. .equals(myTheme))) {
  453. toBeUpdated.add(w);
  454. }
  455. }
  456. // Updates the theme
  457. this.theme = theme;
  458. // Ask windows to update themselves
  459. for (final Iterator i = toBeUpdated.iterator(); i.hasNext();) {
  460. ((Window) i.next()).requestRepaint();
  461. }
  462. }
  463. /**
  464. * Gets the mainWindow of the application.
  465. *
  466. * @return the main window.
  467. */
  468. public Window getMainWindow() {
  469. return mainWindow;
  470. }
  471. /**
  472. * <p>
  473. * Sets the mainWindow. If the main window is not explicitly set, the main
  474. * window defaults to first created window. Setting window as a main window
  475. * of this application also adds the window to this application.
  476. * </p>
  477. *
  478. * @param mainWindow
  479. * the mainWindow to set.
  480. */
  481. public void setMainWindow(Window mainWindow) {
  482. addWindow(mainWindow);
  483. this.mainWindow = mainWindow;
  484. }
  485. /**
  486. * Returns an enumeration of all the names in this application.
  487. *
  488. * See {@link #start(URL, Properties, ApplicationContext)} how properties
  489. * are defined.
  490. *
  491. * @return an enumeration of all the keys in this property list, including
  492. * the keys in the default property list.
  493. *
  494. */
  495. public Enumeration getPropertyNames() {
  496. return properties.propertyNames();
  497. }
  498. /**
  499. * Searches for the property with the specified name in this application.
  500. * This method returns <code>null</code> if the property is not found.
  501. *
  502. * See {@link #start(URL, Properties, ApplicationContext)} how properties
  503. * are defined.
  504. *
  505. * @param name
  506. * the name of the property.
  507. * @return the value in this property list with the specified key value.
  508. */
  509. public String getProperty(String name) {
  510. return properties.getProperty(name);
  511. }
  512. /**
  513. * Adds new resource to the application. The resource can be accessed by the
  514. * user of the application.
  515. *
  516. * @param resource
  517. * the resource to add.
  518. */
  519. public void addResource(ApplicationResource resource) {
  520. // Check if the resource is already mapped
  521. if (resourceKeyMap.containsKey(resource)) {
  522. return;
  523. }
  524. // Generate key
  525. final String key = String.valueOf(++lastResourceKeyNumber);
  526. // Add the resource to mappings
  527. resourceKeyMap.put(resource, key);
  528. keyResourceMap.put(key, resource);
  529. }
  530. /**
  531. * Removes the resource from the application.
  532. *
  533. * @param resource
  534. * the resource to remove.
  535. */
  536. public void removeResource(ApplicationResource resource) {
  537. final Object key = resourceKeyMap.get(resource);
  538. if (key != null) {
  539. resourceKeyMap.remove(resource);
  540. keyResourceMap.remove(key);
  541. }
  542. }
  543. /**
  544. * Gets the relative uri of the resource.
  545. *
  546. * @param resource
  547. * the resource to get relative location.
  548. * @return the relative uri of the resource.
  549. */
  550. public String getRelativeLocation(ApplicationResource resource) {
  551. // Gets the key
  552. final String key = (String) resourceKeyMap.get(resource);
  553. // If the resource is not registered, return null
  554. if (key == null) {
  555. return null;
  556. }
  557. final String filename = resource.getFilename();
  558. if (filename == null) {
  559. return "APP/" + key + "/";
  560. } else {
  561. return "APP/" + key + "/" + filename;
  562. }
  563. }
  564. /**
  565. * This method gets called by terminal. It has lots of duties like to pass
  566. * uri handler to proper uri handlers registered to windows etc.
  567. *
  568. * In most situations developers should NOT OVERRIDE this method. Instead
  569. * developers should implement and register uri handlers to windows.
  570. *
  571. * @see com.itmill.toolkit.terminal.URIHandler#handleURI(URL, String)
  572. */
  573. public DownloadStream handleURI(URL context, String relativeUri) {
  574. // If the relative uri is null, we are ready
  575. if (relativeUri == null) {
  576. return null;
  577. }
  578. // Resolves the prefix
  579. String prefix = relativeUri;
  580. final int index = relativeUri.indexOf('/');
  581. if (index >= 0) {
  582. prefix = relativeUri.substring(0, index);
  583. }
  584. // Handles the resource requests
  585. if (prefix.equals("APP")) {
  586. // Handles the resource request
  587. final int next = relativeUri.indexOf('/', index + 1);
  588. if (next < 0) {
  589. return null;
  590. }
  591. final String key = relativeUri.substring(index + 1, next);
  592. final ApplicationResource resource = (ApplicationResource) keyResourceMap
  593. .get(key);
  594. if (resource != null) {
  595. DownloadStream stream = resource.getStream();
  596. if (stream != null) {
  597. stream.setCacheTime(resource.getCacheTime());
  598. return stream;
  599. }
  600. }
  601. // Resource requests override uri handling
  602. return null;
  603. }
  604. // If the uri is in some window, handle the window uri
  605. Window window = getWindow(prefix);
  606. if (window != null) {
  607. URL windowContext;
  608. try {
  609. windowContext = new URL(context, prefix + "/");
  610. final String windowUri = relativeUri.length() > prefix.length() + 1 ? relativeUri
  611. .substring(prefix.length() + 1)
  612. : "";
  613. return window.handleURI(windowContext, windowUri);
  614. } catch (final MalformedURLException e) {
  615. terminalError(new ApplicationError(e));
  616. return null;
  617. }
  618. }
  619. // If the uri was not pointing to a window, handle the
  620. // uri in main window
  621. window = getMainWindow();
  622. if (window != null) {
  623. return window.handleURI(context, relativeUri);
  624. }
  625. return null;
  626. }
  627. /**
  628. * Gets the default locale for this application.
  629. *
  630. * @return the locale of this application.
  631. */
  632. public Locale getLocale() {
  633. if (locale != null) {
  634. return locale;
  635. }
  636. return Locale.getDefault();
  637. }
  638. /**
  639. * Sets the default locale for this application.
  640. *
  641. * @param locale
  642. * the Locale object.
  643. *
  644. */
  645. public void setLocale(Locale locale) {
  646. this.locale = locale;
  647. }
  648. /**
  649. * <p>
  650. * An event that characterizes a change in the current selection.
  651. * </p>
  652. * Application user change event sent when the setUser is called to change
  653. * the current user of the application.
  654. *
  655. * @version
  656. * @VERSION@
  657. * @since 3.0
  658. */
  659. public class UserChangeEvent extends java.util.EventObject {
  660. /**
  661. * Serial generated by eclipse.
  662. */
  663. private static final long serialVersionUID = 3544951069307188281L;
  664. /**
  665. * New user of the application.
  666. */
  667. private final Object newUser;
  668. /**
  669. * Previous user of the application.
  670. */
  671. private final Object prevUser;
  672. /**
  673. * Constructor for user change event.
  674. *
  675. * @param source
  676. * the application source.
  677. * @param newUser
  678. * the new User.
  679. * @param prevUser
  680. * the previous User.
  681. */
  682. public UserChangeEvent(Application source, Object newUser,
  683. Object prevUser) {
  684. super(source);
  685. this.newUser = newUser;
  686. this.prevUser = prevUser;
  687. }
  688. /**
  689. * Gets the new user of the application.
  690. *
  691. * @return the new User.
  692. */
  693. public Object getNewUser() {
  694. return newUser;
  695. }
  696. /**
  697. * Gets the previous user of the application.
  698. *
  699. * @return the previous Toolkit user, if user has not changed ever on
  700. * application it returns <code>null</code>
  701. */
  702. public Object getPreviousUser() {
  703. return prevUser;
  704. }
  705. /**
  706. * Gets the application where the user change occurred.
  707. *
  708. * @return the Application.
  709. */
  710. public Application getApplication() {
  711. return (Application) getSource();
  712. }
  713. }
  714. /**
  715. * The <code>UserChangeListener</code> interface for listening application
  716. * user changes.
  717. *
  718. * @version
  719. * @VERSION@
  720. * @since 3.0
  721. */
  722. public interface UserChangeListener extends EventListener {
  723. /**
  724. * The <code>applicationUserChanged</code> method Invoked when the
  725. * application user has changed.
  726. *
  727. * @param event
  728. * the change event.
  729. */
  730. public void applicationUserChanged(Application.UserChangeEvent event);
  731. }
  732. /**
  733. * Adds the user change listener.
  734. *
  735. * @param listener
  736. * the user change listener to add.
  737. */
  738. public void addListener(UserChangeListener listener) {
  739. if (userChangeListeners == null) {
  740. userChangeListeners = new LinkedList();
  741. }
  742. userChangeListeners.add(listener);
  743. }
  744. /**
  745. * Removes the user change listener.
  746. *
  747. * @param listener
  748. * the user change listener to remove.
  749. */
  750. public void removeListener(UserChangeListener listener) {
  751. if (userChangeListeners == null) {
  752. return;
  753. }
  754. userChangeListeners.remove(listener);
  755. if (userChangeListeners.isEmpty()) {
  756. userChangeListeners = null;
  757. }
  758. }
  759. /**
  760. * Window detach event.
  761. */
  762. public class WindowDetachEvent extends EventObject {
  763. /**
  764. * Serial generated by eclipse.
  765. */
  766. private static final long serialVersionUID = 3544669568644691769L;
  767. private final Window window;
  768. /**
  769. * Creates a event.
  770. *
  771. * @param window
  772. * the Detached window.
  773. */
  774. public WindowDetachEvent(Window window) {
  775. super(Application.this);
  776. this.window = window;
  777. }
  778. /**
  779. * Gets the detached window.
  780. *
  781. * @return the detached window.
  782. */
  783. public Window getWindow() {
  784. return window;
  785. }
  786. /**
  787. * Gets the application from which the window was detached.
  788. *
  789. * @return the Application.
  790. */
  791. public Application getApplication() {
  792. return (Application) getSource();
  793. }
  794. }
  795. /**
  796. * Window attach event.
  797. */
  798. public class WindowAttachEvent extends EventObject {
  799. /**
  800. * Serial generated by eclipse.
  801. */
  802. private static final long serialVersionUID = 3977578104367822392L;
  803. private final Window window;
  804. /**
  805. * Creates a event.
  806. *
  807. * @param window
  808. * the Attached window.
  809. */
  810. public WindowAttachEvent(Window window) {
  811. super(Application.this);
  812. this.window = window;
  813. }
  814. /**
  815. * Gets the attached window.
  816. *
  817. * @return the attached window.
  818. */
  819. public Window getWindow() {
  820. return window;
  821. }
  822. /**
  823. * Gets the application to which the window was attached.
  824. *
  825. * @return the Application.
  826. */
  827. public Application getApplication() {
  828. return (Application) getSource();
  829. }
  830. }
  831. /**
  832. * Window attach listener interface.
  833. */
  834. public interface WindowAttachListener {
  835. /**
  836. * Window attached
  837. *
  838. * @param event
  839. * the window attach event.
  840. */
  841. public void windowAttached(WindowAttachEvent event);
  842. }
  843. /**
  844. * Window detach listener interface.
  845. */
  846. public interface WindowDetachListener {
  847. /**
  848. * Window detached.
  849. *
  850. * @param event
  851. * the window detach event.
  852. */
  853. public void windowDetached(WindowDetachEvent event);
  854. }
  855. /**
  856. * Adds the window attach listener.
  857. *
  858. * @param listener
  859. * the window attach listener to add.
  860. */
  861. public void addListener(WindowAttachListener listener) {
  862. if (windowAttachListeners == null) {
  863. windowAttachListeners = new LinkedList();
  864. }
  865. windowAttachListeners.add(listener);
  866. }
  867. /**
  868. * Adds the window detach listener.
  869. *
  870. * @param listener
  871. * the window detach listener to add.
  872. */
  873. public void addListener(WindowDetachListener listener) {
  874. if (windowDetachListeners == null) {
  875. windowDetachListeners = new LinkedList();
  876. }
  877. windowDetachListeners.add(listener);
  878. }
  879. /**
  880. * Removes the window attach listener.
  881. *
  882. * @param listener
  883. * the window attach listener to remove.
  884. */
  885. public void removeListener(WindowAttachListener listener) {
  886. if (windowAttachListeners != null) {
  887. windowAttachListeners.remove(listener);
  888. if (windowAttachListeners.isEmpty()) {
  889. windowAttachListeners = null;
  890. }
  891. }
  892. }
  893. /**
  894. * Removes the window detach listener.
  895. *
  896. * @param listener
  897. * the window detach listener to remove.
  898. */
  899. public void removeListener(WindowDetachListener listener) {
  900. if (windowDetachListeners != null) {
  901. windowDetachListeners.remove(listener);
  902. if (windowDetachListeners.isEmpty()) {
  903. windowDetachListeners = null;
  904. }
  905. }
  906. }
  907. /**
  908. * Returns the URL user is redirected to on application close. If the URL is
  909. * <code>null</code>, the application is closed normally as defined by the
  910. * application running environment.
  911. * <p>
  912. * Desktop application just closes the application window and
  913. * web-application redirects the browser to application main URL.
  914. * </p>
  915. *
  916. * @return the URL.
  917. */
  918. public String getLogoutURL() {
  919. return logoutURL;
  920. }
  921. /**
  922. * Sets the URL user is redirected to on application close. If the URL is
  923. * <code>null</code>, the application is closed normally as defined by the
  924. * application running environment: Desktop application just closes the
  925. * application window and web-application redirects the browser to
  926. * application main URL.
  927. *
  928. * @param logoutURL
  929. * the logoutURL to set.
  930. */
  931. public void setLogoutURL(String logoutURL) {
  932. this.logoutURL = logoutURL;
  933. }
  934. /**
  935. * Experimental API, not finalized. Gets the SystemMessages for this
  936. * application. SystemMessages are used to notify the user of various
  937. * critical situations that can occur, such as session expiration,
  938. * client/server out of sync, and internal server error.
  939. *
  940. * You can customize the messages by overriding this method and returning
  941. * {@link CustomizedSystemMessages}
  942. *
  943. * @return the SystemMessages for this application
  944. */
  945. public static SystemMessages getSystemMessages() {
  946. return DEFAULT_SYSTEM_MESSAGES;
  947. }
  948. /**
  949. * <p>
  950. * Invoked by the terminal on any exception that occurs in application and
  951. * is thrown by the <code>setVariable</code> to the terminal. The default
  952. * implementation sets the exceptions as <code>ComponentErrors</code> to the
  953. * component that initiated the exception and prints stack trace to standard
  954. * error stream.
  955. * </p>
  956. * <p>
  957. * You can safely override this method in your application in order to
  958. * direct the errors to some other destination (for example log).
  959. * </p>
  960. *
  961. * @param event
  962. * the change event.
  963. * @see com.itmill.toolkit.terminal.Terminal.ErrorListener#terminalError(com.itmill.toolkit.terminal.Terminal.ErrorEvent)
  964. */
  965. public void terminalError(Terminal.ErrorEvent event) {
  966. Throwable t = event.getThrowable();
  967. if (t instanceof SocketException) {
  968. // Most likely client browser closed socket
  969. System.err
  970. .println("Warning: SocketException in CommunicationManager."
  971. + " Most likely client (browser) closed socket.");
  972. return;
  973. }
  974. // Finds the original source of the error/exception
  975. Object owner = null;
  976. if (event instanceof VariableOwner.ErrorEvent) {
  977. owner = ((VariableOwner.ErrorEvent) event).getVariableOwner();
  978. } else if (event instanceof URIHandler.ErrorEvent) {
  979. owner = ((URIHandler.ErrorEvent) event).getURIHandler();
  980. } else if (event instanceof ParameterHandler.ErrorEvent) {
  981. owner = ((ParameterHandler.ErrorEvent) event).getParameterHandler();
  982. } else if (event instanceof ChangeVariablesErrorEvent) {
  983. owner = ((ChangeVariablesErrorEvent) event).getComponent();
  984. }
  985. // Shows the error in AbstractComponent
  986. if (owner instanceof AbstractComponent) {
  987. final Throwable e = event.getThrowable();
  988. if (e instanceof ErrorMessage) {
  989. ((AbstractComponent) owner).setComponentError((ErrorMessage) e);
  990. } else {
  991. ((AbstractComponent) owner)
  992. .setComponentError(new SystemError(e));
  993. }
  994. } else {
  995. /*
  996. * Can't show it to the user in any way so we print to standard
  997. * error
  998. */
  999. t.printStackTrace();
  1000. }
  1001. }
  1002. /**
  1003. * Gets the application context.
  1004. * <p>
  1005. * The application context is the environment where the application is
  1006. * running in.
  1007. * </p>
  1008. *
  1009. * @return the application context.
  1010. */
  1011. public ApplicationContext getContext() {
  1012. return context;
  1013. }
  1014. /**
  1015. * @deprecated Call component's focus method instead.
  1016. *
  1017. * @param focusable
  1018. */
  1019. public void setFocusedComponent(Focusable focusable) {
  1020. pendingFocus = focusable;
  1021. }
  1022. /**
  1023. * Gets and nulls focused component in this window
  1024. *
  1025. * @deprecated This method will be replaced with focus listener in the
  1026. * future releases.
  1027. * @return Focused component or null if none is focused.
  1028. */
  1029. public Component.Focusable consumeFocus() {
  1030. final Component.Focusable f = pendingFocus;
  1031. pendingFocus = null;
  1032. return f;
  1033. }
  1034. /**
  1035. * Override this method to return correct version number of your
  1036. * Application. Version information is delivered for example to Testing
  1037. * Tools test results.
  1038. *
  1039. * @return version string
  1040. */
  1041. public String getVersion() {
  1042. return "NONVERSIONED";
  1043. }
  1044. /**
  1045. * Gets the application error handler.
  1046. *
  1047. * The default error handler is the application itself.
  1048. *
  1049. * @return Application error handler
  1050. */
  1051. public Terminal.ErrorListener getErrorHandler() {
  1052. return errorHandler;
  1053. }
  1054. /**
  1055. * Sets the application error handler.
  1056. *
  1057. * The default error handler is the application itself.
  1058. *
  1059. * @param errorHandler
  1060. */
  1061. public void setErrorHandler(Terminal.ErrorListener errorHandler) {
  1062. this.errorHandler = errorHandler;
  1063. }
  1064. /**
  1065. * Experimental API, not finalized. Contains the system messages used to
  1066. * notify the user about various critical situations that can occur.
  1067. * <p>
  1068. * Customize by overriding the static Application.getSystemMessages() and
  1069. * returning {@link CustomizedSystemMessages}
  1070. * </p>
  1071. *
  1072. */
  1073. public static class SystemMessages {
  1074. protected String sessionExpiredURL = null;
  1075. protected boolean sessionExpiredNotificationEnabled = true;
  1076. protected String sessionExpiredCaption = "Session Expired";
  1077. protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
  1078. protected String internalErrorURL = null;
  1079. protected boolean internalErrorNotificationEnabled = true;
  1080. protected String internalErrorCaption = "Internal Error";
  1081. protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> to continue.";
  1082. protected String outOfSyncURL = null;
  1083. protected boolean outOfSyncNotificationEnabled = true;
  1084. protected String outOfSyncCaption = "Out of sync";
  1085. 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.";
  1086. /**
  1087. * Use {@link CustomizedSystemMessages} to customize
  1088. */
  1089. private SystemMessages() {
  1090. }
  1091. /**
  1092. * @return the URL to load on null for restart
  1093. */
  1094. public String getSessionExpiredURL() {
  1095. return sessionExpiredURL;
  1096. }
  1097. /**
  1098. * @return true = notification enabled, false = notification disabled
  1099. */
  1100. public boolean isSessionExpiredNotificationEnabled() {
  1101. return sessionExpiredNotificationEnabled;
  1102. }
  1103. /**
  1104. * @return the notification caption, or null for no caption
  1105. */
  1106. public String getSessionExpiredCaption() {
  1107. return (sessionExpiredNotificationEnabled ? sessionExpiredCaption
  1108. : null);
  1109. }
  1110. /**
  1111. * @return the notification message, or null for no message
  1112. */
  1113. public String getSessionExpiredMessage() {
  1114. return (sessionExpiredNotificationEnabled ? sessionExpiredMessage
  1115. : null);
  1116. }
  1117. /**
  1118. * @return the URL to load on null for restart
  1119. */
  1120. public String getInternalErrorURL() {
  1121. return internalErrorURL;
  1122. }
  1123. /**
  1124. * @return true = notification enabled, false = notification disabled
  1125. */
  1126. public boolean isInternalErrorNotificationEnabled() {
  1127. return internalErrorNotificationEnabled;
  1128. }
  1129. /**
  1130. * @return the notification caption, or null for no caption
  1131. */
  1132. public String getInternalErrorCaption() {
  1133. return (internalErrorNotificationEnabled ? internalErrorCaption
  1134. : null);
  1135. }
  1136. /**
  1137. * @return the notification message, or null for no message
  1138. */
  1139. public String getInternalErrorMessage() {
  1140. return (internalErrorNotificationEnabled ? internalErrorMessage
  1141. : null);
  1142. }
  1143. /**
  1144. * @return the URL to load on null for restart
  1145. */
  1146. public String getOutOfSyncURL() {
  1147. return outOfSyncURL;
  1148. }
  1149. /**
  1150. * @return true = notification enabled, false = notification disabled
  1151. */
  1152. public boolean isOutOfSyncNotificationEnabled() {
  1153. return outOfSyncNotificationEnabled;
  1154. }
  1155. /**
  1156. * @return the notification caption, or null for no caption
  1157. */
  1158. public String getOutOfSyncCaption() {
  1159. return (outOfSyncNotificationEnabled ? outOfSyncCaption : null);
  1160. }
  1161. /**
  1162. * @return the notification message, or null for no message
  1163. */
  1164. public String getOutOfSyncMessage() {
  1165. return (outOfSyncNotificationEnabled ? outOfSyncMessage : null);
  1166. }
  1167. }
  1168. /**
  1169. * Experimental API, not finalized. Contains the system messages used to
  1170. * notify the user about various critical situations that can occur.
  1171. * <p>
  1172. * Customize by overriding the static Application.getSystemMessages() and
  1173. * returning CustomizedSystemMessages. Note that getSystemMessages() is
  1174. * static - changing the system messages will by default change the message
  1175. * for all users of the application.
  1176. * </p>
  1177. * <p>
  1178. * The default behavior is to show a notification, and restart the
  1179. * application the the user clicks the message. <br/> Instead of restarting
  1180. * the application, you can set a specific URL that the user is taken
  1181. * to.<br/> Setting both caption and message to null will restart the
  1182. * application (or go to the specified URL) without displaying a
  1183. * notification. set*NotificationEnabled(false) will achieve the same thing.
  1184. * </p>
  1185. * <p>
  1186. * The situations are:
  1187. * <li>Session expired: the user session has expired, usually due to
  1188. * inactivity.</li>
  1189. * <li>Internal error: unhandled critical server error (e.g out of memory,
  1190. * database crash)
  1191. * <li>Out of sync: the client is not in sync with the server. E.g the user
  1192. * opens two windows showing the same application, and makes changes in one
  1193. * of the windows - the other window is no longer in sync, and (for
  1194. * instance) pressing a button that is no longer present in the UI will
  1195. * cause a out-of-sync -situation. You might want to disable the
  1196. * notification if silently ignoring a action (e.g button press) is not a
  1197. * problem in your application.
  1198. * </p>
  1199. */
  1200. public static class CustomizedSystemMessages extends SystemMessages {
  1201. /**
  1202. * Sets the URL to go to when the session has expired.
  1203. *
  1204. * @param sessionExpiredURL
  1205. * the URL to go to, or null to reload current
  1206. */
  1207. public void setSessionExpiredURL(String sessionExpiredURL) {
  1208. this.sessionExpiredURL = sessionExpiredURL;
  1209. }
  1210. /**
  1211. * Enables or disables the notification. If disabled, the set URL (or
  1212. * current) is loaded directly.
  1213. *
  1214. * @param sessionExpiredNotificationEnabled
  1215. * true = enabled, false = disabled
  1216. */
  1217. public void setSessionExpiredNotificationEnabled(
  1218. boolean sessionExpiredNotificationEnabled) {
  1219. this.sessionExpiredNotificationEnabled = sessionExpiredNotificationEnabled;
  1220. }
  1221. /**
  1222. * Sets the caption of the notification. Set to null for no caption. If
  1223. * both caption and message is null, the notification is disabled;
  1224. *
  1225. * @param sessionExpiredCaption
  1226. * the caption
  1227. */
  1228. public void setSessionExpiredCaption(String sessionExpiredCaption) {
  1229. this.sessionExpiredCaption = sessionExpiredCaption;
  1230. }
  1231. /**
  1232. * Sets the message of the notification. Set to null for no message. If
  1233. * both caption and message is null, the notification is disabled;
  1234. *
  1235. * @param sessionExpiredMessage
  1236. * the message
  1237. */
  1238. public void setSessionExpiredMessage(String sessionExpiredMessage) {
  1239. this.sessionExpiredMessage = sessionExpiredMessage;
  1240. }
  1241. /**
  1242. * Sets the URL to go to when an internal error occurs.
  1243. *
  1244. * @param internalErrorURL
  1245. * the URL to go to, or null to reload current
  1246. */
  1247. public void setInternalErrorURL(String internalErrorURL) {
  1248. this.internalErrorURL = internalErrorURL;
  1249. }
  1250. /**
  1251. * Enables or disables the notification. If disabled, the set URL (or
  1252. * current) is loaded directly.
  1253. *
  1254. * @param internalErrorNotificationEnabled
  1255. * true = enabled, false = disabled
  1256. */
  1257. public void setInternalErrorNotificationEnabled(
  1258. boolean internalErrorNotificationEnabled) {
  1259. this.internalErrorNotificationEnabled = internalErrorNotificationEnabled;
  1260. }
  1261. /**
  1262. * Sets the caption of the notification. Set to null for no caption. If
  1263. * both caption and message is null, the notification is disabled;
  1264. *
  1265. * @param internalErrorCaption
  1266. * the caption
  1267. */
  1268. public void setInternalErrorCaption(String internalErrorCaption) {
  1269. this.internalErrorCaption = internalErrorCaption;
  1270. }
  1271. /**
  1272. * Sets the message of the notification. Set to null for no message. If
  1273. * both caption and message is null, the notification is disabled;
  1274. *
  1275. * @param internalErrorMessage
  1276. * the message
  1277. */
  1278. public void setInternalErrorMessage(String internalErrorMessage) {
  1279. this.internalErrorMessage = internalErrorMessage;
  1280. }
  1281. /**
  1282. * Sets the URL to go to when the client is out-of-sync.
  1283. *
  1284. * @param outOfSyncURL
  1285. * the URL to go to, or null to reload current
  1286. */
  1287. public void setOutOfSyncURL(String outOfSyncURL) {
  1288. this.outOfSyncURL = outOfSyncURL;
  1289. }
  1290. /**
  1291. * Enables or disables the notification. If disabled, the set URL (or
  1292. * current) is loaded directly.
  1293. *
  1294. * @param outOfSyncNotificationEnabled
  1295. * true = enabled, false = disabled
  1296. */
  1297. public void setOutOfSyncNotificationEnabled(
  1298. boolean outOfSyncNotificationEnabled) {
  1299. this.outOfSyncNotificationEnabled = outOfSyncNotificationEnabled;
  1300. }
  1301. /**
  1302. * Sets the caption of the notification. Set to null for no caption. If
  1303. * both caption and message is null, the notification is disabled;
  1304. *
  1305. * @param outOfSyncCaption
  1306. * the caption
  1307. */
  1308. public void setOutOfSyncCaption(String outOfSyncCaption) {
  1309. this.outOfSyncCaption = outOfSyncCaption;
  1310. }
  1311. /**
  1312. * Sets the message of the notification. Set to null for no message. If
  1313. * both caption and message is null, the notification is disabled;
  1314. *
  1315. * @param outOfSyncMessage
  1316. * the message
  1317. */
  1318. public void setOutOfSyncMessage(String outOfSyncMessage) {
  1319. this.outOfSyncMessage = outOfSyncMessage;
  1320. }
  1321. }
  1322. public class ApplicationError implements Terminal.ErrorEvent {
  1323. private Throwable throwable;
  1324. public ApplicationError(Throwable throwable) {
  1325. this.throwable = throwable;
  1326. }
  1327. public Throwable getThrowable() {
  1328. return throwable;
  1329. }
  1330. }
  1331. }