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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Interfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license.pdf. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.terminal.gwt.server;
  19. import java.io.BufferedWriter;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.io.OutputStreamWriter;
  24. import java.lang.reflect.Constructor;
  25. import java.net.MalformedURLException;
  26. import java.net.URL;
  27. import java.util.Collection;
  28. import java.util.Date;
  29. import java.util.Enumeration;
  30. import java.util.Iterator;
  31. import java.util.Map;
  32. import java.util.Properties;
  33. import java.util.Set;
  34. import java.util.WeakHashMap;
  35. import javax.servlet.ServletContext;
  36. import javax.servlet.ServletException;
  37. import javax.servlet.http.HttpServlet;
  38. import javax.servlet.http.HttpServletRequest;
  39. import javax.servlet.http.HttpServletResponse;
  40. import javax.servlet.http.HttpSession;
  41. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  42. import org.xml.sax.SAXException;
  43. import com.itmill.toolkit.Application;
  44. import com.itmill.toolkit.service.FileTypeResolver;
  45. import com.itmill.toolkit.service.License;
  46. import com.itmill.toolkit.service.License.InvalidLicenseFile;
  47. import com.itmill.toolkit.service.License.LicenseFileHasNotBeenRead;
  48. import com.itmill.toolkit.service.License.LicenseSignatureIsInvalid;
  49. import com.itmill.toolkit.service.License.LicenseViolation;
  50. import com.itmill.toolkit.terminal.DownloadStream;
  51. import com.itmill.toolkit.terminal.ParameterHandler;
  52. import com.itmill.toolkit.terminal.ThemeResource;
  53. import com.itmill.toolkit.terminal.URIHandler;
  54. import com.itmill.toolkit.ui.Window;
  55. /**
  56. * This servlet connects IT Mill Toolkit Application to Web.
  57. *
  58. * @author IT Mill Ltd.
  59. * @version
  60. * @VERSION@
  61. * @since 5.0
  62. */
  63. public class ApplicationServlet extends HttpServlet {
  64. private static final long serialVersionUID = -4937882979845826574L;
  65. /**
  66. * Version number of this release. For example "4.0.0".
  67. */
  68. public static final String VERSION;
  69. /**
  70. * Major version number. For example 4 in 4.1.0.
  71. */
  72. public static final int VERSION_MAJOR;
  73. /**
  74. * Minor version number. For example 1 in 4.1.0.
  75. */
  76. public static final int VERSION_MINOR;
  77. /**
  78. * Builds number. For example 0-beta1 in 4.0.0-beta1.
  79. */
  80. public static final String VERSION_BUILD;
  81. /* Initialize version numbers from string replaced by build-script. */
  82. static {
  83. if ("@VERSION@".equals("@" + "VERSION" + "@"))
  84. VERSION = "4.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD";
  85. else
  86. VERSION = "@VERSION@";
  87. String[] digits = VERSION.split("\\.");
  88. VERSION_MAJOR = Integer.parseInt(digits[0]);
  89. VERSION_MINOR = Integer.parseInt(digits[1]);
  90. VERSION_BUILD = digits[2];
  91. }
  92. // Configurable parameter names
  93. private static final String PARAMETER_DEBUG = "Debug";
  94. private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
  95. private static final int MAX_BUFFER_SIZE = 64 * 1024;
  96. private static WeakHashMap applicationToLastRequestDate = new WeakHashMap();
  97. private static WeakHashMap applicationToAjaxAppMgrMap = new WeakHashMap();
  98. // License for ApplicationServlets
  99. private static WeakHashMap licenseForApplicationClass = new WeakHashMap();
  100. private static WeakHashMap licensePrintedForApplicationClass = new WeakHashMap();
  101. private static final String RESOURCE_URI = "/RES/";
  102. private static final String AJAX_UIDL_URI = "/UIDL/";
  103. static final String THEME_DIRECTORY_PATH = "ITK-INF/themes/";
  104. // Maximum delay between request for an user to be considered active (in ms)
  105. private static final long ACTIVE_USER_REQUEST_INTERVAL = 1000 * 45;
  106. private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
  107. static final String WIDGETSET_DIRECTORY_PATH = "ITK-INF/widgetsets/";
  108. // Name of the default widget set, used if not specified in web.xml
  109. private static final String DEFAULT_WIDGETSET = "com.itmill.toolkit.terminal.gwt.DefaultWidgetSet";
  110. // Widget set narameter name
  111. private static final String PARAMETER_WIDGETSET = "widgetset";
  112. // Private fields
  113. private Class applicationClass;
  114. private Properties applicationProperties;
  115. private String resourcePath = null;
  116. private String debugMode = "";
  117. /**
  118. * Called by the servlet container to indicate to a servlet that the servlet
  119. * is being placed into service.
  120. *
  121. * @param servletConfig
  122. * the object containing the servlet's configuration and
  123. * initialization parameters
  124. * @throws javax.servlet.ServletException
  125. * if an exception has occurred that interferes with the
  126. * servlet's normal operation.
  127. */
  128. public void init(javax.servlet.ServletConfig servletConfig)
  129. throws javax.servlet.ServletException {
  130. super.init(servletConfig);
  131. // Gets the application class name
  132. String applicationClassName = servletConfig
  133. .getInitParameter("application");
  134. if (applicationClassName == null) {
  135. Log.error("Application not specified in servlet parameters");
  136. }
  137. // Stores the application parameters into Properties object
  138. this.applicationProperties = new Properties();
  139. for (Enumeration e = servletConfig.getInitParameterNames(); e
  140. .hasMoreElements();) {
  141. String name = (String) e.nextElement();
  142. this.applicationProperties.setProperty(name, servletConfig
  143. .getInitParameter(name));
  144. }
  145. // Overrides with server.xml parameters
  146. ServletContext context = servletConfig.getServletContext();
  147. for (Enumeration e = context.getInitParameterNames(); e
  148. .hasMoreElements();) {
  149. String name = (String) e.nextElement();
  150. this.applicationProperties.setProperty(name, context
  151. .getInitParameter(name));
  152. }
  153. // Gets the debug window parameter
  154. String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG, "")
  155. .toLowerCase();
  156. // Enables application specific debug
  157. if (!"".equals(debug) && !"true".equals(debug)
  158. && !"false".equals(debug))
  159. throw new ServletException(
  160. "If debug parameter is given for an application, it must be 'true' or 'false'");
  161. this.debugMode = debug;
  162. // Gets custom class loader
  163. String classLoaderName = getApplicationOrSystemProperty("ClassLoader",
  164. null);
  165. ClassLoader classLoader;
  166. if (classLoaderName == null)
  167. classLoader = getClass().getClassLoader();
  168. else {
  169. try {
  170. Class classLoaderClass = getClass().getClassLoader().loadClass(
  171. classLoaderName);
  172. Constructor c = classLoaderClass
  173. .getConstructor(new Class[] { ClassLoader.class });
  174. classLoader = (ClassLoader) c
  175. .newInstance(new Object[] { getClass().getClassLoader() });
  176. } catch (Exception e) {
  177. Log.error("Could not find specified class loader: "
  178. + classLoaderName);
  179. throw new ServletException(e);
  180. }
  181. }
  182. // Loads the application class using the same class loader
  183. // as the servlet itself
  184. try {
  185. this.applicationClass = classLoader.loadClass(applicationClassName);
  186. } catch (ClassNotFoundException e) {
  187. throw new ServletException("Failed to load application class: "
  188. + applicationClassName);
  189. }
  190. }
  191. /**
  192. * Gets an application or system property value.
  193. *
  194. * @param parameterName
  195. * the Name or the parameter.
  196. * @param defaultValue
  197. * the Default to be used.
  198. * @return String value or default if not found
  199. */
  200. private String getApplicationOrSystemProperty(String parameterName,
  201. String defaultValue) {
  202. // Try application properties
  203. String val = this.applicationProperties.getProperty(parameterName);
  204. if (val != null) {
  205. return val;
  206. }
  207. // Try lowercased application properties for backward compability with
  208. // 3.0.2 and earlier
  209. val = this.applicationProperties.getProperty(parameterName
  210. .toLowerCase());
  211. if (val != null) {
  212. return val;
  213. }
  214. // Try system properties
  215. String pkgName;
  216. Package pkg = this.getClass().getPackage();
  217. if (pkg != null) {
  218. pkgName = pkg.getName();
  219. } else {
  220. String className = this.getClass().getName();
  221. pkgName = new String(className.toCharArray(), 0, className
  222. .lastIndexOf('.'));
  223. }
  224. val = System.getProperty(pkgName + "." + parameterName);
  225. if (val != null) {
  226. return val;
  227. }
  228. // Try lowercased system properties
  229. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  230. if (val != null) {
  231. return val;
  232. }
  233. return defaultValue;
  234. }
  235. /**
  236. * Receives standard HTTP requests from the public service method and
  237. * dispatches them.
  238. *
  239. * @param request
  240. * the object that contains the request the client made of the
  241. * servlet.
  242. * @param response
  243. * the object that contains the response the servlet returns to
  244. * the client.
  245. * @throws ServletException
  246. * if an input or output error occurs while the servlet is
  247. * handling the TRACE request.
  248. * @throws IOException
  249. * if the request for the TRACE cannot be handled.
  250. */
  251. protected void service(HttpServletRequest request,
  252. HttpServletResponse response) throws ServletException, IOException {
  253. Application application = null;
  254. try {
  255. // handle file upload if multipart request
  256. if (ServletFileUpload.isMultipartContent(request)) {
  257. application = getApplication(request);
  258. getApplicationManager(application).handleFileUpload(request,
  259. response);
  260. return;
  261. }
  262. // Update browser details
  263. WebBrowser browser = WebApplicationContext.getApplicationContext(
  264. request.getSession()).getBrowser();
  265. browser.updateBrowserProperties(request);
  266. // TODO Add screen height and width to the GWT client
  267. // Gets the application
  268. application = getApplication(request);
  269. // Sets the last application request date
  270. synchronized (applicationToLastRequestDate) {
  271. applicationToLastRequestDate.put(application, new Date());
  272. }
  273. // Invokes context transaction listeners
  274. ((WebApplicationContext) application.getContext())
  275. .startTransaction(application, request);
  276. // Is this a download request from application
  277. DownloadStream download = null;
  278. // Handles AJAX UIDL requests
  279. String resourceId = request.getPathInfo();
  280. if (resourceId != null && resourceId.startsWith(AJAX_UIDL_URI)) {
  281. getApplicationManager(application).handleUidlRequest(request,
  282. response);
  283. return;
  284. }
  285. // Handles the URI if the application is still running
  286. if (application.isRunning())
  287. download = handleURI(application, request, response);
  288. // If this is not a download request
  289. if (download == null) {
  290. // TODO Clean this branch
  291. // Window renders are not cacheable
  292. response.setHeader("Cache-Control", "no-cache");
  293. response.setHeader("Pragma", "no-cache");
  294. response.setDateHeader("Expires", 0);
  295. // Finds the window within the application
  296. Window window = null;
  297. if (application.isRunning())
  298. window = getApplicationWindow(request, application);
  299. // Removes application if it has stopped
  300. if (!application.isRunning()) {
  301. endApplication(request, response, application);
  302. return;
  303. }
  304. // Sets terminal type for the window, if not already set
  305. if (window.getTerminal() == null) {
  306. window.setTerminal(browser);
  307. }
  308. // Finds theme name
  309. String themeName = window.getTheme();
  310. if (request.getParameter("theme") != null) {
  311. themeName = request.getParameter("theme");
  312. }
  313. // Handles resource requests
  314. if (handleResourceRequest(request, response, themeName))
  315. return;
  316. writeAjaxPage(request, response, window, themeName);
  317. }
  318. // For normal requests, transform the window
  319. if (download != null)
  320. handleDownload(download, request, response);
  321. } catch (Throwable e) {
  322. // Print stacktrace
  323. e.printStackTrace();
  324. // Re-throw other exceptions
  325. throw new ServletException(e);
  326. } finally {
  327. // Notifies transaction end
  328. if (application != null)
  329. ((WebApplicationContext) application.getContext())
  330. .endTransaction(application, request);
  331. }
  332. }
  333. /**
  334. *
  335. * @param request
  336. * the HTTP request.
  337. * @param response
  338. * the HTTP response to write to.
  339. * @param out
  340. * @param unhandledParameters
  341. * @param window
  342. * @param terminalType
  343. * @param theme
  344. * @throws IOException
  345. * if the writing failed due to input/output error.
  346. * @throws MalformedURLException
  347. * if the application is denied access the persistent data store
  348. * represented by the given URL.
  349. */
  350. private void writeAjaxPage(HttpServletRequest request,
  351. HttpServletResponse response, Window window, String themeName)
  352. throws IOException, MalformedURLException {
  353. response.setContentType("text/html");
  354. BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
  355. response.getOutputStream()));
  356. String uri = request.getRequestURL().toString();
  357. boolean hasSlash = (uri.charAt(uri.length() - 1) == '/') ? true : false;
  358. String relative = "";
  359. String t = request.getPathInfo().substring(1);
  360. while (t.indexOf('/') >= 0) {
  361. t = t.substring(t.indexOf('/') + 1);
  362. relative += "../";
  363. }
  364. // TODO remove GoogleMaps namespace and script
  365. page
  366. .write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
  367. + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
  368. page
  369. .write("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<title>IT Mill Toolkit 5</title>\n"
  370. + "<script type=\"text/javascript\">\n"
  371. + " var itmtk = {\n" + " appUri:'");
  372. String[] urlParts = getApplicationUrl(request).toString().split("\\/");
  373. String appUrl = "";
  374. // don't use server and port in uri. It may cause problems with some
  375. // virtual server configurations which lose the server name
  376. for (int i = 3; i < urlParts.length; i++)
  377. appUrl += "/" + urlParts[i];
  378. if (appUrl.endsWith("/")) {
  379. appUrl = appUrl.substring(0, appUrl.length() - 1);
  380. }
  381. page.write(appUrl);
  382. String widgetset = this.applicationProperties
  383. .getProperty(PARAMETER_WIDGETSET);
  384. if (widgetset == null) {
  385. widgetset = DEFAULT_WIDGETSET;
  386. }
  387. page
  388. .write("', pathInfo: '"
  389. + request.getPathInfo()
  390. + "'\n};\n"
  391. + "</script>\n"
  392. + "<script language='javascript' src='"
  393. + (hasSlash ? "../" : "")
  394. + relative
  395. + WIDGETSET_DIRECTORY_PATH
  396. + widgetset
  397. + "/"
  398. + widgetset
  399. + ".nocache.js'></script>"
  400. + "<link REL=\"stylesheet\" TYPE=\"text/css\" HREF=\""
  401. + request.getContextPath()
  402. + "/" // TODO relative url as above?
  403. + THEME_DIRECTORY_PATH
  404. + themeName
  405. + "/styles.css\">"
  406. + "</head>\n<body>\n"
  407. + " <iframe id=\"__gwt_historyFrame\" style=\"width:0;height:0;border:0\"></iframe>\n"
  408. + " <div id=\"itmtk-ajax-window\"></div>"
  409. + " </body>\n" + "</html>\n");
  410. page.close();
  411. }
  412. /**
  413. * Handles the requested URI. An application can add handlers to do special
  414. * processing, when a certain URI is requested. The handlers are invoked
  415. * before any windows URIs are processed and if a DownloadStream is returned
  416. * it is sent to the client.
  417. *
  418. * @param application
  419. * the Application owning the URI.
  420. * @param request
  421. * the HTTP request instance.
  422. * @param response
  423. * the HTTP response to write to.
  424. * @return boolean <code>true</code> if the request was handled and
  425. * further processing should be suppressed, <code>false</code>
  426. * otherwise.
  427. * @see com.itmill.toolkit.terminal.URIHandler
  428. */
  429. private DownloadStream handleURI(Application application,
  430. HttpServletRequest request, HttpServletResponse response) {
  431. String uri = request.getPathInfo();
  432. // If no URI is available
  433. if (uri == null || uri.length() == 0 || uri.equals("/"))
  434. return null;
  435. // Removes the leading /
  436. while (uri.startsWith("/") && uri.length() > 0)
  437. uri = uri.substring(1);
  438. // Handles the uri
  439. DownloadStream stream = null;
  440. try {
  441. stream = application.handleURI(application.getURL(), uri);
  442. } catch (Throwable t) {
  443. application.terminalError(new URIHandlerErrorImpl(application, t));
  444. }
  445. return stream;
  446. }
  447. /**
  448. * Handles the requested URI. An application can add handlers to do special
  449. * processing, when a certain URI is requested. The handlers are invoked
  450. * before any windows URIs are processed and if a DownloadStream is returned
  451. * it is sent to the client.
  452. *
  453. * @param stream
  454. * the download stream.
  455. *
  456. * @param request
  457. * the HTTP request instance.
  458. * @param response
  459. * the HTTP response to write to.
  460. *
  461. * @see com.itmill.toolkit.terminal.URIHandler
  462. */
  463. private void handleDownload(DownloadStream stream,
  464. HttpServletRequest request, HttpServletResponse response) {
  465. // Download from given stream
  466. InputStream data = stream.getStream();
  467. if (data != null) {
  468. // Sets content type
  469. response.setContentType(stream.getContentType());
  470. // Sets cache headers
  471. long cacheTime = stream.getCacheTime();
  472. if (cacheTime <= 0) {
  473. response.setHeader("Cache-Control", "no-cache");
  474. response.setHeader("Pragma", "no-cache");
  475. response.setDateHeader("Expires", 0);
  476. } else {
  477. response.setHeader("Cache-Control", "max-age=" + cacheTime
  478. / 1000);
  479. response.setDateHeader("Expires", System.currentTimeMillis()
  480. + cacheTime);
  481. response.setHeader("Pragma", "cache"); // Required to apply
  482. // caching in some
  483. // Tomcats
  484. }
  485. // Copy download stream parameters directly
  486. // to HTTP headers.
  487. Iterator i = stream.getParameterNames();
  488. if (i != null) {
  489. while (i.hasNext()) {
  490. String param = (String) i.next();
  491. response.setHeader((String) param, stream
  492. .getParameter(param));
  493. }
  494. }
  495. int bufferSize = stream.getBufferSize();
  496. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE)
  497. bufferSize = DEFAULT_BUFFER_SIZE;
  498. byte[] buffer = new byte[bufferSize];
  499. int bytesRead = 0;
  500. try {
  501. OutputStream out = response.getOutputStream();
  502. while ((bytesRead = data.read(buffer)) > 0) {
  503. out.write(buffer, 0, bytesRead);
  504. out.flush();
  505. }
  506. out.close();
  507. } catch (IOException ignored) {
  508. }
  509. }
  510. }
  511. /**
  512. * Handles theme resource file requests. Resources supplied with the themes
  513. * are provided by the WebAdapterServlet.
  514. *
  515. * @param request
  516. * the HTTP request.
  517. * @param response
  518. * the HTTP response.
  519. * @return boolean <code>true</code> if the request was handled and
  520. * further processing should be suppressed, <code>false</code>
  521. * otherwise.
  522. * @throws ServletException
  523. * if an exception has occurred that interferes with the
  524. * servlet's normal operation.
  525. */
  526. private boolean handleResourceRequest(HttpServletRequest request,
  527. HttpServletResponse response, String themeName)
  528. throws ServletException {
  529. // If the resource path is unassigned, initialize it
  530. if (resourcePath == null) {
  531. resourcePath = request.getContextPath() + request.getServletPath()
  532. + RESOURCE_URI;
  533. // WebSphere Application Server related fix
  534. resourcePath = resourcePath.replaceAll("//", "/");
  535. }
  536. String resourceId = request.getPathInfo();
  537. // Checks if this really is a resource request
  538. if (resourceId == null || !resourceId.startsWith(RESOURCE_URI))
  539. return false;
  540. // Checks the resource type
  541. resourceId = resourceId.substring(RESOURCE_URI.length());
  542. InputStream data = null;
  543. // Gets theme resources
  544. try {
  545. data = getServletContext().getResourceAsStream(
  546. THEME_DIRECTORY_PATH + themeName + "/" + resourceId);
  547. } catch (Exception e) {
  548. Log.info(e.getMessage());
  549. data = null;
  550. }
  551. // Writes the response
  552. try {
  553. if (data != null) {
  554. response.setContentType(FileTypeResolver
  555. .getMIMEType(resourceId));
  556. // Use default cache time for theme resources
  557. response.setHeader("Cache-Control", "max-age="
  558. + DEFAULT_THEME_CACHETIME / 1000);
  559. response.setDateHeader("Expires", System.currentTimeMillis()
  560. + DEFAULT_THEME_CACHETIME);
  561. response.setHeader("Pragma", "cache"); // Required to apply
  562. // caching in some
  563. // Tomcats
  564. // Writes the data to client
  565. byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  566. int bytesRead = 0;
  567. OutputStream out = response.getOutputStream();
  568. while ((bytesRead = data.read(buffer)) > 0) {
  569. out.write(buffer, 0, bytesRead);
  570. }
  571. out.close();
  572. data.close();
  573. } else {
  574. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  575. }
  576. } catch (java.io.IOException e) {
  577. Log.info("Resource transfer failed: " + request.getRequestURI()
  578. + ". (" + e.getMessage() + ")");
  579. }
  580. return true;
  581. }
  582. /**
  583. * Gets the current application URL from request.
  584. *
  585. * @param request
  586. * the HTTP request.
  587. * @throws MalformedURLException
  588. * if the application is denied access to the persistent data
  589. * store represented by the given URL.
  590. */
  591. private URL getApplicationUrl(HttpServletRequest request)
  592. throws MalformedURLException {
  593. URL applicationUrl;
  594. try {
  595. URL reqURL = new URL(
  596. (request.isSecure() ? "https://" : "http://")
  597. + request.getServerName()
  598. + ((request.isSecure() && request.getServerPort() == 443)
  599. || (!request.isSecure() && request
  600. .getServerPort() == 80) ? "" : ":"
  601. + request.getServerPort())
  602. + request.getRequestURI());
  603. String servletPath = request.getContextPath()
  604. + request.getServletPath();
  605. if (servletPath.length() == 0
  606. || servletPath.charAt(servletPath.length() - 1) != '/')
  607. servletPath = servletPath + "/";
  608. applicationUrl = new URL(reqURL, servletPath);
  609. } catch (MalformedURLException e) {
  610. Log.error("Error constructing application url "
  611. + request.getRequestURI() + " (" + e + ")");
  612. throw e;
  613. }
  614. return applicationUrl;
  615. }
  616. /**
  617. * Gets the existing application for given request. Looks for application
  618. * instance for given request based on the requested URL.
  619. *
  620. * @param request
  621. * the HTTP request.
  622. * @return Application instance, or null if the URL does not map to valid
  623. * application.
  624. * @throws MalformedURLException
  625. * if the application is denied access to the persistent data
  626. * store represented by the given URL.
  627. * @throws SAXException
  628. * @throws LicenseViolation
  629. * @throws InvalidLicenseFile
  630. * @throws LicenseSignatureIsInvalid
  631. * @throws LicenseFileHasNotBeenRead
  632. * @throws IllegalAccessException
  633. * @throws InstantiationException
  634. */
  635. private Application getApplication(HttpServletRequest request)
  636. throws MalformedURLException, LicenseFileHasNotBeenRead,
  637. LicenseSignatureIsInvalid, InvalidLicenseFile, LicenseViolation,
  638. SAXException, IllegalAccessException, InstantiationException {
  639. // Ensures that the session is still valid
  640. HttpSession session = request.getSession(true);
  641. // Gets application list for the session.
  642. Collection applications = WebApplicationContext.getApplicationContext(
  643. session).getApplications();
  644. // Search for the application (using the application URI) from the list
  645. for (Iterator i = applications.iterator(); i.hasNext();) {
  646. Application a = (Application) i.next();
  647. String aPath = a.getURL().getPath();
  648. String servletPath = request.getContextPath()
  649. + request.getServletPath();
  650. if (servletPath.length() < aPath.length())
  651. servletPath += "/";
  652. if (servletPath.equals(aPath)) {
  653. // Found a running application
  654. if (a.isRunning())
  655. return a;
  656. // Application has stopped, so remove it before creating a new
  657. // application
  658. WebApplicationContext.getApplicationContext(session)
  659. .removeApplication(a);
  660. break;
  661. }
  662. }
  663. // Creates application, because a running one was not found
  664. WebApplicationContext context = WebApplicationContext
  665. .getApplicationContext(request.getSession());
  666. URL applicationUrl = getApplicationUrl(request);
  667. // Creates new application and start it
  668. try {
  669. Application application = (Application) this.applicationClass
  670. .newInstance();
  671. context.addApplication(application);
  672. // Sets initial locale from the request
  673. application.setLocale(request.getLocale());
  674. // Starts application and check license
  675. initializeLicense(application);
  676. application.start(applicationUrl, this.applicationProperties,
  677. context);
  678. checkLicense(application);
  679. return application;
  680. } catch (IllegalAccessException e) {
  681. Log.error("Illegal access to application class "
  682. + this.applicationClass.getName());
  683. throw e;
  684. } catch (InstantiationException e) {
  685. Log.error("Failed to instantiate application class: "
  686. + this.applicationClass.getName());
  687. throw e;
  688. }
  689. }
  690. /**
  691. *
  692. * @param application
  693. */
  694. private void initializeLicense(Application application) {
  695. License license;
  696. synchronized (licenseForApplicationClass) {
  697. license = (License) licenseForApplicationClass.get(application
  698. .getClass());
  699. if (license == null) {
  700. license = new License();
  701. licenseForApplicationClass.put(application.getClass(), license);
  702. }
  703. }
  704. application.setToolkitLicense(license);
  705. }
  706. /**
  707. *
  708. * @param application
  709. * @throws LicenseFileHasNotBeenRead
  710. * if the license file has not been read.
  711. * @throws LicenseSignatureIsInvalid
  712. * if the license file has been changed or signature is
  713. * otherwise invalid.
  714. * @throws InvalidLicenseFile
  715. * if the license file is not of correct XML format.
  716. * @throws LicenseViolation
  717. *
  718. * @throws SAXException
  719. * the Error parsing the license file.
  720. */
  721. private void checkLicense(Application application)
  722. throws LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid,
  723. InvalidLicenseFile, LicenseViolation, SAXException {
  724. License license = application.getToolkitLicense();
  725. if (!license.hasBeenRead())
  726. // Lock threads that have not yet read license
  727. synchronized (license) {
  728. if (!license.hasBeenRead()) {
  729. InputStream lis;
  730. try {
  731. URL url = getServletContext().getResource(
  732. "/WEB-INF/itmill-toolkit-license.xml");
  733. if (url == null) {
  734. throw new RuntimeException(
  735. "License file could not be read. "
  736. + "You can install it to "
  737. + "WEB-INF/itmill-toolkit-license.xml.");
  738. }
  739. lis = url.openStream();
  740. license.readLicenseFile(lis);
  741. } catch (MalformedURLException e) {
  742. // This should not happen
  743. throw new RuntimeException(e);
  744. } catch (IOException e) {
  745. // This should not happen
  746. throw new RuntimeException(e);
  747. }
  748. // For each application class, print license description -
  749. // once
  750. if (!licensePrintedForApplicationClass
  751. .containsKey(applicationClass)) {
  752. licensePrintedForApplicationClass.put(applicationClass,
  753. Boolean.TRUE);
  754. if (license.shouldLimitsBePrintedOnInit()) {
  755. System.out.println(license
  756. .getDescription(application.getClass()
  757. .toString()));
  758. }
  759. }
  760. // Checks license validity
  761. try {
  762. license.check(applicationClass, VERSION_MAJOR,
  763. VERSION_MINOR, "IT Mill Toolkit", null);
  764. } catch (LicenseFileHasNotBeenRead e) {
  765. application.close();
  766. throw e;
  767. } catch (LicenseSignatureIsInvalid e) {
  768. application.close();
  769. throw e;
  770. } catch (InvalidLicenseFile e) {
  771. application.close();
  772. throw e;
  773. } catch (LicenseViolation e) {
  774. application.close();
  775. throw e;
  776. }
  777. }
  778. }
  779. // Checks concurrent user limit
  780. try {
  781. license.checkConcurrentUsers(getNumberOfActiveUsers() + 1);
  782. } catch (LicenseViolation e) {
  783. application.close();
  784. throw e;
  785. }
  786. }
  787. /**
  788. * Gets the number of active application-user pairs.
  789. *
  790. * This returns total number of all applications in the server that are
  791. * considered to be active. For an application to be active, it must have
  792. * been accessed less than ACTIVE_USER_REQUEST_INTERVAL ms.
  793. *
  794. * @return the Number of active application instances in the server.
  795. */
  796. private int getNumberOfActiveUsers() {
  797. int active = 0;
  798. synchronized (applicationToLastRequestDate) {
  799. Set apps = applicationToLastRequestDate.keySet();
  800. long now = System.currentTimeMillis();
  801. for (Iterator i = apps.iterator(); i.hasNext();) {
  802. Date lastReq = (Date) applicationToLastRequestDate
  803. .get(i.next());
  804. if (now - lastReq.getTime() < ACTIVE_USER_REQUEST_INTERVAL)
  805. active++;
  806. }
  807. }
  808. return active;
  809. }
  810. /**
  811. * Ends the application.
  812. *
  813. * @param request
  814. * the HTTP request.
  815. * @param response
  816. * the HTTP response to write to.
  817. * @param application
  818. * the application to end.
  819. * @throws IOException
  820. * if the writing failed due to input/output error.
  821. */
  822. private void endApplication(HttpServletRequest request,
  823. HttpServletResponse response, Application application)
  824. throws IOException {
  825. String logoutUrl = application.getLogoutURL();
  826. if (logoutUrl == null)
  827. logoutUrl = application.getURL().toString();
  828. HttpSession session = request.getSession();
  829. if (session != null) {
  830. WebApplicationContext.getApplicationContext(session)
  831. .removeApplication(application);
  832. }
  833. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  834. }
  835. /**
  836. * Gets the existing application or create a new one. Get a window within an
  837. * application based on the requested URI.
  838. *
  839. * @param request
  840. * the HTTP Request.
  841. * @param application
  842. * the Application to query for window.
  843. * @return Window matching the given URI or null if not found.
  844. * @throws ServletException
  845. * if an exception has occurred that interferes with the
  846. * servlet's normal operation.
  847. */
  848. private Window getApplicationWindow(HttpServletRequest request,
  849. Application application) throws ServletException {
  850. Window window = null;
  851. // Finds the window where the request is handled
  852. String path = request.getPathInfo();
  853. // Main window as the URI is empty
  854. if (path == null || path.length() == 0 || path.equals("/"))
  855. window = application.getMainWindow();
  856. // Try to search by window name
  857. else {
  858. String windowName = null;
  859. if (path.charAt(0) == '/')
  860. path = path.substring(1);
  861. int index = path.indexOf('/');
  862. if (index < 0) {
  863. windowName = path;
  864. path = "";
  865. } else {
  866. windowName = path.substring(0, index);
  867. path = path.substring(index + 1);
  868. }
  869. window = application.getWindow(windowName);
  870. if (window == null) {
  871. // By default, we use main window
  872. window = application.getMainWindow();
  873. } else if (!window.isVisible()) {
  874. // Implicitly painting without actually invoking paint()
  875. window.requestRepaintRequests();
  876. // If the window is invisible send a blank page
  877. return null;
  878. }
  879. }
  880. return window;
  881. }
  882. /**
  883. * Gets relative location of a theme resource.
  884. *
  885. * @param theme
  886. * the Theme name.
  887. * @param resource
  888. * the Theme resource.
  889. * @return External URI specifying the resource
  890. */
  891. public String getResourceLocation(String theme, ThemeResource resource) {
  892. if (resourcePath == null)
  893. return resource.getResourceId();
  894. return resourcePath + theme + "/" + resource.getResourceId();
  895. }
  896. /**
  897. * Checks if web adapter is in debug mode. Extra output is generated to log
  898. * when debug mode is enabled.
  899. *
  900. * @param parameters
  901. * @return <code>true</code> if the web adapter is in debug mode.
  902. * otherwise <code>false</code>.
  903. */
  904. public boolean isDebugMode(Map parameters) {
  905. if (parameters != null) {
  906. Object[] debug = (Object[]) parameters.get("debug");
  907. if (debug != null && !"false".equals(debug[0].toString())
  908. && !"false".equals(debugMode))
  909. return true;
  910. }
  911. return "true".equals(debugMode);
  912. }
  913. /**
  914. * Implementation of ParameterHandler.ErrorEvent interface.
  915. */
  916. public class ParameterHandlerErrorImpl implements
  917. ParameterHandler.ErrorEvent {
  918. private ParameterHandler owner;
  919. private Throwable throwable;
  920. /**
  921. * Gets the contained throwable.
  922. *
  923. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  924. */
  925. public Throwable getThrowable() {
  926. return this.throwable;
  927. }
  928. /**
  929. * Gets the source ParameterHandler.
  930. *
  931. * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
  932. */
  933. public ParameterHandler getParameterHandler() {
  934. return this.owner;
  935. }
  936. }
  937. /**
  938. * Implementation of URIHandler.ErrorEvent interface.
  939. */
  940. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
  941. private URIHandler owner;
  942. private Throwable throwable;
  943. /**
  944. *
  945. * @param owner
  946. * @param throwable
  947. */
  948. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  949. this.owner = owner;
  950. this.throwable = throwable;
  951. }
  952. /**
  953. * Gets the contained throwable.
  954. *
  955. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  956. */
  957. public Throwable getThrowable() {
  958. return this.throwable;
  959. }
  960. /**
  961. * Gets the source URIHandler.
  962. *
  963. * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
  964. */
  965. public URIHandler getURIHandler() {
  966. return this.owner;
  967. }
  968. }
  969. /**
  970. * Gets AJAX application manager for an application.
  971. *
  972. * If this application has not been running in ajax mode before, new manager
  973. * is created and web adapter stops listening to changes.
  974. *
  975. * @param application
  976. * @return AJAX Application Manager
  977. */
  978. private CommunicationManager getApplicationManager(Application application) {
  979. CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap
  980. .get(application);
  981. // This application is going from Web to AJAX mode, create new manager
  982. if (mgr == null) {
  983. // Creates new manager
  984. mgr = new CommunicationManager(application, this);
  985. applicationToAjaxAppMgrMap.put(application, mgr);
  986. // Manager takes control over the application
  987. mgr.takeControl();
  988. }
  989. return mgr;
  990. }
  991. /**
  992. * Gets resource path using different implementations. Required fo
  993. * supporting different servlet container implementations (application
  994. * servers).
  995. *
  996. * @param servletContext
  997. * @param path
  998. * the resource path.
  999. * @return the resource path.
  1000. */
  1001. protected static String getResourcePath(ServletContext servletContext,
  1002. String path) {
  1003. String resultPath = null;
  1004. resultPath = servletContext.getRealPath(path);
  1005. if (resultPath != null) {
  1006. return resultPath;
  1007. } else {
  1008. try {
  1009. URL url = servletContext.getResource(path);
  1010. resultPath = url.getFile();
  1011. } catch (Exception e) {
  1012. // ignored
  1013. }
  1014. }
  1015. return resultPath;
  1016. }
  1017. }