您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ApplicationServlet.java 57KB

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