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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.server;
  5. import java.io.BufferedWriter;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.OutputStream;
  9. import java.io.OutputStreamWriter;
  10. import java.io.PrintWriter;
  11. import java.io.Writer;
  12. import java.lang.reflect.Constructor;
  13. import java.lang.reflect.InvocationTargetException;
  14. import java.lang.reflect.Method;
  15. import java.net.MalformedURLException;
  16. import java.net.URL;
  17. import java.util.Collection;
  18. import java.util.Enumeration;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.Map;
  22. import java.util.Properties;
  23. import javax.servlet.ServletContext;
  24. import javax.servlet.ServletException;
  25. import javax.servlet.ServletOutputStream;
  26. import javax.servlet.http.HttpServlet;
  27. import javax.servlet.http.HttpServletRequest;
  28. import javax.servlet.http.HttpServletResponse;
  29. import javax.servlet.http.HttpSession;
  30. import org.xml.sax.SAXException;
  31. import com.itmill.toolkit.Application;
  32. import com.itmill.toolkit.Application.SystemMessages;
  33. import com.itmill.toolkit.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
  34. import com.itmill.toolkit.service.FileTypeResolver;
  35. import com.itmill.toolkit.terminal.DownloadStream;
  36. import com.itmill.toolkit.terminal.ParameterHandler;
  37. import com.itmill.toolkit.terminal.ThemeResource;
  38. import com.itmill.toolkit.terminal.URIHandler;
  39. import com.itmill.toolkit.ui.Window;
  40. /**
  41. * This servlet connects IT Mill Toolkit Application to Web.
  42. *
  43. * @author IT Mill Ltd.
  44. * @version
  45. * @VERSION@
  46. * @since 5.0
  47. */
  48. public class ApplicationServlet extends HttpServlet {
  49. private static final long serialVersionUID = -4937882979845826574L;
  50. /**
  51. * Version number of this release. For example "5.0.0".
  52. */
  53. public static final String VERSION;
  54. /**
  55. * Major version number. For example 5 in 5.1.0.
  56. */
  57. public static final int VERSION_MAJOR;
  58. /**
  59. * Minor version number. For example 1 in 5.1.0.
  60. */
  61. public static final int VERSION_MINOR;
  62. /**
  63. * Builds number. For example 0-custom_tag in 5.0.0-custom_tag.
  64. */
  65. public static final String VERSION_BUILD;
  66. /* Initialize version numbers from string replaced by build-script. */
  67. static {
  68. if ("@VERSION@".equals("@" + "VERSION" + "@")) {
  69. VERSION = "5.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD";
  70. } else {
  71. VERSION = "@VERSION@";
  72. }
  73. final String[] digits = VERSION.split("\\.");
  74. VERSION_MAJOR = Integer.parseInt(digits[0]);
  75. VERSION_MINOR = Integer.parseInt(digits[1]);
  76. VERSION_BUILD = digits[2];
  77. }
  78. /**
  79. * If the attribute is present in the request, a html fragment will be
  80. * written instead of a whole page.
  81. */
  82. public static final String REQUEST_FRAGMENT = ApplicationServlet.class
  83. .getName()
  84. + ".fragment";
  85. /**
  86. * This request attribute forces widgetset used; e.g for portlets that can
  87. * not have different widgetsets.
  88. */
  89. public static final String REQUEST_WIDGETSET = ApplicationServlet.class
  90. .getName()
  91. + ".widgetset";
  92. /**
  93. * This request attribute is used to add styles to the main element. E.g
  94. * "height:500px" generates a style="height:500px" to the main element,
  95. * useful from some embedding situations (e.g portlet include.)
  96. */
  97. public static final String REQUEST_APPSTYLE = ApplicationServlet.class
  98. .getName()
  99. + ".style";
  100. // Configurable parameter names
  101. private static final String PARAMETER_DEBUG = "Debug";
  102. private static final String PARAMETER_ITMILL_RESOURCES = "Resources";
  103. private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
  104. private static final int MAX_BUFFER_SIZE = 64 * 1024;
  105. // TODO This is session specific not servlet wide data. No need to store
  106. // this here, move it to Session from where it can be queried when required
  107. protected static HashMap applicationToAjaxAppMgrMap = new HashMap();
  108. private static final String RESOURCE_URI = "/RES/";
  109. private static final String AJAX_UIDL_URI = "/UIDL";
  110. static final String THEME_DIRECTORY_PATH = "ITMILL/themes/";
  111. private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
  112. static final String WIDGETSET_DIRECTORY_PATH = "ITMILL/widgetsets/";
  113. // Name of the default widget set, used if not specified in web.xml
  114. private static final String DEFAULT_WIDGETSET = "com.itmill.toolkit.terminal.gwt.DefaultWidgetSet";
  115. // Widget set parameter name
  116. private static final String PARAMETER_WIDGETSET = "widgetset";
  117. // Private fields
  118. private Class applicationClass;
  119. private Properties applicationProperties;
  120. private String resourcePath = null;
  121. private String debugMode = "";
  122. // Is this servlet application runner
  123. private boolean isApplicationRunnerServlet = false;
  124. // If servlet is application runner, store request's classname
  125. private String applicationRunnerClassname = null;
  126. private ClassLoader classLoader;
  127. private boolean testingToolsActive = false;
  128. private String testingToolsServerUri = null;
  129. /**
  130. * Called by the servlet container to indicate to a servlet that the servlet
  131. * is being placed into service.
  132. *
  133. * @param servletConfig
  134. * the object containing the servlet's configuration and
  135. * initialization parameters
  136. * @throws javax.servlet.ServletException
  137. * if an exception has occurred that interferes with the
  138. * servlet's normal operation.
  139. */
  140. public void init(javax.servlet.ServletConfig servletConfig)
  141. throws javax.servlet.ServletException {
  142. super.init(servletConfig);
  143. // Get applicationRunner
  144. final String applicationRunner = servletConfig
  145. .getInitParameter("applicationRunner");
  146. if (applicationRunner != null) {
  147. if ("true".equals(applicationRunner)) {
  148. isApplicationRunnerServlet = true;
  149. } else if ("false".equals(applicationRunner)) {
  150. isApplicationRunnerServlet = false;
  151. } else {
  152. throw new ServletException(
  153. "If applicationRunner parameter is given for an application, it must be 'true' or 'false'");
  154. }
  155. }
  156. // Stores the application parameters into Properties object
  157. applicationProperties = new Properties();
  158. for (final Enumeration e = servletConfig.getInitParameterNames(); e
  159. .hasMoreElements();) {
  160. final String name = (String) e.nextElement();
  161. applicationProperties.setProperty(name, servletConfig
  162. .getInitParameter(name));
  163. }
  164. // Overrides with server.xml parameters
  165. final ServletContext context = servletConfig.getServletContext();
  166. for (final Enumeration e = context.getInitParameterNames(); e
  167. .hasMoreElements();) {
  168. final String name = (String) e.nextElement();
  169. applicationProperties.setProperty(name, context
  170. .getInitParameter(name));
  171. }
  172. // Gets the debug window parameter
  173. final String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG, "")
  174. .toLowerCase();
  175. // Enables application specific debug
  176. if (!"".equals(debug) && !"true".equals(debug)
  177. && !"false".equals(debug)) {
  178. throw new ServletException(
  179. "If debug parameter is given for an application, it must be 'true' or 'false'");
  180. }
  181. debugMode = debug;
  182. // Gets Testing Tools parameters if feature is activated
  183. if (getApplicationOrSystemProperty("testingToolsActive", "false")
  184. .equals("true")) {
  185. testingToolsActive = true;
  186. testingToolsServerUri = getApplicationOrSystemProperty(
  187. "testingToolsServerUri", null);
  188. }
  189. // Gets custom class loader
  190. final String classLoaderName = getApplicationOrSystemProperty(
  191. "ClassLoader", null);
  192. ClassLoader classLoader;
  193. if (classLoaderName == null) {
  194. classLoader = getClass().getClassLoader();
  195. } else {
  196. try {
  197. final Class classLoaderClass = getClass().getClassLoader()
  198. .loadClass(classLoaderName);
  199. final Constructor c = classLoaderClass
  200. .getConstructor(new Class[] { ClassLoader.class });
  201. classLoader = (ClassLoader) c
  202. .newInstance(new Object[] { getClass().getClassLoader() });
  203. } catch (final Exception e) {
  204. throw new ServletException(
  205. "Could not find specified class loader: "
  206. + classLoaderName, e);
  207. }
  208. }
  209. this.classLoader = classLoader;
  210. // Loads the application class using the same class loader
  211. // as the servlet itself
  212. if (!isApplicationRunnerServlet) {
  213. // Gets the application class name
  214. final String applicationClassName = servletConfig
  215. .getInitParameter("application");
  216. if (applicationClassName == null) {
  217. throw new ServletException(
  218. "Application not specified in servlet parameters");
  219. }
  220. try {
  221. applicationClass = classLoader.loadClass(applicationClassName);
  222. } catch (final ClassNotFoundException e) {
  223. throw new ServletException("Failed to load application class: "
  224. + applicationClassName);
  225. }
  226. } else {
  227. // This servlet is in application runner mode, it uses classloader
  228. // later to create Applications based on URL
  229. }
  230. }
  231. /**
  232. * Gets an application or system property value.
  233. *
  234. * @param parameterName
  235. * the Name or the parameter.
  236. * @param defaultValue
  237. * the Default to be used.
  238. * @return String value or default if not found
  239. */
  240. private String getApplicationOrSystemProperty(String parameterName,
  241. String defaultValue) {
  242. // Try application properties
  243. String val = applicationProperties.getProperty(parameterName);
  244. if (val != null) {
  245. return val;
  246. }
  247. // Try lowercased application properties for backward compability with
  248. // 3.0.2 and earlier
  249. val = applicationProperties.getProperty(parameterName.toLowerCase());
  250. if (val != null) {
  251. return val;
  252. }
  253. // Try system properties
  254. String pkgName;
  255. final Package pkg = getClass().getPackage();
  256. if (pkg != null) {
  257. pkgName = pkg.getName();
  258. } else {
  259. final String className = getClass().getName();
  260. pkgName = new String(className.toCharArray(), 0, className
  261. .lastIndexOf('.'));
  262. }
  263. val = System.getProperty(pkgName + "." + parameterName);
  264. if (val != null) {
  265. return val;
  266. }
  267. // Try lowercased system properties
  268. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  269. if (val != null) {
  270. return val;
  271. }
  272. return defaultValue;
  273. }
  274. /**
  275. * Receives standard HTTP requests from the public service method and
  276. * dispatches them.
  277. *
  278. * @param request
  279. * the object that contains the request the client made of the
  280. * servlet.
  281. * @param response
  282. * the object that contains the response the servlet returns to
  283. * the client.
  284. * @throws ServletException
  285. * if an input or output error occurs while the servlet is
  286. * handling the TRACE request.
  287. * @throws IOException
  288. * if the request for the TRACE cannot be handled.
  289. */
  290. protected void service(HttpServletRequest request,
  291. HttpServletResponse response) throws ServletException, IOException {
  292. // check if we should serve static files (widgetsets, themes)
  293. if ((request.getPathInfo() != null)
  294. && (request.getPathInfo().length() > 10)) {
  295. if ((request.getContextPath() != null)
  296. && (request.getRequestURI().startsWith("/ITMILL/"))) {
  297. serveStaticResourcesInITMILL(request.getRequestURI(), response);
  298. return;
  299. } else if (request.getRequestURI().startsWith(
  300. request.getContextPath() + "/ITMILL/")) {
  301. serveStaticResourcesInITMILL(request.getRequestURI().substring(
  302. request.getContextPath().length()), response);
  303. return;
  304. }
  305. }
  306. Application application = null;
  307. boolean UIDLrequest = false;
  308. try {
  309. // handle file upload if multipart request
  310. if (ServletFileUpload.isMultipartContent(request)) {
  311. application = getExistingApplication(request, response);
  312. if (application == null) {
  313. throw new SessionExpired();
  314. }
  315. // Invokes context transaction listeners
  316. // note: endTransaction is called on finalize below
  317. ((WebApplicationContext) application.getContext())
  318. .startTransaction(application, request);
  319. getApplicationManager(application).handleFileUpload(request,
  320. response);
  321. return;
  322. }
  323. // Update browser details
  324. final WebBrowser browser = WebApplicationContext
  325. .getApplicationContext(request.getSession()).getBrowser();
  326. browser.updateBrowserProperties(request);
  327. // TODO Add screen height and width to the GWT client
  328. // Handles AJAX UIDL requests
  329. if (request.getPathInfo() != null) {
  330. String compare = AJAX_UIDL_URI;
  331. if (isApplicationRunnerServlet) {
  332. final String[] URIparts = getApplicationRunnerURIs(request);
  333. applicationRunnerClassname = URIparts[4];
  334. compare = "/" + applicationRunnerClassname + AJAX_UIDL_URI;
  335. }
  336. if (request.getPathInfo().startsWith(compare + "/")
  337. || request.getPathInfo().endsWith(compare)) {
  338. UIDLrequest = true;
  339. application = getExistingApplication(request, response);
  340. if (application == null) {
  341. // No existing applications found
  342. final String repaintAll = request
  343. .getParameter("repaintAll");
  344. if ((repaintAll != null) && (repaintAll.equals("1"))) {
  345. // UIDL request contains valid repaintAll=1 event,
  346. // probably user wants to initiate new application
  347. // through custom index.html without writeAjaxPage
  348. application = getNewApplication(request, response);
  349. } else {
  350. // UIDL request refers to non-existing application
  351. throw new SessionExpired();
  352. }
  353. }
  354. // Invokes context transaction listeners
  355. // note: endTransaction is called on finalize below
  356. ((WebApplicationContext) application.getContext())
  357. .startTransaction(application, request);
  358. // Handle UIDL request
  359. getApplicationManager(application).handleUidlRequest(
  360. request, response, this);
  361. return;
  362. }
  363. }
  364. // Get existing application
  365. application = getExistingApplication(request, response);
  366. if (application == null
  367. || request.getParameter("restartApplication") != null
  368. || request.getParameter("closeApplication") != null) {
  369. if (application != null) {
  370. application.close();
  371. final HttpSession session = request.getSession(false);
  372. if (session != null) {
  373. ApplicationServlet.applicationToAjaxAppMgrMap
  374. .remove(application);
  375. WebApplicationContext.getApplicationContext(session)
  376. .removeApplication(application);
  377. }
  378. }
  379. if (request.getParameter("closeApplication") != null) {
  380. return;
  381. }
  382. // Not found, creating new application
  383. application = getNewApplication(request, response);
  384. }
  385. // Invokes context transaction listeners
  386. // note: endTransaction is called on finalize below
  387. ((WebApplicationContext) application.getContext())
  388. .startTransaction(application, request);
  389. // Removes application if it has stopped
  390. if (!application.isRunning()) {
  391. endApplication(request, response, application);
  392. return;
  393. }
  394. // Finds the window within the application
  395. Window window = null;
  396. window = getApplicationWindow(request, application);
  397. if (window == null) {
  398. throw new ServletException(
  399. "Application did not give any window, did you remember to setMainWindow()?");
  400. }
  401. // Handle parameters
  402. final Map parameters = request.getParameterMap();
  403. if (window != null && parameters != null) {
  404. window.handleParameters(parameters);
  405. }
  406. // Is this a download request from application
  407. DownloadStream download = null;
  408. // Handles the URI if the application is still running
  409. download = handleURI(application, request, response);
  410. // If this is not a download request
  411. if (download == null) {
  412. // Sets terminal type for the window, if not already set
  413. if (window.getTerminal() == null) {
  414. window.setTerminal(browser);
  415. }
  416. // Finds theme name
  417. String themeName = window.getTheme();
  418. if (request.getParameter("theme") != null) {
  419. themeName = request.getParameter("theme");
  420. }
  421. if (themeName == null) {
  422. themeName = "default";
  423. }
  424. // Handles resource requests
  425. if (handleResourceRequest(request, response, themeName)) {
  426. return;
  427. }
  428. // Send initial AJAX page that kickstarts Toolkit application
  429. writeAjaxPage(request, response, window, themeName, application);
  430. } else {
  431. // Client downloads an resource
  432. handleDownload(download, request, response);
  433. }
  434. } catch (final SessionExpired e) {
  435. // Session has expired, notify user
  436. try {
  437. Application.SystemMessages ci = getSystemMessages();
  438. if (!UIDLrequest) {
  439. // 'plain' http req - e.g. browser reload;
  440. // just go ahead redirect the browser
  441. response.sendRedirect(ci.getSessionExpiredURL());
  442. } else {
  443. // send uidl redirect
  444. criticalNotification(request, response, ci
  445. .getSessionExpiredCaption(), ci
  446. .getSessionExpiredMessage(), ci
  447. .getSessionExpiredURL());
  448. }
  449. } catch (SystemMessageException ee) {
  450. throw new ServletException(ee);
  451. }
  452. } catch (final Throwable e) {
  453. // if this was an UIDL request, response UIDL back to client
  454. if (UIDLrequest) {
  455. Application.SystemMessages ci = getSystemMessages();
  456. criticalNotification(request, response, ci
  457. .getInternalErrorCaption(), ci
  458. .getInternalErrorMessage(), ci.getInternalErrorURL());
  459. } else {
  460. // Re-throw other exceptions
  461. throw new ServletException(e);
  462. }
  463. } finally {
  464. // Notifies transaction end
  465. if (application != null) {
  466. ((WebApplicationContext) application.getContext())
  467. .endTransaction(application, request);
  468. }
  469. }
  470. }
  471. /** Get system messages from the current application class */
  472. private SystemMessages getSystemMessages() {
  473. try {
  474. Class appCls = applicationClass;
  475. if (isApplicationRunnerServlet) {
  476. appCls = getClass().getClassLoader().loadClass(
  477. applicationRunnerClassname);
  478. }
  479. Method m = appCls.getMethod("getSystemMessages", null);
  480. return (Application.SystemMessages) m.invoke(null, null);
  481. } catch (ClassNotFoundException e) {
  482. // This should never happen
  483. throw new SystemMessageException(e);
  484. } catch (SecurityException e) {
  485. throw new SystemMessageException(
  486. "Application.getSystemMessage() should be static public", e);
  487. } catch (NoSuchMethodException e) {
  488. // This is completely ok and should be silently ignored
  489. } catch (IllegalArgumentException e) {
  490. // This should never happen
  491. throw new SystemMessageException(e);
  492. } catch (IllegalAccessException e) {
  493. throw new SystemMessageException(
  494. "Application.getSystemMessage() should be static public", e);
  495. } catch (InvocationTargetException e) {
  496. // This should never happen
  497. throw new SystemMessageException(e);
  498. }
  499. return Application.getSystemMessages();
  500. }
  501. /**
  502. * Serve resources in ITMILL directory if requested.
  503. *
  504. * @param request
  505. * @param response
  506. * @throws IOException
  507. */
  508. private void serveStaticResourcesInITMILL(String filename,
  509. HttpServletResponse response) throws IOException {
  510. final ServletContext sc = getServletContext();
  511. InputStream is = sc.getResourceAsStream(filename);
  512. if (is == null) {
  513. // try if requested file is found from classloader
  514. // strip leading "/" otherwise stream from JAR wont work
  515. filename = filename.substring(1);
  516. is = classLoader.getResourceAsStream(filename);
  517. if (is == null) {
  518. // cannot serve requested file
  519. System.err
  520. .println("Requested resource ["
  521. + filename
  522. + "] not found from filesystem or through class loader."
  523. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/ITMILL folder.");
  524. response.setStatus(404);
  525. return;
  526. }
  527. }
  528. final String mimetype = sc.getMimeType(filename);
  529. if (mimetype != null) {
  530. response.setContentType(mimetype);
  531. }
  532. final OutputStream os = response.getOutputStream();
  533. final byte buffer[] = new byte[20000];
  534. int bytes;
  535. while ((bytes = is.read(buffer)) >= 0) {
  536. os.write(buffer, 0, bytes);
  537. }
  538. }
  539. /**
  540. * Send notification to client's application. Used to notify client of
  541. * critical errors and session expiration due to long inactivity. Server has
  542. * no knowledge of what application client refers to.
  543. *
  544. * @param request
  545. * the HTTP request instance.
  546. * @param response
  547. * the HTTP response to write to.
  548. * @param caption
  549. * for the notification
  550. * @param message
  551. * for the notification
  552. * @param url
  553. * url to load after message, null for current page
  554. * @throws IOException
  555. * if the writing failed due to input/output error.
  556. */
  557. void criticalNotification(HttpServletRequest request,
  558. HttpServletResponse response, String caption, String message,
  559. String url) throws IOException {
  560. // clients JS app is still running, but server application either
  561. // no longer exists or it might fail to perform reasonably.
  562. // send a notification to client's application and link how
  563. // to "restart" application.
  564. if (caption != null) {
  565. caption = "\"" + caption + "\"";
  566. }
  567. if (message != null) {
  568. message = "\"" + message + "\"";
  569. }
  570. if (url != null) {
  571. url = "\"" + url + "\"";
  572. }
  573. // Set the response type
  574. response.setContentType("application/json; charset=UTF-8");
  575. final ServletOutputStream out = response.getOutputStream();
  576. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  577. new OutputStreamWriter(out, "UTF-8")));
  578. outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
  579. + "\"appError\": {" + "\"caption\":" + caption + ","
  580. + "\"message\" : " + message + "," + "\"url\" : " + url
  581. + "}}, \"resources\": {}, \"locales\":[]}]");
  582. outWriter.flush();
  583. outWriter.close();
  584. out.flush();
  585. }
  586. /**
  587. * Resolve application URL and widgetset URL. Widgetset is not application
  588. * specific.
  589. *
  590. * @param request
  591. * @return string array consisting of application url first and then
  592. * widgetset url.
  593. * @throws MalformedURLException
  594. */
  595. private String[] getAppAndWidgetUrl(HttpServletRequest request)
  596. throws MalformedURLException {
  597. // don't use server and port in uri. It may cause problems with some
  598. // virtual server configurations which lose the server name
  599. String appUrl = null;
  600. String widgetsetUrl = null;
  601. if (isApplicationRunnerServlet) {
  602. final String[] URIparts = getApplicationRunnerURIs(request);
  603. widgetsetUrl = URIparts[0];
  604. if (widgetsetUrl.equals("/")) {
  605. widgetsetUrl = "";
  606. }
  607. appUrl = URIparts[1];
  608. } else {
  609. String[] urlParts;
  610. urlParts = getApplicationUrl(request).toString().split("\\/");
  611. appUrl = "";
  612. widgetsetUrl = "";
  613. // if context is specified add it to widgetsetUrl
  614. String ctxPath = request.getContextPath();
  615. if (ctxPath.length() == 0
  616. && request
  617. .getAttribute("javax.servlet.include.context_path") != null) {
  618. // include request (e.g portlet), get contex path from
  619. // attribute
  620. ctxPath = (String) request
  621. .getAttribute("javax.servlet.include.context_path");
  622. }
  623. if (urlParts.length > 3
  624. && urlParts[3].equals(ctxPath.replaceAll("\\/", ""))) {
  625. widgetsetUrl += "/" + urlParts[3];
  626. }
  627. for (int i = 3; i < urlParts.length; i++) {
  628. appUrl += "/" + urlParts[i];
  629. }
  630. if (appUrl.endsWith("/")) {
  631. appUrl = appUrl.substring(0, appUrl.length() - 1);
  632. }
  633. }
  634. return new String[] { appUrl, widgetsetUrl };
  635. }
  636. /**
  637. *
  638. * @param request
  639. * the HTTP request.
  640. * @param response
  641. * the HTTP response to write to.
  642. * @param out
  643. * @param unhandledParameters
  644. * @param window
  645. * @param terminalType
  646. * @param theme
  647. * @throws IOException
  648. * if the writing failed due to input/output error.
  649. * @throws MalformedURLException
  650. * if the application is denied access the persistent data store
  651. * represented by the given URL.
  652. */
  653. private void writeAjaxPage(HttpServletRequest request,
  654. HttpServletResponse response, Window window, String themeName,
  655. Application application) throws IOException, MalformedURLException {
  656. // e.g portlets only want a html fragment
  657. boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null);
  658. if (fragment) {
  659. request.setAttribute(Application.class.getName(), application);
  660. }
  661. final BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
  662. response.getOutputStream()));
  663. final String pathInfo = request.getPathInfo() == null ? "/" : request
  664. .getPathInfo();
  665. String title = ((window == null || window.getCaption() == null) ? "IT Mill Toolkit 5"
  666. : window.getCaption());
  667. String widgetset = null;
  668. // request widgetset takes precedence (e.g portlet include)
  669. Object reqParam = request.getAttribute(REQUEST_WIDGETSET);
  670. try {
  671. widgetset = (String) reqParam;
  672. } catch (Exception e) {
  673. // FIXME: Handle exception
  674. System.err.println("Warning: request param '" + REQUEST_WIDGETSET
  675. + "' could not be used (is not a String)" + e);
  676. }
  677. if (widgetset == null) {
  678. widgetset = applicationProperties.getProperty(PARAMETER_WIDGETSET);
  679. }
  680. if (widgetset == null) {
  681. widgetset = DEFAULT_WIDGETSET;
  682. }
  683. final String[] urls = getAppAndWidgetUrl(request);
  684. final String appUrl = urls[0];
  685. final String widgetsetUrl = urls[1];
  686. final String staticFilePath = getApplicationOrSystemProperty(
  687. PARAMETER_ITMILL_RESOURCES, widgetsetUrl);
  688. // Default theme does not use theme URI
  689. String themeUri = null;
  690. if (themeName != null) {
  691. // Using custom theme
  692. themeUri = staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName;
  693. }
  694. boolean testingApplication = testingToolsActive
  695. && request.getParameter("TT") != null;
  696. if (!fragment) {
  697. // Window renders are not cacheable
  698. response.setCharacterEncoding("utf-8");
  699. response.setHeader("Cache-Control", "no-cache");
  700. response.setHeader("Pragma", "no-cache");
  701. response.setDateHeader("Expires", 0);
  702. response.setContentType("text/html");
  703. // write html header
  704. page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
  705. + "XHTML 1.0 Transitional//EN\" "
  706. + "\"http://www.w3.org/TR/xhtml1/"
  707. + "DTD/xhtml1-transitional.dtd\">\n");
  708. page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
  709. + ">\n<head>\n");
  710. page
  711. .write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n");
  712. page.write("<style type=\"text/css\">"
  713. + "html, body {height:100%;}</style>");
  714. page.write("<title>" + title + "</title>");
  715. if (testingApplication) {
  716. // TT script needs to be in head as it needs to be the first
  717. // to hook capturing event listeners
  718. writeTestingToolsScripts(page, request);
  719. }
  720. page
  721. .write("\n</head>\n<body scroll=\"auto\" class=\"i-generated-body\">\n");
  722. }
  723. String appId = appUrl;
  724. if ("".equals(appUrl)) {
  725. appId = "ROOT";
  726. }
  727. appId = appId.replaceAll("[^a-zA-Z0-9]", "");
  728. if (isGecko17(request)) {
  729. // special start page for gecko 1.7 versions. Firefox 1.0 is not
  730. // supported, but the hack is make it possible to use linux and
  731. // hosted mode browser for debugging. Note that due this hack,
  732. // debugging gwt code in portals with linux will be problematic if
  733. // there are multiple toolkit portlets visible at the same time.
  734. // TODO remove this when hosted mode on linux gets newer gecko
  735. page.write("<iframe id=\"__gwt_historyFrame\" "
  736. + "style=\"width:0;height:0;border:0;overflow:"
  737. + "hidden\" src=\"javascript:false\"></iframe>\n");
  738. page.write("<script language='javascript' src='" + staticFilePath
  739. + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + "/"
  740. + widgetset + ".nocache.js'></script>\n");
  741. page.write("<script type=\"text/javascript\">\n");
  742. page.write("//<![CDATA[\n");
  743. page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
  744. + "if(!itmill) { var itmill = {}} \n"
  745. + "itmill.toolkitConfigurations = {};\n"
  746. + "itmill.themesLoaded = {}};\n");
  747. page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
  748. page.write("appUri:'" + appUrl + "', ");
  749. page.write("pathInfo: '" + pathInfo + "', ");
  750. page.write("themeUri:");
  751. page.write(themeUri != null ? "'" + themeUri + "'" : "null");
  752. page.write(", versionInfo : {toolkitVersion:\"");
  753. page.write(VERSION);
  754. page.write("\",applicationVersion:\"");
  755. page.write(application.getVersion());
  756. page.write("\"}");
  757. page.write("};\n//]]>\n</script>\n");
  758. if (themeName != null) {
  759. // Custom theme's stylesheet, load only once, in different
  760. // script
  761. // tag to be dominate styles injected by widget
  762. // set
  763. page.write("<script type=\"text/javascript\">\n");
  764. page.write("//<![CDATA[\n");
  765. page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
  766. page
  767. .write("var stylesheet = document.createElement('link');\n");
  768. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  769. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  770. page.write("stylesheet.setAttribute('href', '" + themeUri
  771. + "/styles.css');\n");
  772. page
  773. .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  774. page.write("itmill.themesLoaded['" + themeName
  775. + "'] = true;\n}\n");
  776. page.write("//]]>\n</script>\n");
  777. }
  778. } else {
  779. page.write("<script type=\"text/javascript\">\n");
  780. page.write("//<![CDATA[\n");
  781. page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
  782. + "if(!itmill) { var itmill = {}} \n"
  783. + "itmill.toolkitConfigurations = {};\n"
  784. + "itmill.themesLoaded = {};\n");
  785. page.write("document.write('<iframe id=\"__gwt_historyFrame\" "
  786. + "style=\"width:0;height:0;border:0;overflow:"
  787. + "hidden\" src=\"javascript:false\"></iframe>');\n");
  788. page.write("document.write(\"<script language='javascript' src='"
  789. + staticFilePath + "/" + WIDGETSET_DIRECTORY_PATH
  790. + widgetset + "/" + widgetset
  791. + ".nocache.js'><\\/script>\");\n}\n");
  792. page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
  793. page.write("appUri:'" + appUrl + "', ");
  794. page.write("pathInfo: '" + pathInfo + "', ");
  795. page.write("themeUri:");
  796. page.write(themeUri != null ? "'" + themeUri + "'" : "null");
  797. page.write(", versionInfo : {toolkitVersion:\"");
  798. page.write(VERSION);
  799. page.write("\",applicationVersion:\"");
  800. page.write(application.getVersion());
  801. page.write("\"}");
  802. page.write("};\n//]]>\n</script>\n");
  803. if (themeName != null) {
  804. // Custom theme's stylesheet, load only once, in different
  805. // script
  806. // tag to be dominate styles injected by widget
  807. // set
  808. page.write("<script type=\"text/javascript\">\n");
  809. page.write("//<![CDATA[\n");
  810. page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
  811. page
  812. .write("var stylesheet = document.createElement('link');\n");
  813. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  814. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  815. page.write("stylesheet.setAttribute('href', '" + themeUri
  816. + "/styles.css');\n");
  817. page
  818. .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  819. page.write("itmill.themesLoaded['" + themeName
  820. + "'] = true;\n}\n");
  821. page.write("//]]>\n</script>\n");
  822. }
  823. }
  824. String style = null;
  825. reqParam = request.getAttribute(REQUEST_APPSTYLE);
  826. if (reqParam != null) {
  827. style = "style=\"" + reqParam + "\"";
  828. }
  829. page.write("<div id=\"" + appId + "\" class=\"i-app\" "
  830. + (style != null ? style : "") + "></div>\n");
  831. if (!fragment) {
  832. page.write("</body>\n</html>\n");
  833. }
  834. page.close();
  835. }
  836. private boolean isGecko17(HttpServletRequest request) {
  837. final WebBrowser browser = WebApplicationContext.getApplicationContext(
  838. request.getSession()).getBrowser();
  839. if (browser != null && browser.getBrowserApplication() != null) {
  840. if (browser.getBrowserApplication().indexOf("rv:1.7.") > 0
  841. && browser.getBrowserApplication().indexOf("Gecko") > 0) {
  842. return true;
  843. }
  844. }
  845. return false;
  846. }
  847. private void writeTestingToolsScripts(Writer page,
  848. HttpServletRequest request) throws IOException {
  849. // Testing Tools script and CSS files are served from Testing Tools
  850. // Server
  851. String ext = getTestingToolsUri(request);
  852. ext = ext.substring(0, ext.lastIndexOf('/'));
  853. page.write("<script src=\"" + ext + "/ext/TT.js"
  854. + "\" type=\"text/javascript\"></script>\n");
  855. page.write("<link rel=\"stylesheet\" href=\"" + ext + "/ext/TT.css"
  856. + "\" type=\"text/css\" />\n");
  857. }
  858. private String getTestingToolsUri(HttpServletRequest request) {
  859. if (testingToolsServerUri == null) {
  860. // Default behavior is that Testing Tools Server application exists
  861. // on same host as current application does in port 8099.
  862. testingToolsServerUri = "http" + "://" + request.getServerName()
  863. + ":8099" + "/TestingToolsServer";
  864. }
  865. return testingToolsServerUri;
  866. }
  867. /**
  868. * Handles the requested URI. An application can add handlers to do special
  869. * processing, when a certain URI is requested. The handlers are invoked
  870. * before any windows URIs are processed and if a DownloadStream is returned
  871. * it is sent to the client.
  872. *
  873. * @param application
  874. * the Application owning the URI.
  875. * @param request
  876. * the HTTP request instance.
  877. * @param response
  878. * the HTTP response to write to.
  879. * @return boolean <code>true</code> if the request was handled and further
  880. * processing should be suppressed, <code>false</code> otherwise.
  881. * @see com.itmill.toolkit.terminal.URIHandler
  882. */
  883. private DownloadStream handleURI(Application application,
  884. HttpServletRequest request, HttpServletResponse response) {
  885. String uri = request.getPathInfo();
  886. // If no URI is available
  887. if (uri == null) {
  888. uri = "";
  889. }
  890. // Removes the leading /
  891. while (uri.startsWith("/") && uri.length() > 0) {
  892. uri = uri.substring(1);
  893. }
  894. // If using application runner, remove package and class name
  895. if (isApplicationRunnerServlet) {
  896. uri = uri.replaceFirst(applicationRunnerClassname + "/", "");
  897. }
  898. // Handles the uri
  899. DownloadStream stream = null;
  900. try {
  901. stream = application.handleURI(application.getURL(), uri);
  902. } catch (final Throwable t) {
  903. application.terminalError(new URIHandlerErrorImpl(application, t));
  904. }
  905. return stream;
  906. }
  907. /**
  908. * Handles the requested URI. An application can add handlers to do special
  909. * processing, when a certain URI is requested. The handlers are invoked
  910. * before any windows URIs are processed and if a DownloadStream is returned
  911. * it is sent to the client.
  912. *
  913. * @param stream
  914. * the download stream.
  915. *
  916. * @param request
  917. * the HTTP request instance.
  918. * @param response
  919. * the HTTP response to write to.
  920. * @throws IOException
  921. *
  922. * @see com.itmill.toolkit.terminal.URIHandler
  923. */
  924. private void handleDownload(DownloadStream stream,
  925. HttpServletRequest request, HttpServletResponse response)
  926. throws IOException {
  927. if (stream.getParameter("Location") != null) {
  928. response.setStatus(HttpServletResponse.SC_FOUND);
  929. response.addHeader("Location", stream.getParameter("Location"));
  930. return;
  931. }
  932. // Download from given stream
  933. final InputStream data = stream.getStream();
  934. if (data != null) {
  935. // Sets content type
  936. response.setContentType(stream.getContentType());
  937. // Sets cache headers
  938. final long cacheTime = stream.getCacheTime();
  939. if (cacheTime <= 0) {
  940. response.setHeader("Cache-Control", "no-cache");
  941. response.setHeader("Pragma", "no-cache");
  942. response.setDateHeader("Expires", 0);
  943. } else {
  944. response.setHeader("Cache-Control", "max-age=" + cacheTime
  945. / 1000);
  946. response.setDateHeader("Expires", System.currentTimeMillis()
  947. + cacheTime);
  948. response.setHeader("Pragma", "cache"); // Required to apply
  949. // caching in some
  950. // Tomcats
  951. }
  952. // Copy download stream parameters directly
  953. // to HTTP headers.
  954. final Iterator i = stream.getParameterNames();
  955. if (i != null) {
  956. while (i.hasNext()) {
  957. final String param = (String) i.next();
  958. response.setHeader(param, stream.getParameter(param));
  959. }
  960. }
  961. int bufferSize = stream.getBufferSize();
  962. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) {
  963. bufferSize = DEFAULT_BUFFER_SIZE;
  964. }
  965. final byte[] buffer = new byte[bufferSize];
  966. int bytesRead = 0;
  967. final OutputStream out = response.getOutputStream();
  968. while ((bytesRead = data.read(buffer)) > 0) {
  969. out.write(buffer, 0, bytesRead);
  970. out.flush();
  971. }
  972. out.close();
  973. }
  974. }
  975. /**
  976. * Handles theme resource file requests. Resources supplied with the themes
  977. * are provided by the WebAdapterServlet.
  978. *
  979. * @param request
  980. * the HTTP request.
  981. * @param response
  982. * the HTTP response.
  983. * @return boolean <code>true</code> if the request was handled and further
  984. * processing should be suppressed, <code>false</code> otherwise.
  985. * @throws ServletException
  986. * if an exception has occurred that interferes with the
  987. * servlet's normal operation.
  988. */
  989. private boolean handleResourceRequest(HttpServletRequest request,
  990. HttpServletResponse response, String themeName)
  991. throws ServletException {
  992. // If the resource path is unassigned, initialize it
  993. if (resourcePath == null) {
  994. resourcePath = request.getContextPath() + request.getServletPath()
  995. + RESOURCE_URI;
  996. // WebSphere Application Server related fix
  997. resourcePath = resourcePath.replaceAll("//", "/");
  998. }
  999. String resourceId = request.getPathInfo();
  1000. // Checks if this really is a resource request
  1001. if (resourceId == null || !resourceId.startsWith(RESOURCE_URI)) {
  1002. return false;
  1003. }
  1004. // Checks the resource type
  1005. resourceId = resourceId.substring(RESOURCE_URI.length());
  1006. InputStream data = null;
  1007. // Gets theme resources
  1008. try {
  1009. data = getServletContext().getResourceAsStream(
  1010. THEME_DIRECTORY_PATH + themeName + "/" + resourceId);
  1011. } catch (final Exception e) {
  1012. // FIXME: Handle exception
  1013. e.printStackTrace();
  1014. data = null;
  1015. }
  1016. // Writes the response
  1017. try {
  1018. if (data != null) {
  1019. response.setContentType(FileTypeResolver
  1020. .getMIMEType(resourceId));
  1021. // Use default cache time for theme resources
  1022. response.setHeader("Cache-Control", "max-age="
  1023. + DEFAULT_THEME_CACHETIME / 1000);
  1024. response.setDateHeader("Expires", System.currentTimeMillis()
  1025. + DEFAULT_THEME_CACHETIME);
  1026. response.setHeader("Pragma", "cache"); // Required to apply
  1027. // caching in some
  1028. // Tomcats
  1029. // Writes the data to client
  1030. final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  1031. int bytesRead = 0;
  1032. final OutputStream out = response.getOutputStream();
  1033. while ((bytesRead = data.read(buffer)) > 0) {
  1034. out.write(buffer, 0, bytesRead);
  1035. }
  1036. out.close();
  1037. data.close();
  1038. } else {
  1039. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  1040. }
  1041. } catch (final java.io.IOException e) {
  1042. // FIXME: Handle exception
  1043. System.err.println("Resource transfer failed: "
  1044. + request.getRequestURI() + ". (" + e.getMessage() + ")");
  1045. }
  1046. return true;
  1047. }
  1048. /**
  1049. * Gets the current application URL from request.
  1050. *
  1051. * @param request
  1052. * the HTTP request.
  1053. * @throws MalformedURLException
  1054. * if the application is denied access to the persistent data
  1055. * store represented by the given URL.
  1056. */
  1057. private URL getApplicationUrl(HttpServletRequest request)
  1058. throws MalformedURLException {
  1059. URL applicationUrl;
  1060. final URL reqURL = new URL(
  1061. (request.isSecure() ? "https://" : "http://")
  1062. + request.getServerName()
  1063. + ((request.isSecure() && request.getServerPort() == 443)
  1064. || (!request.isSecure() && request
  1065. .getServerPort() == 80) ? "" : ":"
  1066. + request.getServerPort())
  1067. + request.getRequestURI());
  1068. String servletPath = "";
  1069. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  1070. // this is an include request
  1071. servletPath = request.getAttribute(
  1072. "javax.servlet.include.context_path").toString()
  1073. + request
  1074. .getAttribute("javax.servlet.include.servlet_path");
  1075. } else {
  1076. servletPath = request.getContextPath() + request.getServletPath();
  1077. }
  1078. if (servletPath.length() == 0
  1079. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1080. servletPath = servletPath + "/";
  1081. }
  1082. applicationUrl = new URL(reqURL, servletPath);
  1083. return applicationUrl;
  1084. }
  1085. /**
  1086. * Parses application runner URIs.
  1087. *
  1088. * If request URL is e.g.
  1089. * http://localhost:8080/itmill/run/com.itmill.toolkit.demo.Calc then
  1090. * <ul>
  1091. * <li>context=itmill</li>
  1092. * <li>Runner servlet=run</li>
  1093. * <li>Toolkit application=com.itmill.toolkit.demo.Calc</li>
  1094. * </ul>
  1095. *
  1096. * @param request
  1097. * @return string array containing widgetset URI, application URI and
  1098. * context, runner, application classname
  1099. */
  1100. private String[] getApplicationRunnerURIs(HttpServletRequest request) {
  1101. final String[] urlParts = request.getRequestURI().toString().split(
  1102. "\\/");
  1103. String context = null;
  1104. String runner = null;
  1105. String applicationClassname = null;
  1106. if (urlParts[1].equals(request.getContextPath().replaceAll("\\/", ""))) {
  1107. // class name comes after web context and runner application
  1108. context = urlParts[1];
  1109. runner = urlParts[2];
  1110. applicationClassname = urlParts[3];
  1111. return new String[] { "/" + context,
  1112. "/" + context + "/" + runner + "/" + applicationClassname,
  1113. context, runner, applicationClassname };
  1114. } else {
  1115. // no context
  1116. context = "";
  1117. runner = urlParts[1];
  1118. applicationClassname = urlParts[2];
  1119. return new String[] { "/",
  1120. "/" + runner + "/" + applicationClassname, context, runner,
  1121. applicationClassname };
  1122. }
  1123. }
  1124. /**
  1125. * Gets the existing application for given request. Looks for application
  1126. * instance for given request based on the requested URL.
  1127. *
  1128. * @param request
  1129. * the HTTP request.
  1130. * @param response
  1131. * @return Application instance, or null if the URL does not map to valid
  1132. * application.
  1133. * @throws MalformedURLException
  1134. * if the application is denied access to the persistent data
  1135. * store represented by the given URL.
  1136. * @throws SAXException
  1137. * @throws IllegalAccessException
  1138. * @throws InstantiationException
  1139. */
  1140. private Application getExistingApplication(HttpServletRequest request,
  1141. HttpServletResponse response) throws MalformedURLException,
  1142. SAXException, IllegalAccessException, InstantiationException {
  1143. // Ensures that the session is still valid
  1144. final HttpSession session = request.getSession(true);
  1145. // Gets application list for the session.
  1146. final Collection applications = WebApplicationContext
  1147. .getApplicationContext(session).getApplications();
  1148. // Search for the application (using the application URI) from the list
  1149. for (final Iterator i = applications.iterator(); i.hasNext();) {
  1150. final Application a = (Application) i.next();
  1151. final String aPath = a.getURL().getPath();
  1152. String servletPath = "";
  1153. if (isApplicationRunnerServlet) {
  1154. final String[] URIparts = getApplicationRunnerURIs(request);
  1155. servletPath = URIparts[1] + "/";
  1156. } else {
  1157. servletPath = request.getContextPath()
  1158. + request.getServletPath();
  1159. if (servletPath.length() < aPath.length()) {
  1160. servletPath += "/";
  1161. }
  1162. }
  1163. if (servletPath.equals(aPath)) {
  1164. // Found a running application
  1165. if (a.isRunning()) {
  1166. return a;
  1167. }
  1168. // Application has stopped, so remove it before creating a new
  1169. // application
  1170. WebApplicationContext.getApplicationContext(session)
  1171. .removeApplication(a);
  1172. break;
  1173. }
  1174. }
  1175. // Existing application not found
  1176. return null;
  1177. }
  1178. /**
  1179. * Creates new application for given request.
  1180. *
  1181. * @param request
  1182. * the HTTP request.
  1183. * @param response
  1184. * @return Application instance, or null if the URL does not map to valid
  1185. * application.
  1186. * @throws MalformedURLException
  1187. * if the application is denied access to the persistent data
  1188. * store represented by the given URL.
  1189. * @throws SAXException
  1190. * @throws IllegalAccessException
  1191. * @throws InstantiationException
  1192. */
  1193. private Application getNewApplication(HttpServletRequest request,
  1194. HttpServletResponse response) throws MalformedURLException,
  1195. SAXException, IllegalAccessException, InstantiationException {
  1196. // Create application
  1197. final WebApplicationContext context = WebApplicationContext
  1198. .getApplicationContext(request.getSession());
  1199. final URL applicationUrl;
  1200. if (isApplicationRunnerServlet) {
  1201. final String[] URIparts = getApplicationRunnerURIs(request);
  1202. final String applicationClassname = URIparts[4];
  1203. applicationUrl = new URL(getApplicationUrl(request).toString()
  1204. + applicationClassname + "/");
  1205. try {
  1206. applicationClass = classLoader.loadClass(applicationClassname);
  1207. } catch (final ClassNotFoundException e) {
  1208. throw new InstantiationException(
  1209. "Failed to load application class: "
  1210. + applicationClassname);
  1211. }
  1212. } else {
  1213. applicationUrl = getApplicationUrl(request);
  1214. }
  1215. // Creates new application and start it
  1216. try {
  1217. final Application application = (Application) applicationClass
  1218. .newInstance();
  1219. context.addApplication(application);
  1220. // Sets initial locale from the request
  1221. application.setLocale(request.getLocale());
  1222. // Starts application
  1223. application.start(applicationUrl, applicationProperties, context);
  1224. return application;
  1225. } catch (final IllegalAccessException e) {
  1226. throw e;
  1227. } catch (final InstantiationException e) {
  1228. throw e;
  1229. }
  1230. }
  1231. /**
  1232. * Ends the application.
  1233. *
  1234. * @param request
  1235. * the HTTP request.
  1236. * @param response
  1237. * the HTTP response to write to.
  1238. * @param application
  1239. * the application to end.
  1240. * @throws IOException
  1241. * if the writing failed due to input/output error.
  1242. */
  1243. private void endApplication(HttpServletRequest request,
  1244. HttpServletResponse response, Application application)
  1245. throws IOException {
  1246. String logoutUrl = application.getLogoutURL();
  1247. if (logoutUrl == null) {
  1248. logoutUrl = application.getURL().toString();
  1249. }
  1250. final HttpSession session = request.getSession();
  1251. if (session != null) {
  1252. WebApplicationContext.getApplicationContext(session)
  1253. .removeApplication(application);
  1254. }
  1255. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  1256. }
  1257. /**
  1258. * Gets the existing application or create a new one. Get a window within an
  1259. * application based on the requested URI.
  1260. *
  1261. * @param request
  1262. * the HTTP Request.
  1263. * @param application
  1264. * the Application to query for window.
  1265. * @return Window matching the given URI or null if not found.
  1266. * @throws ServletException
  1267. * if an exception has occurred that interferes with the
  1268. * servlet's normal operation.
  1269. */
  1270. private Window getApplicationWindow(HttpServletRequest request,
  1271. Application application) throws ServletException {
  1272. Window window = null;
  1273. // Finds the window where the request is handled
  1274. String path = request.getPathInfo();
  1275. // Main window as the URI is empty
  1276. if (path == null || path.length() == 0 || path.equals("/")) {
  1277. window = application.getMainWindow();
  1278. } else {
  1279. String windowName = null;
  1280. if (path.charAt(0) == '/') {
  1281. path = path.substring(1);
  1282. }
  1283. final int index = path.indexOf('/');
  1284. if (index < 0) {
  1285. windowName = path;
  1286. path = "";
  1287. } else {
  1288. windowName = path.substring(0, index);
  1289. path = path.substring(index + 1);
  1290. }
  1291. window = application.getWindow(windowName);
  1292. if (window == null) {
  1293. // By default, we use main window
  1294. window = application.getMainWindow();
  1295. } else if (!window.isVisible()) {
  1296. // Implicitly painting without actually invoking paint()
  1297. window.requestRepaintRequests();
  1298. // If the window is invisible send a blank page
  1299. return null;
  1300. }
  1301. }
  1302. return window;
  1303. }
  1304. /**
  1305. * Gets relative location of a theme resource.
  1306. *
  1307. * @param theme
  1308. * the Theme name.
  1309. * @param resource
  1310. * the Theme resource.
  1311. * @return External URI specifying the resource
  1312. */
  1313. public String getResourceLocation(String theme, ThemeResource resource) {
  1314. if (resourcePath == null) {
  1315. return resource.getResourceId();
  1316. }
  1317. return resourcePath + theme + "/" + resource.getResourceId();
  1318. }
  1319. /**
  1320. * Checks if web adapter is in debug mode. Extra output is generated to log
  1321. * when debug mode is enabled.
  1322. *
  1323. * @param parameters
  1324. * @return <code>true</code> if the web adapter is in debug mode. otherwise
  1325. * <code>false</code>.
  1326. */
  1327. public boolean isDebugMode(Map parameters) {
  1328. if (parameters != null) {
  1329. final Object[] debug = (Object[]) parameters.get("debug");
  1330. if (debug != null && !"false".equals(debug[0].toString())
  1331. && !"false".equals(debugMode)) {
  1332. return true;
  1333. }
  1334. }
  1335. return "true".equals(debugMode);
  1336. }
  1337. /**
  1338. * Implementation of ParameterHandler.ErrorEvent interface.
  1339. */
  1340. public class ParameterHandlerErrorImpl implements
  1341. ParameterHandler.ErrorEvent {
  1342. private ParameterHandler owner;
  1343. private Throwable throwable;
  1344. /**
  1345. * Gets the contained throwable.
  1346. *
  1347. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1348. */
  1349. public Throwable getThrowable() {
  1350. return throwable;
  1351. }
  1352. /**
  1353. * Gets the source ParameterHandler.
  1354. *
  1355. * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
  1356. */
  1357. public ParameterHandler getParameterHandler() {
  1358. return owner;
  1359. }
  1360. }
  1361. /**
  1362. * Implementation of URIHandler.ErrorEvent interface.
  1363. */
  1364. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
  1365. private final URIHandler owner;
  1366. private final Throwable throwable;
  1367. /**
  1368. *
  1369. * @param owner
  1370. * @param throwable
  1371. */
  1372. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  1373. this.owner = owner;
  1374. this.throwable = throwable;
  1375. }
  1376. /**
  1377. * Gets the contained throwable.
  1378. *
  1379. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1380. */
  1381. public Throwable getThrowable() {
  1382. return throwable;
  1383. }
  1384. /**
  1385. * Gets the source URIHandler.
  1386. *
  1387. * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
  1388. */
  1389. public URIHandler getURIHandler() {
  1390. return owner;
  1391. }
  1392. }
  1393. /**
  1394. * Gets communication manager for an application.
  1395. *
  1396. * If this application has not been running before, new manager is created.
  1397. *
  1398. * @param application
  1399. * @return CommunicationManager
  1400. */
  1401. private CommunicationManager getApplicationManager(Application application) {
  1402. CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap
  1403. .get(application);
  1404. if (mgr == null) {
  1405. // Creates new manager
  1406. mgr = new CommunicationManager(application);
  1407. applicationToAjaxAppMgrMap.put(application, mgr);
  1408. }
  1409. return mgr;
  1410. }
  1411. /**
  1412. * Gets resource path using different implementations. Required to
  1413. * supporting different servlet container implementations (application
  1414. * servers).
  1415. *
  1416. * @param servletContext
  1417. * @param path
  1418. * the resource path.
  1419. * @return the resource path.
  1420. */
  1421. protected static String getResourcePath(ServletContext servletContext,
  1422. String path) {
  1423. String resultPath = null;
  1424. resultPath = servletContext.getRealPath(path);
  1425. if (resultPath != null) {
  1426. return resultPath;
  1427. } else {
  1428. try {
  1429. final URL url = servletContext.getResource(path);
  1430. resultPath = url.getFile();
  1431. } catch (final Exception e) {
  1432. // FIXME: Handle exception
  1433. e.printStackTrace();
  1434. }
  1435. }
  1436. return resultPath;
  1437. }
  1438. }