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.

ApplicationServlet.java 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Intarfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license/license.txt. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see license/licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.terminal.web;
  19. import java.io.BufferedReader;
  20. import java.io.BufferedWriter;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.InputStreamReader;
  26. import java.io.OutputStream;
  27. import java.io.OutputStreamWriter;
  28. import java.io.PrintWriter;
  29. import java.net.MalformedURLException;
  30. import java.net.URL;
  31. import java.util.Arrays;
  32. import java.util.Collection;
  33. import java.util.Date;
  34. import java.util.Enumeration;
  35. import java.util.HashMap;
  36. import java.util.HashSet;
  37. import java.util.Iterator;
  38. import java.util.LinkedList;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.Properties;
  42. import java.util.Set;
  43. import java.util.StringTokenizer;
  44. import java.util.WeakHashMap;
  45. import javax.servlet.ServletContext;
  46. import javax.servlet.ServletException;
  47. import javax.servlet.http.HttpServlet;
  48. import javax.servlet.http.HttpServletRequest;
  49. import javax.servlet.http.HttpServletResponse;
  50. import javax.servlet.http.HttpSession;
  51. import javax.servlet.http.HttpSessionBindingEvent;
  52. import javax.servlet.http.HttpSessionBindingListener;
  53. import org.xml.sax.SAXException;
  54. import com.itmill.toolkit.Application;
  55. import com.itmill.toolkit.Application.WindowAttachEvent;
  56. import com.itmill.toolkit.Application.WindowDetachEvent;
  57. import com.itmill.toolkit.service.FileTypeResolver;
  58. import com.itmill.toolkit.terminal.DownloadStream;
  59. import com.itmill.toolkit.terminal.Paintable;
  60. import com.itmill.toolkit.terminal.ParameterHandler;
  61. import com.itmill.toolkit.terminal.ThemeResource;
  62. import com.itmill.toolkit.terminal.URIHandler;
  63. import com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent;
  64. import com.itmill.toolkit.terminal.web.ThemeSource.ThemeException;
  65. import com.itmill.toolkit.ui.Window;
  66. import com.itmill.toolkit.service.License;
  67. import com.itmill.toolkit.service.License.InvalidLicenseFile;
  68. import com.itmill.toolkit.service.License.LicenseFileHasAlreadyBeenRead;
  69. import com.itmill.toolkit.service.License.LicenseFileHasNotBeenRead;
  70. import com.itmill.toolkit.service.License.LicenseSignatureIsInvalid;
  71. import com.itmill.toolkit.service.License.LicenseViolation;
  72. /**
  73. * This servlet is the core of the MillStone Web Adapter, that adapts the
  74. * MillStone applications to Web standards. The web adapter can be used to
  75. * represent the most MillStone application using Web browsers and corresponding
  76. * technologies.
  77. *
  78. * @author IT Mill Ltd.
  79. * @version @VERSION@
  80. * @since 3.0
  81. */
  82. public class ApplicationServlet extends HttpServlet implements
  83. Application.WindowAttachListener, Application.WindowDetachListener,
  84. Paintable.RepaintRequestListener {
  85. // Version
  86. public static final String VERSION;
  87. public static final int VERSION_MAJOR;
  88. public static final int VERSION_MINOR;
  89. public static final String VERSION_BUILD;
  90. static {
  91. if ("@VERSION@".equals("@"+"VERSION"+"@"))
  92. VERSION = "4.0.0-INTERNAL-NONVERSIONED-DEBUG-BUILD";
  93. else
  94. VERSION = "@VERSION@";
  95. String[] digits = VERSION.split("\\.");
  96. VERSION_MAJOR = Integer.parseInt(digits[0]);
  97. VERSION_MINOR= Integer.parseInt(digits[1]);
  98. VERSION_BUILD = digits[2];
  99. // TODO REMOVE DEBUG
  100. System.out.println(VERSION + "," + VERSION_MAJOR + "," + VERSION_MINOR + "," + VERSION_BUILD);
  101. }
  102. // Configurable parameter names
  103. private static final String PARAMETER_DEBUG = "Debug";
  104. private static final String PARAMETER_DEFAULT_THEME_JAR = "DefaultThemeJar";
  105. private static final String PARAMETER_THEMESOURCE = "ThemeSource";
  106. private static final String PARAMETER_THEME_CACHETIME = "ThemeCacheTime";
  107. private static final String PARAMETER_MAX_TRANSFORMERS = "MaxTransformers";
  108. private static final String PARAMETER_TRANSFORMER_CACHETIME = "TransformerCacheTime";
  109. private static int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
  110. private static int DEFAULT_BUFFER_SIZE = 32 * 1024;
  111. private static int DEFAULT_MAX_TRANSFORMERS = 1;
  112. private static int MAX_BUFFER_SIZE = 64 * 1024;
  113. private static String SESSION_ATTR_VARMAP = "itmill-toolkit-varmap";
  114. static String SESSION_ATTR_CONTEXT = "itmill-toolkit-context";
  115. static String SESSION_ATTR_APPS = "itmill-toolkit-apps";
  116. private static String SESSION_BINDING_LISTENER = "itmill-toolkit-bindinglistener";
  117. private static String DEFAULT_THEME = "default";
  118. private static String RESOURCE_URI = "/RES/";
  119. private static String AJAX_UIDL_URI = "/UIDL/";
  120. private static String THEME_DIRECTORY_PATH = "WEB-INF/lib/themes/";
  121. private static String THEME_LISTING_FILE = THEME_DIRECTORY_PATH
  122. + "themes.txt";
  123. private static String DEFAULT_THEME_JAR_PREFIX = "itmill-toolkit-themes";
  124. private static String DEFAULT_THEME_JAR = "WEB-INF/lib/"
  125. + DEFAULT_THEME_JAR_PREFIX + "-" + VERSION + ".jar";
  126. private static String DEFAULT_THEME_TEMP_FILE_PREFIX = "ITMILL_TMP_";
  127. private static String SERVER_COMMAND_PARAM = "SERVER_COMMANDS";
  128. private static int SERVER_COMMAND_STREAM_MAINTAIN_PERIOD = 15000;
  129. private static int SERVER_COMMAND_HEADER_PADDING = 2000;
  130. // Maximum delay between request for an user to be considered active (in ms)
  131. private static long ACTIVE_USER_REQUEST_INTERVAL = 1000 * 45;
  132. // Private fields
  133. private Class applicationClass;
  134. private Properties applicationProperties;
  135. private UIDLTransformerFactory transformerFactory;
  136. private CollectionThemeSource themeSource;
  137. private String resourcePath = null;
  138. private boolean debugMode = false;
  139. private int maxConcurrentTransformers;
  140. private long transformerCacheTime;
  141. private long themeCacheTime;
  142. private WeakHashMap applicationToDirtyWindowSetMap = new WeakHashMap();
  143. private WeakHashMap applicationToServerCommandStreamLock = new WeakHashMap();
  144. private static WeakHashMap applicationToLastRequestDate = new WeakHashMap();
  145. private List allWindows = new LinkedList();
  146. private WeakHashMap applicationToAjaxAppMgrMap = new WeakHashMap();
  147. private HashMap licenseForApplicationClass = new HashMap();
  148. private static HashSet licensePrintedForApplicationClass = new HashSet();
  149. /**
  150. * Called by the servlet container to indicate to a servlet that the servlet
  151. * is being placed into service.
  152. *
  153. * @param servletConfig
  154. * object containing the servlet's configuration and
  155. * initialization parameters
  156. * @throws ServletException
  157. * if an exception has occurred that interferes with the
  158. * servlet's normal operation.
  159. */
  160. public void init(javax.servlet.ServletConfig servletConfig)
  161. throws javax.servlet.ServletException {
  162. super.init(servletConfig);
  163. // Get the application class name
  164. String applicationClassName = servletConfig
  165. .getInitParameter("application");
  166. if (applicationClassName == null) {
  167. Log.error("Application not specified in servlet parameters");
  168. }
  169. // Store the application parameters into Properties object
  170. this.applicationProperties = new Properties();
  171. for (Enumeration e = servletConfig.getInitParameterNames(); e
  172. .hasMoreElements();) {
  173. String name = (String) e.nextElement();
  174. this.applicationProperties.setProperty(name, servletConfig
  175. .getInitParameter(name));
  176. }
  177. // Override with server.xml parameters
  178. ServletContext context = servletConfig.getServletContext();
  179. for (Enumeration e = context.getInitParameterNames(); e
  180. .hasMoreElements();) {
  181. String name = (String) e.nextElement();
  182. this.applicationProperties.setProperty(name, context
  183. .getInitParameter(name));
  184. }
  185. // Get the debug window parameter
  186. String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG, "false");
  187. // Enable application specific debug
  188. this.debugMode = debug.equals("true");
  189. // Get the maximum number of simultaneous transformers
  190. this.maxConcurrentTransformers = Integer
  191. .parseInt(getApplicationOrSystemProperty(
  192. PARAMETER_MAX_TRANSFORMERS, "-1"));
  193. if (this.maxConcurrentTransformers < 1)
  194. this.maxConcurrentTransformers = DEFAULT_MAX_TRANSFORMERS;
  195. // Get cache time for transformers
  196. this.transformerCacheTime = Integer
  197. .parseInt(getApplicationOrSystemProperty(
  198. PARAMETER_TRANSFORMER_CACHETIME, "-1")) * 1000;
  199. // Get cache time for theme resources
  200. this.themeCacheTime = Integer.parseInt(getApplicationOrSystemProperty(
  201. PARAMETER_THEME_CACHETIME, "-1")) * 1000;
  202. if (this.themeCacheTime < 0) {
  203. this.themeCacheTime = DEFAULT_THEME_CACHETIME;
  204. }
  205. // Add all specified theme sources
  206. this.themeSource = new CollectionThemeSource();
  207. List directorySources = getThemeSources();
  208. for (Iterator i = directorySources.iterator(); i.hasNext();) {
  209. this.themeSource.add((ThemeSource) i.next());
  210. }
  211. // Add the default theme source
  212. String[] defaultThemeFiles = new String[] {
  213. getApplicationOrSystemProperty(PARAMETER_DEFAULT_THEME_JAR,
  214. DEFAULT_THEME_JAR)
  215. };
  216. File f = findDefaultThemeJar(defaultThemeFiles);
  217. try {
  218. // Add themes.jar if exists
  219. if (f != null && f.exists())
  220. this.themeSource.add(new JarThemeSource(f, this, ""));
  221. else {
  222. Log.warn("Default theme JAR not found in: "
  223. + Arrays.asList(defaultThemeFiles));
  224. }
  225. } catch (Exception e) {
  226. throw new ServletException("Failed to load default theme from "
  227. + Arrays.asList(defaultThemeFiles), e);
  228. }
  229. // Check that at least one themesource was loaded
  230. if (this.themeSource.getThemes().size() <= 0) {
  231. throw new ServletException(
  232. "No themes found in specified themesources.");
  233. }
  234. // Initialize the transformer factory, if not initialized
  235. if (this.transformerFactory == null) {
  236. this.transformerFactory = new UIDLTransformerFactory(
  237. this.themeSource, this, this.maxConcurrentTransformers,
  238. this.transformerCacheTime);
  239. }
  240. // Load the application class using the same class loader
  241. // as the servlet itself
  242. ClassLoader loader = this.getClass().getClassLoader();
  243. try {
  244. this.applicationClass = loader.loadClass(applicationClassName);
  245. } catch (ClassNotFoundException e) {
  246. throw new ServletException("Failed to load application class: "
  247. + applicationClassName);
  248. }
  249. }
  250. /**
  251. * Get an application or system property value.
  252. *
  253. * @param parameterName
  254. * Name or the parameter
  255. * @param defaultValue
  256. * Default to be used
  257. * @return String value or default if not found
  258. */
  259. private String getApplicationOrSystemProperty(String parameterName,
  260. String defaultValue) {
  261. // Try application properties
  262. String val = this.applicationProperties.getProperty(parameterName);
  263. if (val != null) {
  264. return val;
  265. }
  266. // Try lowercased application properties for backward compability with
  267. // 3.0.2 and earlier
  268. val = this.applicationProperties.getProperty(parameterName
  269. .toLowerCase());
  270. if (val != null) {
  271. return val;
  272. }
  273. // Try system properties
  274. String pkgName;
  275. Package pkg = this.getClass().getPackage();
  276. if (pkg != null) {
  277. pkgName = pkg.getName();
  278. } else {
  279. String clazzName = this.getClass().getName();
  280. pkgName = new String(clazzName.toCharArray(), 0, clazzName
  281. .lastIndexOf('.'));
  282. }
  283. val = System.getProperty(pkgName + "." + parameterName);
  284. if (val != null) {
  285. return val;
  286. }
  287. // Try lowercased system properties
  288. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  289. if (val != null) {
  290. return val;
  291. }
  292. return defaultValue;
  293. }
  294. /**
  295. * Get ThemeSources from given path. Construct the list of avalable themes
  296. * in path using the following sources: 1. content of THEME_PATH directory
  297. * (if available) 2. The themes listed in THEME_LIST_FILE 3. "themesource"
  298. * application parameter - "org. millstone.webadapter. themesource" system
  299. * property
  300. *
  301. * @param THEME_DIRECTORY_PATH
  302. * @return List
  303. */
  304. private List getThemeSources() throws ServletException {
  305. List returnValue = new LinkedList();
  306. // Check the list file in theme directory
  307. List sourcePaths = new LinkedList();
  308. try {
  309. BufferedReader reader = new BufferedReader(new InputStreamReader(
  310. this.getServletContext().getResourceAsStream(
  311. THEME_LISTING_FILE)));
  312. String line = null;
  313. while ((line = reader.readLine()) != null) {
  314. sourcePaths.add(THEME_DIRECTORY_PATH + line.trim());
  315. }
  316. if (this.isDebugMode()) {
  317. Log.debug("Listed " + sourcePaths.size() + " themes in "
  318. + THEME_LISTING_FILE + ". Loading " + sourcePaths);
  319. }
  320. } catch (Exception ignored) {
  321. // If the file reading fails, just skip to next method
  322. }
  323. // If no file was found or it was empty,
  324. // try to add themes filesystem directory if it is accessible
  325. if (sourcePaths.size() <= 0) {
  326. if (this.isDebugMode()) {
  327. Log.debug("No themes listed in " + THEME_LISTING_FILE
  328. + ". Trying to read the content of directory "
  329. + THEME_DIRECTORY_PATH);
  330. }
  331. try {
  332. String path = this.getServletContext().getRealPath(
  333. THEME_DIRECTORY_PATH);
  334. if (path != null) {
  335. File f = new File(path);
  336. if (f != null && f.exists())
  337. returnValue.add(new DirectoryThemeSource(f, this));
  338. }
  339. } catch (java.io.IOException je) {
  340. Log.info("Theme directory " + THEME_DIRECTORY_PATH
  341. + " not available. Skipped.");
  342. } catch (ThemeException e) {
  343. throw new ServletException("Failed to load themes from "
  344. + THEME_DIRECTORY_PATH, e);
  345. }
  346. }
  347. // Add the theme sources from application properties
  348. String paramValue = getApplicationOrSystemProperty(
  349. PARAMETER_THEMESOURCE, null);
  350. if (paramValue != null) {
  351. StringTokenizer st = new StringTokenizer(paramValue, ";");
  352. while (st.hasMoreTokens()) {
  353. sourcePaths.add(st.nextToken());
  354. }
  355. }
  356. // Construct appropriate theme source instances for each path
  357. for (Iterator i = sourcePaths.iterator(); i.hasNext();) {
  358. String source = (String) i.next();
  359. File sourceFile = new File(source);
  360. try {
  361. // Relative files are treated as streams (to support
  362. // resource inside WAR files)
  363. if (!sourceFile.isAbsolute()) {
  364. returnValue.add(new ServletThemeSource(this
  365. .getServletContext(), this, source));
  366. } else if (sourceFile.isDirectory()) {
  367. // Absolute directories are read from filesystem
  368. returnValue.add(new DirectoryThemeSource(sourceFile, this));
  369. } else {
  370. // Absolute JAR-files are read from filesystem
  371. returnValue.add(new JarThemeSource(sourceFile, this, ""));
  372. }
  373. } catch (Exception e) {
  374. // Any exception breaks the the init
  375. throw new ServletException("Invalid theme source: " + source, e);
  376. }
  377. }
  378. // Return the constructed list of theme sources
  379. return returnValue;
  380. }
  381. /**
  382. * Receives standard HTTP requests from the public service method and
  383. * dispatches them.
  384. *
  385. * @param request
  386. * object that contains the request the client made of the
  387. * servlet
  388. * @param response
  389. * object that contains the response the servlet returns to the
  390. * client
  391. * @throws ServletException
  392. * if an input or output error occurs while the servlet is
  393. * handling the TRACE request
  394. * @throws IOException
  395. * if the request for the TRACE cannot be handled
  396. */
  397. protected void service(HttpServletRequest request,
  398. HttpServletResponse response) throws ServletException, IOException {
  399. // Transformer and output stream for the result
  400. UIDLTransformer transformer = null;
  401. HttpVariableMap variableMap = null;
  402. OutputStream out = response.getOutputStream();
  403. HashSet currentlyDirtyWindowsForThisApplication = new HashSet();
  404. WebApplicationContext appContext = null;
  405. Application application = null;
  406. try {
  407. // If the resource path is unassigned, initialize it
  408. if (resourcePath == null)
  409. resourcePath = request.getContextPath()
  410. + request.getServletPath() + RESOURCE_URI;
  411. // Handle resource requests
  412. if (handleResourceRequest(request, response))
  413. return;
  414. // Handle server commands
  415. if (handleServerCommands(request, response))
  416. return;
  417. // Get the application
  418. application = getApplication(request);
  419. // Create application if it doesn't exist
  420. if (application == null)
  421. application = createApplication(request);
  422. // Is this a download request from application
  423. DownloadStream download = null;
  424. // Invoke context transaction listeners
  425. if (application != null) {
  426. appContext = (WebApplicationContext) application.getContext();
  427. }
  428. if (appContext != null) {
  429. appContext.startTransaction(application, request);
  430. }
  431. // Set the last application request date
  432. applicationToLastRequestDate.put(application, new Date());
  433. // The rest of the process is synchronized with the application
  434. // in order to guarantee that no parallel variable handling is
  435. // made
  436. synchronized (application) {
  437. // Handle UIDL requests?
  438. String resourceId = request.getPathInfo();
  439. if (resourceId != null && resourceId.startsWith(AJAX_UIDL_URI)) {
  440. getApplicationManager(application).handleXmlHttpRequest(
  441. request, response);
  442. return;
  443. }
  444. // Get the variable map
  445. variableMap = getVariableMap(application, request);
  446. if (variableMap == null)
  447. return;
  448. // Change all variables based on request parameters
  449. Map unhandledParameters = variableMap.handleVariables(request,
  450. application);
  451. // Check/handle client side feature checks
  452. WebBrowserProbe
  453. .handleProbeRequest(request, unhandledParameters);
  454. // Handle the URI if the application is still running
  455. if (application.isRunning())
  456. download = handleURI(application, request, response);
  457. // If this is not a download request
  458. if (download == null) {
  459. // Window renders are not cacheable
  460. response.setHeader("Cache-Control", "no-cache");
  461. response.setHeader("Pragma", "no-cache");
  462. response.setDateHeader("Expires", 0);
  463. // Find the window within the application
  464. Window window = null;
  465. if (application.isRunning())
  466. window = getApplicationWindow(request, application);
  467. // Handle the unhandled parameters if the application is
  468. // still running
  469. if (window != null && unhandledParameters != null
  470. && !unhandledParameters.isEmpty()) {
  471. try {
  472. window.handleParameters(unhandledParameters);
  473. } catch (Throwable t) {
  474. application
  475. .terminalError(new ParameterHandlerErrorImpl(
  476. window, t));
  477. }
  478. }
  479. // Remove application if it has stopped
  480. if (!application.isRunning()) {
  481. endApplication(request, response, application);
  482. return;
  483. }
  484. // Return blank page, if no window found
  485. if (window == null) {
  486. response.setContentType("text/html");
  487. BufferedWriter page = new BufferedWriter(
  488. new OutputStreamWriter(out));
  489. page.write("<html><head><script>");
  490. page
  491. .write(ThemeFunctionLibrary
  492. .generateWindowScript(
  493. null,
  494. application,
  495. this,
  496. WebBrowserProbe
  497. .getTerminalType(request
  498. .getSession())));
  499. page.write("</script></head><body>");
  500. page
  501. .write("The requested window has been removed from application.");
  502. page.write("</body></html>");
  503. page.close();
  504. return;
  505. }
  506. // Get the terminal type for the window
  507. WebBrowser terminalType = (WebBrowser) window.getTerminal();
  508. // Set terminal type for the window, if not already set
  509. if (terminalType == null) {
  510. terminalType = WebBrowserProbe.getTerminalType(request
  511. .getSession());
  512. window.setTerminal(terminalType);
  513. }
  514. // Find theme and initialize TransformerType
  515. UIDLTransformerType transformerType = null;
  516. if (window.getTheme() != null) {
  517. Theme activeTheme;
  518. if ((activeTheme = this.themeSource
  519. .getThemeByName(window.getTheme())) != null) {
  520. transformerType = new UIDLTransformerType(
  521. terminalType, activeTheme);
  522. } else {
  523. Log
  524. .info("Theme named '"
  525. + window.getTheme()
  526. + "' not found. Using system default theme.");
  527. }
  528. }
  529. // Use default theme if selected theme was not found.
  530. if (transformerType == null) {
  531. Theme defaultTheme = this.themeSource
  532. .getThemeByName(ApplicationServlet.DEFAULT_THEME);
  533. if (defaultTheme == null) {
  534. throw new ServletException(
  535. "Default theme not found in the specified theme source(s).");
  536. }
  537. transformerType = new UIDLTransformerType(terminalType,
  538. defaultTheme);
  539. }
  540. transformer = this.transformerFactory
  541. .getTransformer(transformerType);
  542. // Set the response type
  543. response.setContentType(terminalType.getContentType());
  544. // Create UIDL writer
  545. WebPaintTarget paintTarget = transformer
  546. .getPaintTarget(variableMap);
  547. // Assure that the correspoding debug window will be
  548. // repainted property
  549. // by clearing it before the actual paint.
  550. DebugWindow debugWindow = (DebugWindow) application
  551. .getWindow(DebugWindow.WINDOW_NAME);
  552. if (debugWindow != null && debugWindow != window) {
  553. debugWindow.setWindowUIDL(window, "Painting...");
  554. }
  555. // Paint window
  556. window.paint(paintTarget);
  557. paintTarget.close();
  558. // For exception handling, memorize the current dirty status
  559. Collection dirtyWindows = (Collection) applicationToDirtyWindowSetMap
  560. .get(application);
  561. if (dirtyWindows == null) {
  562. dirtyWindows = new HashSet();
  563. applicationToDirtyWindowSetMap.put(application,
  564. dirtyWindows);
  565. }
  566. currentlyDirtyWindowsForThisApplication
  567. .addAll(dirtyWindows);
  568. // Window is now painted
  569. windowPainted(application, window);
  570. // Debug
  571. if (debugWindow != null && debugWindow != window) {
  572. debugWindow
  573. .setWindowUIDL(window, paintTarget.getUIDL());
  574. }
  575. // Set the function library state for this thread
  576. ThemeFunctionLibrary.setState(application, window,
  577. transformerType.getWebBrowser(), request
  578. .getSession(), this, transformerType
  579. .getTheme().getName());
  580. }
  581. }
  582. // For normal requests, transform the window
  583. if (download == null) {
  584. // Transform and output the result to browser
  585. // Note that the transform and transfer of the result is
  586. // not synchronized with the variable map. This allows
  587. // parallel transfers and transforms for better performance,
  588. // but requires that all calls from the XSL to java are
  589. // thread-safe
  590. transformer.transform(out);
  591. }
  592. // For download request, transfer the downloaded data
  593. else {
  594. handleDownload(download, request, response);
  595. }
  596. } catch (UIDLTransformerException te) {
  597. try {
  598. // Write the error report to client
  599. response.setContentType("text/html");
  600. BufferedWriter err = new BufferedWriter(new OutputStreamWriter(
  601. out));
  602. err
  603. .write("<html><head><title>Application Internal Error</title></head><body>");
  604. err.write("<h1>" + te.getMessage() + "</h1>");
  605. err.write(te.getHTMLDescription());
  606. err.write("</body></html>");
  607. err.close();
  608. } catch (Throwable t) {
  609. Log.except("Failed to write error page: " + t
  610. + ". Original exception was: ", te);
  611. }
  612. // Add previously dirty windows to dirtyWindowList in order
  613. // to make sure that eventually they are repainted
  614. Application currentApplication = getApplication(request);
  615. for (Iterator iter = currentlyDirtyWindowsForThisApplication
  616. .iterator(); iter.hasNext();) {
  617. Window dirtyWindow = (Window) iter.next();
  618. addDirtyWindow(currentApplication, dirtyWindow);
  619. }
  620. } catch (Throwable e) {
  621. // Re-throw other exceptions
  622. throw new ServletException(e);
  623. } finally {
  624. // Release transformer
  625. if (transformer != null)
  626. transformerFactory.releaseTransformer(transformer);
  627. // Notify transaction end
  628. if (appContext != null && application != null) {
  629. appContext.endTransaction(application, request);
  630. }
  631. // Clean the function library state for this thread
  632. // for security reasons
  633. ThemeFunctionLibrary.cleanState();
  634. }
  635. }
  636. /**
  637. * Handle the requested URI. An application can add handlers to do special
  638. * processing, when a certain URI is requested. The handlers are invoked
  639. * before any windows URIs are processed and if a DownloadStream is returned
  640. * it is sent to the client.
  641. *
  642. * @see com.itmill.toolkit.terminal.URIHandler
  643. *
  644. * @param application
  645. * Application owning the URI
  646. * @param request
  647. * HTTP request instance
  648. * @param response
  649. * HTTP response to write to.
  650. * @return boolean True if the request was handled and further processing
  651. * should be suppressed, false otherwise.
  652. */
  653. private DownloadStream handleURI(Application application,
  654. HttpServletRequest request, HttpServletResponse response) {
  655. String uri = request.getPathInfo();
  656. // If no URI is available
  657. if (uri == null || uri.length() == 0 || uri.equals("/"))
  658. return null;
  659. // Remove the leading /
  660. while (uri.startsWith("/") && uri.length() > 0)
  661. uri = uri.substring(1);
  662. // Handle the uri
  663. DownloadStream stream = null;
  664. try {
  665. stream = application.handleURI(application.getURL(), uri);
  666. } catch (Throwable t) {
  667. application.terminalError(new URIHandlerErrorImpl(application, t));
  668. }
  669. return stream;
  670. }
  671. /**
  672. * Handle the requested URI. An application can add handlers to do special
  673. * processing, when a certain URI is requested. The handlers are invoked
  674. * before any windows URIs are processed and if a DownloadStream is returned
  675. * it is sent to the client.
  676. *
  677. * @see com.itmill.toolkit.terminal.URIHandler
  678. *
  679. * @param application
  680. * Application owning the URI
  681. * @param request
  682. * HTTP request instance
  683. * @param response
  684. * HTTP response to write to.
  685. * @return boolean True if the request was handled and further processing
  686. * should be suppressed, false otherwise.
  687. */
  688. private void handleDownload(DownloadStream stream,
  689. HttpServletRequest request, HttpServletResponse response) {
  690. // Download from given stream
  691. InputStream data = stream.getStream();
  692. if (data != null) {
  693. // Set content type
  694. response.setContentType(stream.getContentType());
  695. // Set cache headers
  696. long cacheTime = stream.getCacheTime();
  697. if (cacheTime <= 0) {
  698. response.setHeader("Cache-Control", "no-cache");
  699. response.setHeader("Pragma", "no-cache");
  700. response.setDateHeader("Expires", 0);
  701. } else {
  702. response.setHeader("Cache-Control", "max-age=" + cacheTime
  703. / 1000);
  704. response.setDateHeader("Expires", System.currentTimeMillis()
  705. + cacheTime);
  706. response.setHeader("Pragma", "cache"); // Required to apply
  707. // caching in some
  708. // Tomcats
  709. }
  710. // Copy download stream parameters directly
  711. // to HTTP headers.
  712. Iterator i = stream.getParameterNames();
  713. if (i != null) {
  714. while (i.hasNext()) {
  715. String param = (String) i.next();
  716. response.setHeader((String) param, stream
  717. .getParameter(param));
  718. }
  719. }
  720. int bufferSize = stream.getBufferSize();
  721. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE)
  722. bufferSize = DEFAULT_BUFFER_SIZE;
  723. byte[] buffer = new byte[bufferSize];
  724. int bytesRead = 0;
  725. try {
  726. OutputStream out = response.getOutputStream();
  727. while ((bytesRead = data.read(buffer)) > 0) {
  728. out.write(buffer, 0, bytesRead);
  729. out.flush();
  730. }
  731. out.close();
  732. } catch (IOException ignored) {
  733. }
  734. }
  735. }
  736. /**
  737. * Look for default theme JAR file.
  738. *
  739. * @return Jar file or null if not found.
  740. */
  741. private File findDefaultThemeJar(String[] fileList) {
  742. // Try to find the default theme JAR file based on the given path
  743. for (int i = 0; i < fileList.length; i++) {
  744. String path = this.getServletContext().getRealPath(fileList[i]);
  745. File file = null;
  746. if (path != null && (file = new File(path)).exists()) {
  747. return file;
  748. }
  749. }
  750. // If we do not have access to individual files, create a temporary
  751. // file from named resource.
  752. for (int i = 0; i < fileList.length; i++) {
  753. InputStream defaultTheme = this.getServletContext()
  754. .getResourceAsStream(fileList[i]);
  755. // Read the content to temporary file and return it
  756. if (defaultTheme != null) {
  757. return createTemporaryFile(defaultTheme, ".jar");
  758. }
  759. }
  760. // Try to find the default theme JAR file based on file naming scheme
  761. // NOTE: This is for backward compability with 3.0.2 and earlier.
  762. String path = this.getServletContext().getRealPath("/WEB-INF/lib");
  763. if (path != null) {
  764. File lib = new File(path);
  765. String[] files = lib.list();
  766. if (files != null) {
  767. for (int i = 0; i < files.length; i++) {
  768. if (files[i].toLowerCase().endsWith(".jar")
  769. && files[i].startsWith(DEFAULT_THEME_JAR_PREFIX)) {
  770. return new File(lib, files[i]);
  771. }
  772. }
  773. }
  774. }
  775. // If no file was found return null
  776. return null;
  777. }
  778. /**
  779. * Create a temporary file for given stream.
  780. *
  781. * @param stream
  782. * Stream to be stored into temporary file.
  783. * @param extension
  784. * File type extension
  785. * @return File
  786. */
  787. private File createTemporaryFile(InputStream stream, String extension) {
  788. File tmpFile;
  789. try {
  790. tmpFile = File.createTempFile(DEFAULT_THEME_TEMP_FILE_PREFIX,
  791. extension);
  792. FileOutputStream out = new FileOutputStream(tmpFile);
  793. byte[] buf = new byte[1024];
  794. int bytes = 0;
  795. while ((bytes = stream.read(buf)) > 0) {
  796. out.write(buf, 0, bytes);
  797. }
  798. out.close();
  799. } catch (IOException e) {
  800. System.err
  801. .println("Failed to create temporary file for default theme: "
  802. + e);
  803. tmpFile = null;
  804. }
  805. return tmpFile;
  806. }
  807. /**
  808. * Handle theme resource file requests. Resources supplied with the themes
  809. * are provided by the WebAdapterServlet.
  810. *
  811. * @param request
  812. * HTTP request
  813. * @param response
  814. * HTTP response
  815. * @return boolean True if the request was handled and further processing
  816. * should be suppressed, false otherwise.
  817. */
  818. private boolean handleResourceRequest(HttpServletRequest request,
  819. HttpServletResponse response) throws ServletException {
  820. String resourceId = request.getPathInfo();
  821. // Check if this really is a resource request
  822. if (resourceId == null || !resourceId.startsWith(RESOURCE_URI))
  823. return false;
  824. // Check the resource type
  825. resourceId = resourceId.substring(RESOURCE_URI.length());
  826. InputStream data = null;
  827. // Get theme resources
  828. try {
  829. data = themeSource.getResource(resourceId);
  830. } catch (ThemeSource.ThemeException e) {
  831. Log.info(e.getMessage());
  832. data = null;
  833. }
  834. // Write the response
  835. try {
  836. if (data != null) {
  837. response.setContentType(FileTypeResolver
  838. .getMIMEType(resourceId));
  839. // Use default cache time for theme resources
  840. if (this.themeCacheTime > 0) {
  841. response.setHeader("Cache-Control", "max-age="
  842. + this.themeCacheTime / 1000);
  843. response.setDateHeader("Expires", System
  844. .currentTimeMillis()
  845. + this.themeCacheTime);
  846. response.setHeader("Pragma", "cache"); // Required to apply
  847. // caching in some
  848. // Tomcats
  849. }
  850. // Write the data to client
  851. byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  852. int bytesRead = 0;
  853. OutputStream out = response.getOutputStream();
  854. while ((bytesRead = data.read(buffer)) > 0) {
  855. out.write(buffer, 0, bytesRead);
  856. }
  857. out.close();
  858. data.close();
  859. } else {
  860. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  861. }
  862. } catch (java.io.IOException e) {
  863. Log.info("Resource transfer failed: " + request.getRequestURI()
  864. + ". (" + e.getMessage() + ")");
  865. }
  866. return true;
  867. }
  868. /** Get the variable map for the session */
  869. private static synchronized HttpVariableMap getVariableMap(
  870. Application application, HttpServletRequest request) {
  871. HttpSession session = request.getSession();
  872. // Get the application to variablemap map
  873. Map varMapMap = (Map) session.getAttribute(SESSION_ATTR_VARMAP);
  874. if (varMapMap == null) {
  875. varMapMap = new WeakHashMap();
  876. session.setAttribute(SESSION_ATTR_VARMAP, varMapMap);
  877. }
  878. // Create a variable map, if it does not exists.
  879. HttpVariableMap variableMap = (HttpVariableMap) varMapMap
  880. .get(application);
  881. if (variableMap == null) {
  882. variableMap = new HttpVariableMap();
  883. varMapMap.put(application, variableMap);
  884. }
  885. return variableMap;
  886. }
  887. /** Get the current application URL from request */
  888. private URL getApplicationUrl(HttpServletRequest request)
  889. throws MalformedURLException {
  890. URL applicationUrl;
  891. try {
  892. URL reqURL = new URL((request.isSecure() ? "https://" : "http://")
  893. + request.getServerName() + ":" + request.getServerPort()
  894. + request.getRequestURI());
  895. String servletPath = request.getContextPath()
  896. + request.getServletPath();
  897. if (servletPath.length() == 0
  898. || servletPath.charAt(servletPath.length() - 1) != '/')
  899. servletPath = servletPath + "/";
  900. applicationUrl = new URL(reqURL, servletPath);
  901. } catch (MalformedURLException e) {
  902. Log.error("Error constructing application url "
  903. + request.getRequestURI() + " (" + e + ")");
  904. throw e;
  905. }
  906. return applicationUrl;
  907. }
  908. /**
  909. * Get the existing application for given request. Looks for application
  910. * instance for given request based on the requested URL.
  911. *
  912. * @param request
  913. * HTTP request
  914. * @return Application instance, or null if the URL does not map to valid
  915. * application.
  916. */
  917. private Application getApplication(HttpServletRequest request)
  918. throws MalformedURLException {
  919. // Ensure that the session is still valid
  920. HttpSession session = request.getSession(false);
  921. if (session == null)
  922. return null;
  923. // Get application list for the session.
  924. LinkedList applications = (LinkedList) session
  925. .getAttribute(SESSION_ATTR_APPS);
  926. if (applications == null)
  927. return null;
  928. // Search for the application (using the application URI) from the list
  929. Application application = null;
  930. for (Iterator i = applications.iterator(); i.hasNext()
  931. && application == null;) {
  932. Application a = (Application) i.next();
  933. String aPath = a.getURL().getPath();
  934. String servletPath = request.getContextPath()
  935. + request.getServletPath();
  936. if (servletPath.length() < aPath.length())
  937. servletPath += "/";
  938. if (servletPath.equals(aPath))
  939. application = a;
  940. }
  941. // Remove stopped applications from the list
  942. if (application != null && !application.isRunning()) {
  943. applications.remove(application);
  944. application = null;
  945. }
  946. return application;
  947. }
  948. /**
  949. * Create a new application.
  950. *
  951. * @return New application instance
  952. * @throws SAXException
  953. * @throws LicenseViolation
  954. * @throws InvalidLicenseFile
  955. * @throws LicenseSignatureIsInvalid
  956. * @throws LicenseFileHasNotBeenRead
  957. */
  958. private Application createApplication(HttpServletRequest request)
  959. throws MalformedURLException, InstantiationException,
  960. IllegalAccessException, LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid, InvalidLicenseFile, LicenseViolation, SAXException {
  961. Application application = null;
  962. // Get the application url
  963. URL applicationUrl = getApplicationUrl(request);
  964. // Get application list.
  965. HttpSession session = request.getSession();
  966. if (session == null)
  967. return null;
  968. LinkedList applications = (LinkedList) session
  969. .getAttribute(SESSION_ATTR_APPS);
  970. if (applications == null) {
  971. applications = new LinkedList();
  972. session.setAttribute(SESSION_ATTR_APPS, applications);
  973. HttpSessionBindingListener sessionBindingListener = new SessionBindingListener(
  974. applications);
  975. session.setAttribute(SESSION_BINDING_LISTENER,
  976. sessionBindingListener);
  977. }
  978. // Create new application and start it
  979. try {
  980. application = (Application) this.applicationClass.newInstance();
  981. applications.add(application);
  982. // Listen to window add/removes (for web mode)
  983. application.addListener((Application.WindowAttachListener) this);
  984. application.addListener((Application.WindowDetachListener) this);
  985. // Set localte
  986. application.setLocale(request.getLocale());
  987. // Get application context for this session
  988. WebApplicationContext context = (WebApplicationContext) session
  989. .getAttribute(SESSION_ATTR_CONTEXT);
  990. if (context == null) {
  991. context = new WebApplicationContext(session);
  992. session.setAttribute(SESSION_ATTR_CONTEXT, context);
  993. }
  994. // Start application and check license
  995. initializeLicense(application);
  996. application.start(applicationUrl, this.applicationProperties,
  997. context);
  998. checkLicense(application);
  999. } catch (IllegalAccessException e) {
  1000. Log.error("Illegal access to application class "
  1001. + this.applicationClass.getName());
  1002. throw e;
  1003. } catch (InstantiationException e) {
  1004. Log.error("Failed to instantiate application class: "
  1005. + this.applicationClass.getName());
  1006. throw e;
  1007. }
  1008. return application;
  1009. }
  1010. private void initializeLicense(Application application) {
  1011. License license = (License) licenseForApplicationClass.get(application
  1012. .getClass());
  1013. if (license == null) {
  1014. license = new License();
  1015. licenseForApplicationClass.put(application.getClass(), license);
  1016. }
  1017. application.setToolkitLicense(license);
  1018. }
  1019. private void checkLicense(Application application) throws LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid, InvalidLicenseFile, LicenseViolation, SAXException{
  1020. License license = application.getToolkitLicense();
  1021. if (!license.hasBeenRead()) {
  1022. InputStream lis;
  1023. try {
  1024. lis = getServletContext().getResource(
  1025. "/WEB-INF/itmill-toolkit-license.xml").openStream();
  1026. license.readLicenseFile(lis);
  1027. } catch (MalformedURLException e) {
  1028. // This should not happen
  1029. throw new RuntimeException(e);
  1030. } catch (IOException e) {
  1031. // This should not happen
  1032. throw new RuntimeException(e);
  1033. } catch (LicenseFileHasAlreadyBeenRead e) {
  1034. // This should not happen
  1035. throw new RuntimeException(e);
  1036. }
  1037. }
  1038. // For each application class, print license description - once
  1039. if (!licensePrintedForApplicationClass.contains(applicationClass)) {
  1040. licensePrintedForApplicationClass.add(applicationClass);
  1041. if (license.shouldLimitsBePrintedOnInit())
  1042. System.out.print(license.getDescription());
  1043. }
  1044. // Check license validity
  1045. try {
  1046. license.check(applicationClass, getNumberOfActiveUsers()+1, VERSION_MAJOR,
  1047. VERSION_MINOR, "IT Mill Toolkit", null);
  1048. } catch (LicenseFileHasNotBeenRead e) {
  1049. application.close();
  1050. throw e;
  1051. } catch (LicenseSignatureIsInvalid e) {
  1052. application.close();
  1053. throw e;
  1054. } catch (InvalidLicenseFile e) {
  1055. application.close();
  1056. throw e;
  1057. } catch (LicenseViolation e) {
  1058. application.close();
  1059. throw e;
  1060. }
  1061. }
  1062. /** Get the number of active application-user pairs.
  1063. *
  1064. * This returns total number of all applications in the server that are considered to be active. For
  1065. * an application to be active, it must have been accessed less than ACTIVE_USER_REQUEST_INTERVAL ms.
  1066. *
  1067. * @return Number of active application instances in the server.
  1068. */
  1069. private int getNumberOfActiveUsers() {
  1070. Set apps = applicationToLastRequestDate.keySet();
  1071. int active = 0;
  1072. long now = System.currentTimeMillis();
  1073. for (Iterator i=apps.iterator(); i.hasNext();) {
  1074. Date lastReq = (Date) applicationToLastRequestDate.get(i.next());
  1075. if (now - lastReq.getTime() < ACTIVE_USER_REQUEST_INTERVAL)
  1076. active++;
  1077. }
  1078. return active;
  1079. }
  1080. /** End application */
  1081. private void endApplication(HttpServletRequest request,
  1082. HttpServletResponse response, Application application)
  1083. throws IOException {
  1084. String logoutUrl = application.getLogoutURL();
  1085. if (logoutUrl == null)
  1086. logoutUrl = application.getURL().toString();
  1087. HttpSession session = request.getSession();
  1088. if (session != null) {
  1089. LinkedList applications = (LinkedList) session
  1090. .getAttribute(SESSION_ATTR_APPS);
  1091. if (applications != null)
  1092. applications.remove(application);
  1093. }
  1094. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  1095. }
  1096. /**
  1097. * Get the existing application or create a new one. Get a window within an
  1098. * application based on the requested URI.
  1099. *
  1100. * @param request
  1101. * HTTP Request.
  1102. * @param application
  1103. * Application to query for window.
  1104. * @return Window mathing the given URI or null if not found.
  1105. */
  1106. private Window getApplicationWindow(HttpServletRequest request,
  1107. Application application) throws ServletException {
  1108. Window window = null;
  1109. // Find the window where the request is handled
  1110. String path = request.getPathInfo();
  1111. // Main window as the URI is empty
  1112. if (path == null || path.length() == 0 || path.equals("/"))
  1113. window = application.getMainWindow();
  1114. // Try to search by window name
  1115. else {
  1116. String windowName = null;
  1117. if (path.charAt(0) == '/')
  1118. path = path.substring(1);
  1119. int index = path.indexOf('/');
  1120. if (index < 0) {
  1121. windowName = path;
  1122. path = "";
  1123. } else {
  1124. windowName = path.substring(0, index);
  1125. path = path.substring(index + 1);
  1126. }
  1127. window = application.getWindow(windowName);
  1128. if (window == null) {
  1129. // If the window has existed, and is now removed
  1130. // send a blank page
  1131. if (allWindows.contains(windowName))
  1132. return null;
  1133. // By default, we use main window
  1134. window = application.getMainWindow();
  1135. } else if (!window.isVisible()) {
  1136. // Implicitly painting without actually invoking paint()
  1137. window.requestRepaintRequests();
  1138. // If the window is invisible send a blank page
  1139. return null;
  1140. }
  1141. }
  1142. // Create and open new debug window for application if requested
  1143. if (this.debugMode
  1144. && application.getWindow(DebugWindow.WINDOW_NAME) == null)
  1145. try {
  1146. DebugWindow debugWindow = new DebugWindow(application, request
  1147. .getSession(false), this);
  1148. debugWindow.setWidth(370);
  1149. debugWindow.setHeight(480);
  1150. application.addWindow(debugWindow);
  1151. } catch (Exception e) {
  1152. throw new ServletException(
  1153. "Failed to create debug window for application", e);
  1154. }
  1155. return window;
  1156. }
  1157. /**
  1158. * Get relative location of a theme resource.
  1159. *
  1160. * @param theme
  1161. * Theme name
  1162. * @param resource
  1163. * Theme resource
  1164. * @return External URI specifying the resource
  1165. */
  1166. public String getResourceLocation(String theme, ThemeResource resource) {
  1167. if (resourcePath == null)
  1168. return resource.getResourceId();
  1169. return resourcePath + theme + "/" + resource.getResourceId();
  1170. }
  1171. /**
  1172. * Check if web adapter is in debug mode. Extra output is generated to log
  1173. * when debug mode is enabled.
  1174. *
  1175. * @return Debug mode
  1176. */
  1177. public boolean isDebugMode() {
  1178. return debugMode;
  1179. }
  1180. /**
  1181. * Returns the theme source.
  1182. *
  1183. * @return ThemeSource
  1184. */
  1185. public ThemeSource getThemeSource() {
  1186. return themeSource;
  1187. }
  1188. protected void addDirtyWindow(Application application, Window window) {
  1189. synchronized (applicationToDirtyWindowSetMap) {
  1190. HashSet dirtyWindows = (HashSet) applicationToDirtyWindowSetMap
  1191. .get(application);
  1192. if (dirtyWindows == null) {
  1193. dirtyWindows = new HashSet();
  1194. applicationToDirtyWindowSetMap.put(application, dirtyWindows);
  1195. }
  1196. dirtyWindows.add(window);
  1197. }
  1198. }
  1199. protected void removeDirtyWindow(Application application, Window window) {
  1200. synchronized (applicationToDirtyWindowSetMap) {
  1201. HashSet dirtyWindows = (HashSet) applicationToDirtyWindowSetMap
  1202. .get(application);
  1203. if (dirtyWindows != null)
  1204. dirtyWindows.remove(window);
  1205. }
  1206. }
  1207. /**
  1208. * @see com.itmill.toolkit.Application.WindowAttachListener#windowAttached(Application.WindowAttachEvent)
  1209. */
  1210. public void windowAttached(WindowAttachEvent event) {
  1211. Window win = event.getWindow();
  1212. win.addListener((Paintable.RepaintRequestListener) this);
  1213. // Add to window names
  1214. allWindows.add(win.getName());
  1215. // Add window to dirty window references if it is visible
  1216. // Or request the window to pass on the repaint requests
  1217. if (win.isVisible())
  1218. addDirtyWindow(event.getApplication(), win);
  1219. else
  1220. win.requestRepaintRequests();
  1221. }
  1222. /**
  1223. * @see com.itmill.toolkit.Application.WindowDetachListener#windowDetached(Application.WindowDetachEvent)
  1224. */
  1225. public void windowDetached(WindowDetachEvent event) {
  1226. event.getWindow().removeListener(
  1227. (Paintable.RepaintRequestListener) this);
  1228. // Add dirty window reference for closing the window
  1229. addDirtyWindow(event.getApplication(), event.getWindow());
  1230. }
  1231. /**
  1232. * @see com.itmill.toolkit.terminal.Paintable.RepaintRequestListener#repaintRequested(Paintable.RepaintRequestEvent)
  1233. */
  1234. public void repaintRequested(RepaintRequestEvent event) {
  1235. Paintable p = event.getPaintable();
  1236. Application app = null;
  1237. if (p instanceof Window)
  1238. app = ((Window) p).getApplication();
  1239. if (app != null)
  1240. addDirtyWindow(app, ((Window) p));
  1241. Object lock = applicationToServerCommandStreamLock.get(app);
  1242. if (lock != null)
  1243. synchronized (lock) {
  1244. lock.notifyAll();
  1245. }
  1246. }
  1247. /** Get the list of dirty windows in application */
  1248. protected Set getDirtyWindows(Application app) {
  1249. HashSet dirtyWindows;
  1250. synchronized (applicationToDirtyWindowSetMap) {
  1251. dirtyWindows = (HashSet) applicationToDirtyWindowSetMap.get(app);
  1252. }
  1253. return dirtyWindows;
  1254. }
  1255. /** Remove a window from the list of dirty windows */
  1256. private void windowPainted(Application app, Window window) {
  1257. removeDirtyWindow(app, window);
  1258. }
  1259. /**
  1260. * Generate server commands stream. If the server commands are not
  1261. * requested, return false
  1262. */
  1263. private boolean handleServerCommands(HttpServletRequest request,
  1264. HttpServletResponse response) {
  1265. // Server commands are allways requested with certain parameter
  1266. if (request.getParameter(SERVER_COMMAND_PARAM) == null)
  1267. return false;
  1268. // Get the application
  1269. Application application;
  1270. try {
  1271. application = getApplication(request);
  1272. } catch (MalformedURLException e) {
  1273. return false;
  1274. }
  1275. if (application == null)
  1276. return false;
  1277. // Create continuous server commands stream
  1278. try {
  1279. // Writer for writing the stream
  1280. PrintWriter w = new PrintWriter(response.getOutputStream());
  1281. // Print necessary http page headers and padding
  1282. w.println("<html><head></head><body>");
  1283. for (int i = 0; i < SERVER_COMMAND_HEADER_PADDING; i++)
  1284. w.print(' ');
  1285. // Clock for synchronizing the stream
  1286. Object lock = new Object();
  1287. synchronized (applicationToServerCommandStreamLock) {
  1288. Object oldlock = applicationToServerCommandStreamLock
  1289. .get(application);
  1290. if (oldlock != null)
  1291. synchronized (oldlock) {
  1292. oldlock.notifyAll();
  1293. }
  1294. applicationToServerCommandStreamLock.put(application, lock);
  1295. }
  1296. while (applicationToServerCommandStreamLock.get(application) == lock
  1297. && application.isRunning()) {
  1298. synchronized (application) {
  1299. // Session expiration
  1300. Date lastRequest = (Date) applicationToLastRequestDate
  1301. .get(application);
  1302. if (lastRequest != null
  1303. && lastRequest.getTime()
  1304. + request.getSession()
  1305. .getMaxInactiveInterval() * 1000 < System
  1306. .currentTimeMillis()) {
  1307. // Session expired, close application
  1308. application.close();
  1309. } else {
  1310. // Application still alive - keep updating windows
  1311. Set dws = getDirtyWindows(application);
  1312. if (dws != null && !dws.isEmpty()) {
  1313. // For one of the dirty windows (in each
  1314. // application)
  1315. // request redraw
  1316. Window win = (Window) dws.iterator().next();
  1317. w
  1318. .println("<script>\n"
  1319. + ThemeFunctionLibrary
  1320. .getWindowRefreshScript(
  1321. application,
  1322. win,
  1323. WebBrowserProbe
  1324. .getTerminalType(request
  1325. .getSession()))
  1326. + "</script>");
  1327. removeDirtyWindow(application, win);
  1328. // Windows that are closed immediately are "painted"
  1329. // now
  1330. if (win.getApplication() == null
  1331. || !win.isVisible())
  1332. win.requestRepaintRequests();
  1333. }
  1334. }
  1335. }
  1336. // Send the generated commands and newline immediately to
  1337. // browser
  1338. w.println(" ");
  1339. w.flush();
  1340. response.flushBuffer();
  1341. synchronized (lock) {
  1342. try {
  1343. lock.wait(SERVER_COMMAND_STREAM_MAINTAIN_PERIOD);
  1344. } catch (InterruptedException ignored) {
  1345. }
  1346. }
  1347. }
  1348. } catch (IOException ignore) {
  1349. // In case of an Exceptions the server command stream is
  1350. // terminated
  1351. synchronized (applicationToServerCommandStreamLock) {
  1352. if (applicationToServerCommandStreamLock.get(application) == application)
  1353. applicationToServerCommandStreamLock.remove(application);
  1354. }
  1355. }
  1356. return true;
  1357. }
  1358. private class SessionBindingListener implements HttpSessionBindingListener {
  1359. private LinkedList applications;
  1360. protected SessionBindingListener(LinkedList applications) {
  1361. this.applications = applications;
  1362. }
  1363. /**
  1364. * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
  1365. */
  1366. public void valueBound(HttpSessionBindingEvent arg0) {
  1367. // We are not interested in bindings
  1368. }
  1369. /**
  1370. * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
  1371. */
  1372. public void valueUnbound(HttpSessionBindingEvent event) {
  1373. // If the binding listener is unbound from the session, the
  1374. // session must be closing
  1375. if (event.getName().equals(SESSION_BINDING_LISTENER)) {
  1376. // Close all applications
  1377. Object[] apps = applications.toArray();
  1378. for (int i = 0; i < apps.length; i++) {
  1379. if (apps[i] != null) {
  1380. // Close app
  1381. ((Application) apps[i]).close();
  1382. // Stop application server commands stream
  1383. Object lock = applicationToServerCommandStreamLock
  1384. .get(apps[i]);
  1385. if (lock != null)
  1386. synchronized (lock) {
  1387. lock.notifyAll();
  1388. }
  1389. applicationToServerCommandStreamLock.remove(apps[i]);
  1390. // Remove application from applications list
  1391. applications.remove(apps[i]);
  1392. }
  1393. }
  1394. }
  1395. }
  1396. }
  1397. /** Implementation of ParameterHandler.ErrorEvent interface. */
  1398. public class ParameterHandlerErrorImpl implements
  1399. ParameterHandler.ErrorEvent {
  1400. private ParameterHandler owner;
  1401. private Throwable throwable;
  1402. private ParameterHandlerErrorImpl(ParameterHandler owner,
  1403. Throwable throwable) {
  1404. this.owner = owner;
  1405. this.throwable = throwable;
  1406. }
  1407. /**
  1408. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1409. */
  1410. public Throwable getThrowable() {
  1411. return this.throwable;
  1412. }
  1413. /**
  1414. * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
  1415. */
  1416. public ParameterHandler getParameterHandler() {
  1417. return this.owner;
  1418. }
  1419. }
  1420. /** Implementation of URIHandler.ErrorEvent interface. */
  1421. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
  1422. private URIHandler owner;
  1423. private Throwable throwable;
  1424. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  1425. this.owner = owner;
  1426. this.throwable = throwable;
  1427. }
  1428. /**
  1429. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1430. */
  1431. public Throwable getThrowable() {
  1432. return this.throwable;
  1433. }
  1434. /**
  1435. * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
  1436. */
  1437. public URIHandler getURIHandler() {
  1438. return this.owner;
  1439. }
  1440. }
  1441. /**
  1442. * Get AJAX application manager for an application.
  1443. *
  1444. * If this application has not been running in ajax mode before, new manager
  1445. * is created and web adapter stops listening to changes.
  1446. *
  1447. * @param application
  1448. * @return AJAX Application Manager
  1449. */
  1450. private AjaxApplicationManager getApplicationManager(Application application) {
  1451. AjaxApplicationManager mgr = (AjaxApplicationManager) applicationToAjaxAppMgrMap
  1452. .get(application);
  1453. // This application is going from Web to AJAX mode, create new manager
  1454. if (mgr == null) {
  1455. // Create new manager
  1456. mgr = new AjaxApplicationManager(application);
  1457. applicationToAjaxAppMgrMap.put(application, mgr);
  1458. // Stop sending changes to this servlet because manager will take
  1459. // control
  1460. application.removeListener((Application.WindowAttachListener) this);
  1461. application.removeListener((Application.WindowDetachListener) this);
  1462. // Manager takes control over the application
  1463. mgr.takeControl();
  1464. }
  1465. return mgr;
  1466. }
  1467. }