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

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