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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642
  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. System.err.println("Could not find specified class loader: "
  205. + classLoaderName);
  206. throw new ServletException(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
  280. * the servlet.
  281. * @param response
  282. * the object that contains the response the servlet returns
  283. * to 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. Application.SystemMessages ci = getSystemMessages();
  437. if (!UIDLrequest) {
  438. // 'plain' http req - e.g. browser reload;
  439. // just go ahead redirect the browser
  440. response.sendRedirect(ci.getSessionExpiredURL());
  441. } else {
  442. // send uidl redirect
  443. criticalNotification(request, response, ci
  444. .getSessionExpiredCaption(), ci
  445. .getSessionExpiredMessage(), ci.getSessionExpiredURL());
  446. }
  447. } catch (final Throwable e) {
  448. e.printStackTrace();
  449. // if this was an UIDL request, response UIDL back to client
  450. if (UIDLrequest) {
  451. Application.SystemMessages ci = getSystemMessages();
  452. criticalNotification(request, response, ci
  453. .getInternalErrorCaption(), ci
  454. .getInternalErrorMessage(), ci.getInternalErrorURL());
  455. } else {
  456. // Re-throw other exceptions
  457. throw new ServletException(e);
  458. }
  459. } finally {
  460. // Notifies transaction end
  461. if (application != null) {
  462. ((WebApplicationContext) application.getContext())
  463. .endTransaction(application, request);
  464. }
  465. }
  466. }
  467. /** Get system messages from the current application class */
  468. private SystemMessages getSystemMessages() {
  469. try {
  470. Class appCls = applicationClass;
  471. if (isApplicationRunnerServlet) {
  472. appCls = getClass().getClassLoader().loadClass(
  473. applicationRunnerClassname);
  474. }
  475. Method m = appCls.getMethod("getSystemMessages", null);
  476. return (Application.SystemMessages) m.invoke(null, null);
  477. } catch (ClassNotFoundException e) {
  478. // This should never happen
  479. e.printStackTrace();
  480. } catch (SecurityException e) {
  481. e.printStackTrace();
  482. System.out
  483. .print("Error: getSystemMessage() should be static public");
  484. } catch (NoSuchMethodException e) {
  485. // This is completely ok and should be silently ignored
  486. } catch (IllegalArgumentException e) {
  487. // This should never happen
  488. e.printStackTrace();
  489. } catch (IllegalAccessException e) {
  490. e.printStackTrace();
  491. System.out
  492. .print("Error: getSystemMessage() should be static public");
  493. } catch (InvocationTargetException e) {
  494. // This should never happen
  495. e.printStackTrace();
  496. }
  497. return Application.getSystemMessages();
  498. }
  499. /**
  500. * Serve resources in ITMILL directory if requested.
  501. *
  502. * @param request
  503. * @param response
  504. * @throws IOException
  505. */
  506. private void serveStaticResourcesInITMILL(String filename,
  507. HttpServletResponse response) throws IOException {
  508. final ServletContext sc = getServletContext();
  509. InputStream is = sc.getResourceAsStream(filename);
  510. if (is == null) {
  511. // try if requested file is found from classloader
  512. try {
  513. // strip leading "/" otherwise stream from JAR wont work
  514. filename = filename.substring(1);
  515. is = classLoader.getResourceAsStream(filename);
  516. } catch (final Exception e) {
  517. e.printStackTrace();
  518. }
  519. if (is == null) {
  520. // cannot serve requested file
  521. System.err
  522. .println("Requested resource ["
  523. + filename
  524. + "] not found from filesystem or through class loader."
  525. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/ITMILL folder.");
  526. response.setStatus(404);
  527. return;
  528. }
  529. }
  530. final String mimetype = sc.getMimeType(filename);
  531. if (mimetype != null) {
  532. response.setContentType(mimetype);
  533. }
  534. final OutputStream os = response.getOutputStream();
  535. final byte buffer[] = new byte[20000];
  536. int bytes;
  537. while ((bytes = is.read(buffer)) >= 0) {
  538. os.write(buffer, 0, bytes);
  539. }
  540. }
  541. /**
  542. * Send notification to client's application. Used to notify client of
  543. * critical errors and session expiration due to long inactivity. Server has
  544. * no knowledge of what application client refers to.
  545. *
  546. * @param request
  547. * the HTTP request instance.
  548. * @param response
  549. * the HTTP response to write to.
  550. * @param caption
  551. * for the notification
  552. * @param message
  553. * for the notification
  554. * @param url
  555. * url to load after message, null for current page
  556. * @throws IOException
  557. * if the writing failed due to input/output error.
  558. */
  559. void criticalNotification(HttpServletRequest request,
  560. HttpServletResponse response, String caption, String message,
  561. String url) throws IOException {
  562. // clients JS app is still running, but server application either
  563. // no longer exists or it might fail to perform reasonably.
  564. // send a notification to client's application and link how
  565. // to "restart" application.
  566. if (caption != null) {
  567. caption = "\"" + caption + "\"";
  568. }
  569. if (message != null) {
  570. message = "\"" + message + "\"";
  571. }
  572. if (url != null) {
  573. url = "\"" + url + "\"";
  574. }
  575. // Set the response type
  576. response.setContentType("application/json; charset=UTF-8");
  577. final ServletOutputStream out = response.getOutputStream();
  578. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  579. new OutputStreamWriter(out, "UTF-8")));
  580. outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
  581. + "\"appError\": {" + "\"caption\":" + caption + ","
  582. + "\"message\" : " + message + "," + "\"url\" : " + url
  583. + "}}, \"resources\": {}, \"locales\":[]}]");
  584. outWriter.flush();
  585. outWriter.close();
  586. out.flush();
  587. }
  588. /**
  589. * Resolve application URL and widgetset URL. Widgetset is not application
  590. * specific.
  591. *
  592. * @param request
  593. * @return string array consisting of application url first and then
  594. * widgetset url.
  595. */
  596. private String[] getAppAndWidgetUrl(HttpServletRequest request) {
  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. try {
  611. urlParts = getApplicationUrl(request).toString().split("\\/");
  612. appUrl = "";
  613. widgetsetUrl = "";
  614. // if context is specified add it to widgetsetUrl
  615. String ctxPath = request.getContextPath();
  616. if (ctxPath.length() == 0
  617. && request
  618. .getAttribute("javax.servlet.include.context_path") != null) {
  619. // include request (e.g portlet), get contex path from
  620. // attribute
  621. ctxPath = (String) request
  622. .getAttribute("javax.servlet.include.context_path");
  623. }
  624. if (urlParts.length > 3
  625. && urlParts[3].equals(ctxPath.replaceAll("\\/", ""))) {
  626. widgetsetUrl += "/" + urlParts[3];
  627. }
  628. for (int i = 3; i < urlParts.length; i++) {
  629. appUrl += "/" + urlParts[i];
  630. }
  631. if (appUrl.endsWith("/")) {
  632. appUrl = appUrl.substring(0, appUrl.length() - 1);
  633. }
  634. } catch (final MalformedURLException e) {
  635. e.printStackTrace();
  636. }
  637. }
  638. return new String[] { appUrl, widgetsetUrl };
  639. }
  640. /**
  641. *
  642. * @param request
  643. * the HTTP request.
  644. * @param response
  645. * the HTTP response to write to.
  646. * @param out
  647. * @param unhandledParameters
  648. * @param window
  649. * @param terminalType
  650. * @param theme
  651. * @throws IOException
  652. * if the writing failed due to input/output error.
  653. * @throws MalformedURLException
  654. * if the application is denied access the persistent data
  655. * store represented by the given URL.
  656. */
  657. private void writeAjaxPage(HttpServletRequest request,
  658. HttpServletResponse response, Window window, String themeName,
  659. Application application) throws IOException, MalformedURLException {
  660. // e.g portlets only want a html fragment
  661. boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null);
  662. if (fragment) {
  663. request.setAttribute(Application.class.getName(), application);
  664. }
  665. final BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
  666. response.getOutputStream()));
  667. final String pathInfo = request.getPathInfo() == null ? "/" : request
  668. .getPathInfo();
  669. String title = ((window == null || window.getCaption() == null) ? "IT Mill Toolkit 5"
  670. : window.getCaption());
  671. String widgetset = null;
  672. // request widgetset takes precedence (e.g portlet include)
  673. Object reqParam = request.getAttribute(REQUEST_WIDGETSET);
  674. try {
  675. widgetset = (String) reqParam;
  676. } catch (Exception e) {
  677. System.err.println("Warning: request param " + REQUEST_WIDGETSET
  678. + " could not be used (not a String?)" + e);
  679. }
  680. if (widgetset == null) {
  681. widgetset = applicationProperties.getProperty(PARAMETER_WIDGETSET);
  682. }
  683. if (widgetset == null) {
  684. widgetset = DEFAULT_WIDGETSET;
  685. }
  686. final String[] urls = getAppAndWidgetUrl(request);
  687. final String appUrl = urls[0];
  688. final String widgetsetUrl = urls[1];
  689. final String staticFilePath = getApplicationOrSystemProperty(
  690. PARAMETER_ITMILL_RESOURCES, widgetsetUrl);
  691. // Default theme does not use theme URI
  692. String themeUri = null;
  693. if (themeName != null) {
  694. // Using custom theme
  695. themeUri = staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName;
  696. }
  697. boolean testingApplication = testingToolsActive
  698. && request.getParameter("TT") != null;
  699. if (!fragment) {
  700. // Window renders are not cacheable
  701. response.setCharacterEncoding("utf-8");
  702. response.setHeader("Cache-Control", "no-cache");
  703. response.setHeader("Pragma", "no-cache");
  704. response.setDateHeader("Expires", 0);
  705. response.setContentType("text/html");
  706. // write html header
  707. page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
  708. + "XHTML 1.0 Transitional//EN\" "
  709. + "\"http://www.w3.org/TR/xhtml1/"
  710. + "DTD/xhtml1-transitional.dtd\">\n");
  711. page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
  712. + ">\n<head>\n");
  713. page.write("<style type=\"text/css\">"
  714. + "html, body {height:100%;}</style>");
  715. page.write("<title>" + title + "</title>");
  716. if (testingApplication) {
  717. // TT script needs to be in head as it needs to be the first
  718. // to hook capturing event listeners
  719. writeTestingToolsScripts(page, request);
  720. }
  721. page
  722. .write("\n</head>\n<body scroll=\"auto\" class=\"i-generated-body\">\n");
  723. }
  724. String appId = appUrl;
  725. if ("".equals(appUrl)) {
  726. appId = "ROOT";
  727. }
  728. appId = appId.replaceAll("[^a-zA-Z0-9]", "");
  729. if (isGecko17(request)) {
  730. // special start page for gecko 1.7 versions. Firefox 1.0 is not
  731. // supported, but the hack is make it possible to use linux and
  732. // hosted mode browser for debugging. Note that due this hack,
  733. // debugging gwt code in portals with linux will be problematic if
  734. // there are multiple toolkit portlets visible at the same time.
  735. // TODO remove this when hosted mode on linux gets newer gecko
  736. page.write("<iframe id=\"__gwt_historyFrame\" "
  737. + "style=\"width:0;height:0;border:0;overflow:"
  738. + "hidden\" src=\"javascript:false\"></iframe>\n");
  739. page.write("<script language='javascript' src='" + staticFilePath
  740. + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + "/"
  741. + widgetset + ".nocache.js'></script>\n");
  742. page.write("<script type=\"text/javascript\">\n");
  743. page.write("//<![CDATA[\n");
  744. page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
  745. + "if(!itmill) { var itmill = {}} \n"
  746. + "itmill.toolkitConfigurations = {};\n"
  747. + "itmill.themesLoaded = {}};\n");
  748. page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
  749. page.write("appUri:'" + appUrl + "', ");
  750. page.write("pathInfo: '" + pathInfo + "', ");
  751. page.write("themeUri:");
  752. page.write(themeUri != null ? "'" + themeUri + "'" : "null");
  753. page.write(", versionInfo : {toolkitVersion:\"");
  754. page.write(VERSION);
  755. page.write("\",applicationVersion:\"");
  756. page.write(application.getVersion());
  757. page.write("\"}");
  758. page.write("};\n//]]>\n</script>\n");
  759. if (themeName != null) {
  760. // Custom theme's stylesheet, load only once, in different
  761. // script
  762. // tag to be dominate styles injected by widget
  763. // set
  764. page.write("<script type=\"text/javascript\">\n");
  765. page.write("//<![CDATA[\n");
  766. page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
  767. page
  768. .write("var stylesheet = document.createElement('link');\n");
  769. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  770. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  771. page.write("stylesheet.setAttribute('href', '" + themeUri
  772. + "/styles.css');\n");
  773. page
  774. .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  775. page.write("itmill.themesLoaded['" + themeName
  776. + "'] = true;\n}\n");
  777. page.write("//]]>\n</script>\n");
  778. }
  779. } else {
  780. page.write("<script type=\"text/javascript\">\n");
  781. page.write("//<![CDATA[\n");
  782. page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
  783. + "if(!itmill) { var itmill = {}} \n"
  784. + "itmill.toolkitConfigurations = {};\n"
  785. + "itmill.themesLoaded = {};\n");
  786. page.write("document.write('<iframe id=\"__gwt_historyFrame\" "
  787. + "style=\"width:0;height:0;border:0;overflow:"
  788. + "hidden\" src=\"javascript:false\"></iframe>');\n");
  789. page.write("document.write(\"<script language='javascript' src='"
  790. + staticFilePath + "/" + WIDGETSET_DIRECTORY_PATH
  791. + widgetset + "/" + widgetset
  792. + ".nocache.js'><\\/script>\");\n}\n");
  793. page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
  794. page.write("appUri:'" + appUrl + "', ");
  795. page.write("pathInfo: '" + pathInfo + "', ");
  796. page.write("themeUri:");
  797. page.write(themeUri != null ? "'" + themeUri + "'" : "null");
  798. page.write(", versionInfo : {toolkitVersion:\"");
  799. page.write(VERSION);
  800. page.write("\",applicationVersion:\"");
  801. page.write(application.getVersion());
  802. page.write("\"}");
  803. page.write("};\n//]]>\n</script>\n");
  804. if (themeName != null) {
  805. // Custom theme's stylesheet, load only once, in different
  806. // script
  807. // tag to be dominate styles injected by widget
  808. // set
  809. page.write("<script type=\"text/javascript\">\n");
  810. page.write("//<![CDATA[\n");
  811. page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
  812. page
  813. .write("var stylesheet = document.createElement('link');\n");
  814. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  815. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  816. page.write("stylesheet.setAttribute('href', '" + themeUri
  817. + "/styles.css');\n");
  818. page
  819. .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  820. page.write("itmill.themesLoaded['" + themeName
  821. + "'] = true;\n}\n");
  822. page.write("//]]>\n</script>\n");
  823. }
  824. }
  825. String style = null;
  826. reqParam = request.getAttribute(REQUEST_APPSTYLE);
  827. if (reqParam != null) {
  828. style = "style=\"" + reqParam + "\"";
  829. }
  830. page.write("<div id=\"" + appId + "\" class=\"i-app\" "
  831. + (style != null ? style : "") + "></div>\n");
  832. if (!fragment) {
  833. page.write("</body>\n</html>\n");
  834. }
  835. page.close();
  836. }
  837. private boolean isGecko17(HttpServletRequest request) {
  838. final WebBrowser browser = WebApplicationContext.getApplicationContext(
  839. request.getSession()).getBrowser();
  840. if (browser != null && browser.getBrowserApplication() != null) {
  841. if (browser.getBrowserApplication().indexOf("rv:1.7.") > 0
  842. && browser.getBrowserApplication().indexOf("Gecko") > 0) {
  843. return true;
  844. }
  845. }
  846. return false;
  847. }
  848. private void writeTestingToolsScripts(Writer page,
  849. HttpServletRequest request) throws IOException {
  850. // Testing Tools script and CSS files are served from Testing Tools
  851. // Server
  852. String ext = getTestingToolsUri(request);
  853. ext = ext.substring(0, ext.lastIndexOf('/'));
  854. page.write("<script src=\"" + ext + "/ext/TT.js"
  855. + "\" type=\"text/javascript\"></script>\n");
  856. page.write("<link rel=\"stylesheet\" href=\"" + ext + "/ext/TT.css"
  857. + "\" type=\"text/css\" />\n");
  858. }
  859. private String getTestingToolsUri(HttpServletRequest request) {
  860. if (testingToolsServerUri == null) {
  861. // Default behavior is that Testing Tools Server application exists
  862. // on same host as current application does in port 8099.
  863. testingToolsServerUri = "http" + "://" + request.getServerName()
  864. + ":8099" + "/TestingToolsServer";
  865. }
  866. return testingToolsServerUri;
  867. }
  868. /**
  869. * Handles the requested URI. An application can add handlers to do special
  870. * processing, when a certain URI is requested. The handlers are invoked
  871. * before any windows URIs are processed and if a DownloadStream is returned
  872. * it is sent to the client.
  873. *
  874. * @param application
  875. * the Application owning the URI.
  876. * @param request
  877. * the HTTP request instance.
  878. * @param response
  879. * the HTTP response to write to.
  880. * @return boolean <code>true</code> if the request was handled and
  881. * further processing should be suppressed, <code>false</code>
  882. * otherwise.
  883. * @see com.itmill.toolkit.terminal.URIHandler
  884. */
  885. private DownloadStream handleURI(Application application,
  886. HttpServletRequest request, HttpServletResponse response) {
  887. String uri = request.getPathInfo();
  888. // If no URI is available
  889. if (uri == null) {
  890. uri = "";
  891. }
  892. // Removes the leading /
  893. while (uri.startsWith("/") && uri.length() > 0) {
  894. uri = uri.substring(1);
  895. }
  896. // If using application runner, remove package and class name
  897. if (isApplicationRunnerServlet) {
  898. uri = uri.replaceFirst(applicationRunnerClassname + "/", "");
  899. }
  900. // Handles the uri
  901. DownloadStream stream = null;
  902. try {
  903. stream = application.handleURI(application.getURL(), uri);
  904. } catch (final Throwable t) {
  905. application.terminalError(new URIHandlerErrorImpl(application, t));
  906. }
  907. return stream;
  908. }
  909. /**
  910. * Handles the requested URI. An application can add handlers to do special
  911. * processing, when a certain URI is requested. The handlers are invoked
  912. * before any windows URIs are processed and if a DownloadStream is returned
  913. * it is sent to the client.
  914. *
  915. * @param stream
  916. * the download stream.
  917. *
  918. * @param request
  919. * the HTTP request instance.
  920. * @param response
  921. * the HTTP response to write to.
  922. *
  923. * @see com.itmill.toolkit.terminal.URIHandler
  924. */
  925. private void handleDownload(DownloadStream stream,
  926. HttpServletRequest request, HttpServletResponse response) {
  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. try {
  968. final OutputStream out = response.getOutputStream();
  969. while ((bytesRead = data.read(buffer)) > 0) {
  970. out.write(buffer, 0, bytesRead);
  971. out.flush();
  972. }
  973. out.close();
  974. } catch (final IOException ignored) {
  975. System.err
  976. .println("Warning: ApplicationServlet.handleDownload()"
  977. + " threw IOException.");
  978. }
  979. }
  980. }
  981. /**
  982. * Handles theme resource file requests. Resources supplied with the themes
  983. * are provided by the WebAdapterServlet.
  984. *
  985. * @param request
  986. * the HTTP request.
  987. * @param response
  988. * the HTTP response.
  989. * @return boolean <code>true</code> if the request was handled and
  990. * further processing should be suppressed, <code>false</code>
  991. * otherwise.
  992. * @throws ServletException
  993. * if an exception has occurred that interferes with the
  994. * servlet's normal operation.
  995. */
  996. private boolean handleResourceRequest(HttpServletRequest request,
  997. HttpServletResponse response, String themeName)
  998. throws ServletException {
  999. // If the resource path is unassigned, initialize it
  1000. if (resourcePath == null) {
  1001. resourcePath = request.getContextPath() + request.getServletPath()
  1002. + RESOURCE_URI;
  1003. // WebSphere Application Server related fix
  1004. resourcePath = resourcePath.replaceAll("//", "/");
  1005. }
  1006. String resourceId = request.getPathInfo();
  1007. // Checks if this really is a resource request
  1008. if (resourceId == null || !resourceId.startsWith(RESOURCE_URI)) {
  1009. return false;
  1010. }
  1011. // Checks the resource type
  1012. resourceId = resourceId.substring(RESOURCE_URI.length());
  1013. InputStream data = null;
  1014. // Gets theme resources
  1015. try {
  1016. data = getServletContext().getResourceAsStream(
  1017. THEME_DIRECTORY_PATH + themeName + "/" + resourceId);
  1018. } catch (final Exception e) {
  1019. e.printStackTrace();
  1020. data = null;
  1021. }
  1022. // Writes the response
  1023. try {
  1024. if (data != null) {
  1025. response.setContentType(FileTypeResolver
  1026. .getMIMEType(resourceId));
  1027. // Use default cache time for theme resources
  1028. response.setHeader("Cache-Control", "max-age="
  1029. + DEFAULT_THEME_CACHETIME / 1000);
  1030. response.setDateHeader("Expires", System.currentTimeMillis()
  1031. + DEFAULT_THEME_CACHETIME);
  1032. response.setHeader("Pragma", "cache"); // Required to apply
  1033. // caching in some
  1034. // Tomcats
  1035. // Writes the data to client
  1036. final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  1037. int bytesRead = 0;
  1038. final OutputStream out = response.getOutputStream();
  1039. while ((bytesRead = data.read(buffer)) > 0) {
  1040. out.write(buffer, 0, bytesRead);
  1041. }
  1042. out.close();
  1043. data.close();
  1044. } else {
  1045. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  1046. }
  1047. } catch (final java.io.IOException e) {
  1048. System.err.println("Resource transfer failed: "
  1049. + request.getRequestURI() + ". (" + e.getMessage() + ")");
  1050. }
  1051. return true;
  1052. }
  1053. /**
  1054. * Gets the current application URL from request.
  1055. *
  1056. * @param request
  1057. * the HTTP request.
  1058. * @throws MalformedURLException
  1059. * if the application is denied access to the persistent
  1060. * data store represented by the given URL.
  1061. */
  1062. private URL getApplicationUrl(HttpServletRequest request)
  1063. throws MalformedURLException {
  1064. URL applicationUrl;
  1065. try {
  1066. final URL reqURL = new URL(
  1067. (request.isSecure() ? "https://" : "http://")
  1068. + request.getServerName()
  1069. + ((request.isSecure() && request.getServerPort() == 443)
  1070. || (!request.isSecure() && request
  1071. .getServerPort() == 80) ? "" : ":"
  1072. + request.getServerPort())
  1073. + request.getRequestURI());
  1074. String servletPath = "";
  1075. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  1076. // this is an include request
  1077. servletPath = request.getAttribute(
  1078. "javax.servlet.include.context_path").toString()
  1079. + request
  1080. .getAttribute("javax.servlet.include.servlet_path");
  1081. } else {
  1082. servletPath = request.getContextPath()
  1083. + request.getServletPath();
  1084. }
  1085. if (servletPath.length() == 0
  1086. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1087. servletPath = servletPath + "/";
  1088. }
  1089. applicationUrl = new URL(reqURL, servletPath);
  1090. } catch (final MalformedURLException e) {
  1091. System.err.println("Error constructing application url "
  1092. + request.getRequestURI() + " (" + e + ")");
  1093. throw e;
  1094. }
  1095. return applicationUrl;
  1096. }
  1097. /**
  1098. * Parses application runner URIs.
  1099. *
  1100. * If request URL is e.g.
  1101. * http://localhost:8080/itmill/run/com.itmill.toolkit.demo.Calc then
  1102. * <ul>
  1103. * <li>context=itmill</li>
  1104. * <li>Runner servlet=run</li>
  1105. * <li>Toolkit application=com.itmill.toolkit.demo.Calc</li>
  1106. * </ul>
  1107. *
  1108. * @param request
  1109. * @return string array containing widgetset URI, application URI and
  1110. * context, runner, application classname
  1111. */
  1112. private String[] getApplicationRunnerURIs(HttpServletRequest request) {
  1113. final String[] urlParts = request.getRequestURI().toString().split(
  1114. "\\/");
  1115. String context = null;
  1116. String runner = null;
  1117. String applicationClassname = null;
  1118. if (urlParts[1].equals(request.getContextPath().replaceAll("\\/", ""))) {
  1119. // class name comes after web context and runner application
  1120. context = urlParts[1];
  1121. runner = urlParts[2];
  1122. applicationClassname = urlParts[3];
  1123. return new String[] { "/" + context,
  1124. "/" + context + "/" + runner + "/" + applicationClassname,
  1125. context, runner, applicationClassname };
  1126. } else {
  1127. // no context
  1128. context = "";
  1129. runner = urlParts[1];
  1130. applicationClassname = urlParts[2];
  1131. return new String[] { "/",
  1132. "/" + runner + "/" + applicationClassname, context, runner,
  1133. applicationClassname };
  1134. }
  1135. }
  1136. /**
  1137. * Gets the existing application for given request. Looks for application
  1138. * instance for given request based on the requested URL.
  1139. *
  1140. * @param request
  1141. * the HTTP request.
  1142. * @param response
  1143. * @return Application instance, or null if the URL does not map to valid
  1144. * application.
  1145. * @throws MalformedURLException
  1146. * if the application is denied access to the persistent
  1147. * data store represented by the given URL.
  1148. * @throws SAXException
  1149. * @throws IllegalAccessException
  1150. * @throws InstantiationException
  1151. */
  1152. private Application getExistingApplication(HttpServletRequest request,
  1153. HttpServletResponse response) throws MalformedURLException,
  1154. SAXException, IllegalAccessException, InstantiationException {
  1155. // Ensures that the session is still valid
  1156. final HttpSession session = request.getSession(true);
  1157. // Gets application list for the session.
  1158. final Collection applications = WebApplicationContext
  1159. .getApplicationContext(session).getApplications();
  1160. // Search for the application (using the application URI) from the list
  1161. for (final Iterator i = applications.iterator(); i.hasNext();) {
  1162. final Application a = (Application) i.next();
  1163. final String aPath = a.getURL().getPath();
  1164. String servletPath = "";
  1165. if (isApplicationRunnerServlet) {
  1166. final String[] URIparts = getApplicationRunnerURIs(request);
  1167. servletPath = URIparts[1] + "/";
  1168. } else {
  1169. servletPath = request.getContextPath()
  1170. + request.getServletPath();
  1171. if (servletPath.length() < aPath.length()) {
  1172. servletPath += "/";
  1173. }
  1174. }
  1175. if (servletPath.equals(aPath)) {
  1176. // Found a running application
  1177. if (a.isRunning()) {
  1178. return a;
  1179. }
  1180. // Application has stopped, so remove it before creating a new
  1181. // application
  1182. WebApplicationContext.getApplicationContext(session)
  1183. .removeApplication(a);
  1184. break;
  1185. }
  1186. }
  1187. // Existing application not found
  1188. return null;
  1189. }
  1190. /**
  1191. * Creates new application for given request.
  1192. *
  1193. * @param request
  1194. * the HTTP request.
  1195. * @param response
  1196. * @return Application instance, or null if the URL does not map to valid
  1197. * application.
  1198. * @throws MalformedURLException
  1199. * if the application is denied access to the persistent
  1200. * data store represented by the given URL.
  1201. * @throws SAXException
  1202. * @throws IllegalAccessException
  1203. * @throws InstantiationException
  1204. */
  1205. private Application getNewApplication(HttpServletRequest request,
  1206. HttpServletResponse response) throws MalformedURLException,
  1207. SAXException, IllegalAccessException, InstantiationException {
  1208. // Create application
  1209. final WebApplicationContext context = WebApplicationContext
  1210. .getApplicationContext(request.getSession());
  1211. final URL applicationUrl;
  1212. if (isApplicationRunnerServlet) {
  1213. final String[] URIparts = getApplicationRunnerURIs(request);
  1214. final String applicationClassname = URIparts[4];
  1215. applicationUrl = new URL(getApplicationUrl(request).toString()
  1216. + applicationClassname + "/");
  1217. try {
  1218. applicationClass = classLoader.loadClass(applicationClassname);
  1219. } catch (final ClassNotFoundException e) {
  1220. throw new InstantiationException(
  1221. "Failed to load application class: "
  1222. + applicationClassname);
  1223. }
  1224. } else {
  1225. applicationUrl = getApplicationUrl(request);
  1226. }
  1227. // Creates new application and start it
  1228. try {
  1229. final Application application = (Application) applicationClass
  1230. .newInstance();
  1231. context.addApplication(application);
  1232. // Sets initial locale from the request
  1233. application.setLocale(request.getLocale());
  1234. // Starts application
  1235. application.start(applicationUrl, applicationProperties, context);
  1236. return application;
  1237. } catch (final IllegalAccessException e) {
  1238. System.err.println("Illegal access to application class "
  1239. + applicationClass.getName());
  1240. throw e;
  1241. } catch (final InstantiationException e) {
  1242. System.err.println("Failed to instantiate application class: "
  1243. + applicationClass.getName());
  1244. throw e;
  1245. }
  1246. }
  1247. /**
  1248. * Ends the application.
  1249. *
  1250. * @param request
  1251. * the HTTP request.
  1252. * @param response
  1253. * the HTTP response to write to.
  1254. * @param application
  1255. * the application to end.
  1256. * @throws IOException
  1257. * if the writing failed due to input/output error.
  1258. */
  1259. private void endApplication(HttpServletRequest request,
  1260. HttpServletResponse response, Application application)
  1261. throws IOException {
  1262. String logoutUrl = application.getLogoutURL();
  1263. if (logoutUrl == null) {
  1264. logoutUrl = application.getURL().toString();
  1265. }
  1266. final HttpSession session = request.getSession();
  1267. if (session != null) {
  1268. WebApplicationContext.getApplicationContext(session)
  1269. .removeApplication(application);
  1270. }
  1271. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  1272. }
  1273. /**
  1274. * Gets the existing application or create a new one. Get a window within an
  1275. * application based on the requested URI.
  1276. *
  1277. * @param request
  1278. * the HTTP Request.
  1279. * @param application
  1280. * the Application to query for window.
  1281. * @return Window matching the given URI or null if not found.
  1282. * @throws ServletException
  1283. * if an exception has occurred that interferes with the
  1284. * servlet's normal operation.
  1285. */
  1286. private Window getApplicationWindow(HttpServletRequest request,
  1287. Application application) throws ServletException {
  1288. Window window = null;
  1289. // Finds the window where the request is handled
  1290. String path = request.getPathInfo();
  1291. // Main window as the URI is empty
  1292. if (path == null || path.length() == 0 || path.equals("/")) {
  1293. window = application.getMainWindow();
  1294. } else {
  1295. String windowName = null;
  1296. if (path.charAt(0) == '/') {
  1297. path = path.substring(1);
  1298. }
  1299. final int index = path.indexOf('/');
  1300. if (index < 0) {
  1301. windowName = path;
  1302. path = "";
  1303. } else {
  1304. windowName = path.substring(0, index);
  1305. path = path.substring(index + 1);
  1306. }
  1307. window = application.getWindow(windowName);
  1308. if (window == null) {
  1309. // By default, we use main window
  1310. window = application.getMainWindow();
  1311. } else if (!window.isVisible()) {
  1312. // Implicitly painting without actually invoking paint()
  1313. window.requestRepaintRequests();
  1314. // If the window is invisible send a blank page
  1315. return null;
  1316. }
  1317. }
  1318. return window;
  1319. }
  1320. /**
  1321. * Gets relative location of a theme resource.
  1322. *
  1323. * @param theme
  1324. * the Theme name.
  1325. * @param resource
  1326. * the Theme resource.
  1327. * @return External URI specifying the resource
  1328. */
  1329. public String getResourceLocation(String theme, ThemeResource resource) {
  1330. if (resourcePath == null) {
  1331. return resource.getResourceId();
  1332. }
  1333. return resourcePath + theme + "/" + resource.getResourceId();
  1334. }
  1335. /**
  1336. * Checks if web adapter is in debug mode. Extra output is generated to log
  1337. * when debug mode is enabled.
  1338. *
  1339. * @param parameters
  1340. * @return <code>true</code> if the web adapter is in debug mode.
  1341. * otherwise <code>false</code>.
  1342. */
  1343. public boolean isDebugMode(Map parameters) {
  1344. if (parameters != null) {
  1345. final Object[] debug = (Object[]) parameters.get("debug");
  1346. if (debug != null && !"false".equals(debug[0].toString())
  1347. && !"false".equals(debugMode)) {
  1348. return true;
  1349. }
  1350. }
  1351. return "true".equals(debugMode);
  1352. }
  1353. /**
  1354. * Implementation of ParameterHandler.ErrorEvent interface.
  1355. */
  1356. public class ParameterHandlerErrorImpl implements
  1357. ParameterHandler.ErrorEvent {
  1358. private ParameterHandler owner;
  1359. private Throwable throwable;
  1360. /**
  1361. * Gets the contained throwable.
  1362. *
  1363. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1364. */
  1365. public Throwable getThrowable() {
  1366. return throwable;
  1367. }
  1368. /**
  1369. * Gets the source ParameterHandler.
  1370. *
  1371. * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
  1372. */
  1373. public ParameterHandler getParameterHandler() {
  1374. return owner;
  1375. }
  1376. }
  1377. /**
  1378. * Implementation of URIHandler.ErrorEvent interface.
  1379. */
  1380. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
  1381. private final URIHandler owner;
  1382. private final Throwable throwable;
  1383. /**
  1384. *
  1385. * @param owner
  1386. * @param throwable
  1387. */
  1388. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  1389. this.owner = owner;
  1390. this.throwable = throwable;
  1391. }
  1392. /**
  1393. * Gets the contained throwable.
  1394. *
  1395. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  1396. */
  1397. public Throwable getThrowable() {
  1398. return throwable;
  1399. }
  1400. /**
  1401. * Gets the source URIHandler.
  1402. *
  1403. * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
  1404. */
  1405. public URIHandler getURIHandler() {
  1406. return owner;
  1407. }
  1408. }
  1409. /**
  1410. * Gets communication manager for an application.
  1411. *
  1412. * If this application has not been running before, new manager is created.
  1413. *
  1414. * @param application
  1415. * @return CommunicationManager
  1416. */
  1417. private CommunicationManager getApplicationManager(Application application) {
  1418. CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap
  1419. .get(application);
  1420. if (mgr == null) {
  1421. // Creates new manager
  1422. mgr = new CommunicationManager(application);
  1423. applicationToAjaxAppMgrMap.put(application, mgr);
  1424. }
  1425. return mgr;
  1426. }
  1427. /**
  1428. * Gets resource path using different implementations. Required to
  1429. * supporting different servlet container implementations (application
  1430. * servers).
  1431. *
  1432. * @param servletContext
  1433. * @param path
  1434. * the resource path.
  1435. * @return the resource path.
  1436. */
  1437. protected static String getResourcePath(ServletContext servletContext,
  1438. String path) {
  1439. String resultPath = null;
  1440. resultPath = servletContext.getRealPath(path);
  1441. if (resultPath != null) {
  1442. return resultPath;
  1443. } else {
  1444. try {
  1445. final URL url = servletContext.getResource(path);
  1446. resultPath = url.getFile();
  1447. } catch (final Exception e) {
  1448. e.printStackTrace();
  1449. }
  1450. }
  1451. return resultPath;
  1452. }
  1453. }