選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

ApplicationServlet.java 52KB

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