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.

AbstractApplicationServlet.java 81KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.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.Serializable;
  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.security.GeneralSecurityException;
  18. import java.util.Collection;
  19. import java.util.Date;
  20. import java.util.Enumeration;
  21. import java.util.Iterator;
  22. import java.util.Locale;
  23. import java.util.Map;
  24. import java.util.Properties;
  25. import javax.servlet.ServletContext;
  26. import javax.servlet.ServletException;
  27. import javax.servlet.ServletOutputStream;
  28. import javax.servlet.http.HttpServlet;
  29. import javax.servlet.http.HttpServletRequest;
  30. import javax.servlet.http.HttpServletResponse;
  31. import javax.servlet.http.HttpSession;
  32. import com.vaadin.Application;
  33. import com.vaadin.Application.SystemMessages;
  34. import com.vaadin.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
  35. import com.vaadin.terminal.DownloadStream;
  36. import com.vaadin.terminal.ParameterHandler;
  37. import com.vaadin.terminal.Terminal;
  38. import com.vaadin.terminal.ThemeResource;
  39. import com.vaadin.terminal.URIHandler;
  40. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  41. import com.vaadin.ui.Window;
  42. /**
  43. * Abstract implementation of the ApplicationServlet which handles all
  44. * communication between the client and the server.
  45. *
  46. * It is possible to extend this class to provide own functionality but in most
  47. * cases this is unnecessary.
  48. *
  49. *
  50. * @author IT Mill Ltd.
  51. * @version
  52. * @VERSION@
  53. * @since 6.0
  54. */
  55. @SuppressWarnings("serial")
  56. public abstract class AbstractApplicationServlet extends HttpServlet implements
  57. Constants {
  58. // TODO Move some (all?) of the constants to a separate interface (shared
  59. // with portlet)
  60. /**
  61. * The version number of this release. For example "6.2.0". Always in the
  62. * format "major.minor.revision[.build]". The build part is optional. All of
  63. * major, minor, revision must be integers.
  64. */
  65. public static final String VERSION;
  66. /**
  67. * Major version number. For example 6 in 6.2.0.
  68. */
  69. public static final int VERSION_MAJOR;
  70. /**
  71. * Minor version number. For example 2 in 6.2.0.
  72. */
  73. public static final int VERSION_MINOR;
  74. /**
  75. * Version revision number. For example 0 in 6.2.0.
  76. */
  77. public static final int VERSION_REVISION;
  78. /**
  79. * Build identifier. For example "nightly-20091123-c9963" in
  80. * 6.2.0.nightly-20091123-c9963.
  81. */
  82. public static final String VERSION_BUILD;
  83. /* Initialize version numbers from string replaced by build-script. */
  84. static {
  85. if ("@VERSION@".equals("@" + "VERSION" + "@")) {
  86. VERSION = "9.9.9.INTERNAL-DEBUG-BUILD";
  87. } else {
  88. VERSION = "@VERSION@";
  89. }
  90. final String[] digits = VERSION.split("\\.", 4);
  91. VERSION_MAJOR = Integer.parseInt(digits[0]);
  92. VERSION_MINOR = Integer.parseInt(digits[1]);
  93. VERSION_REVISION = Integer.parseInt(digits[2]);
  94. if (digits.length == 4) {
  95. VERSION_BUILD = digits[3];
  96. } else {
  97. VERSION_BUILD = "";
  98. }
  99. }
  100. /**
  101. * If the attribute is present in the request, a html fragment will be
  102. * written instead of a whole page.
  103. *
  104. * It is set to "true" by the {@link ApplicationPortlet} (Portlet 1.0) and
  105. * read by {@link AbstractApplicationServlet}.
  106. */
  107. public static final String REQUEST_FRAGMENT = ApplicationServlet.class
  108. .getName() + ".fragment";
  109. /**
  110. * This request attribute forces widgetsets to be loaded from under the
  111. * specified base path; e.g shared widgetset for all portlets in a portal.
  112. *
  113. * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
  114. * {@link Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH} and read by
  115. * {@link AbstractApplicationServlet}.
  116. */
  117. public static final String REQUEST_VAADIN_STATIC_FILE_PATH = ApplicationServlet.class
  118. .getName() + ".widgetsetPath";
  119. /**
  120. * This request attribute forces widgetset used; e.g for portlets that can
  121. * not have different widgetsets.
  122. *
  123. * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
  124. * {@link ApplicationPortlet.PORTLET_PARAMETER_WIDGETSET} and read by
  125. * {@link AbstractApplicationServlet}.
  126. */
  127. public static final String REQUEST_WIDGETSET = ApplicationServlet.class
  128. .getName() + ".widgetset";
  129. /**
  130. * This request attribute indicates the shared widgetset (e.g. portal-wide
  131. * default widgetset).
  132. *
  133. * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
  134. * {@link Constants.PORTAL_PARAMETER_VAADIN_WIDGETSET} and read by
  135. * {@link AbstractApplicationServlet}.
  136. */
  137. public static final String REQUEST_SHARED_WIDGETSET = ApplicationServlet.class
  138. .getName() + ".sharedWidgetset";
  139. /**
  140. * If set, do not load the default theme but assume that loading it is
  141. * handled e.g. by ApplicationPortlet.
  142. *
  143. * It is set by the {@link ApplicationPortlet} (Portlet 1.0) based on
  144. * {@link Constants.PORTAL_PARAMETER_VAADIN_THEME} and read by
  145. * {@link AbstractApplicationServlet}.
  146. */
  147. public static final String REQUEST_DEFAULT_THEME = ApplicationServlet.class
  148. .getName() + ".defaultThemeUri";
  149. /**
  150. * This request attribute is used to add styles to the main element. E.g
  151. * "height:500px" generates a style="height:500px" to the main element,
  152. * useful from some embedding situations (e.g portlet include.)
  153. *
  154. * It is typically set by the {@link ApplicationPortlet} (Portlet 1.0) based
  155. * on {@link ApplicationPortlet.PORTLET_PARAMETER_STYLE} and read by
  156. * {@link AbstractApplicationServlet}.
  157. */
  158. public static final String REQUEST_APPSTYLE = ApplicationServlet.class
  159. .getName() + ".style";
  160. private Properties applicationProperties;
  161. private boolean productionMode = false;
  162. private final String resourcePath = null;
  163. private int resourceCacheTime = 3600;
  164. /**
  165. * Called by the servlet container to indicate to a servlet that the servlet
  166. * is being placed into service.
  167. *
  168. * @param servletConfig
  169. * the object containing the servlet's configuration and
  170. * initialization parameters
  171. * @throws javax.servlet.ServletException
  172. * if an exception has occurred that interferes with the
  173. * servlet's normal operation.
  174. */
  175. @SuppressWarnings("unchecked")
  176. @Override
  177. public void init(javax.servlet.ServletConfig servletConfig)
  178. throws javax.servlet.ServletException {
  179. super.init(servletConfig);
  180. // Stores the application parameters into Properties object
  181. applicationProperties = new Properties();
  182. for (final Enumeration e = servletConfig.getInitParameterNames(); e
  183. .hasMoreElements();) {
  184. final String name = (String) e.nextElement();
  185. applicationProperties.setProperty(name,
  186. servletConfig.getInitParameter(name));
  187. }
  188. // Overrides with server.xml parameters
  189. final ServletContext context = servletConfig.getServletContext();
  190. for (final Enumeration e = context.getInitParameterNames(); e
  191. .hasMoreElements();) {
  192. final String name = (String) e.nextElement();
  193. applicationProperties.setProperty(name,
  194. context.getInitParameter(name));
  195. }
  196. checkProductionMode();
  197. checkCrossSiteProtection();
  198. checkResourceCacheTime();
  199. }
  200. private void checkCrossSiteProtection() {
  201. if (getApplicationOrSystemProperty(
  202. SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, "false").equals(
  203. "true")) {
  204. /*
  205. * Print an information/warning message about running with xsrf
  206. * protection disabled
  207. */
  208. System.err.println(WARNING_XSRF_PROTECTION_DISABLED);
  209. }
  210. }
  211. private void checkProductionMode() {
  212. // Check if the application is in production mode.
  213. // We are in production mode if Debug=false or productionMode=true
  214. if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true")
  215. .equals("false")) {
  216. // "Debug=true" is the old way and should no longer be used
  217. productionMode = true;
  218. } else if (getApplicationOrSystemProperty(
  219. SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) {
  220. // "productionMode=true" is the real way to do it
  221. productionMode = true;
  222. }
  223. if (!productionMode) {
  224. /* Print an information/warning message about running in debug mode */
  225. System.err.println(NOT_PRODUCTION_MODE_INFO);
  226. }
  227. }
  228. private void checkResourceCacheTime() {
  229. // Check if the browser caching time has been set in web.xml
  230. try {
  231. String rct = getApplicationOrSystemProperty(
  232. SERVLET_PARAMETER_RESOURCE_CACHE_TIME, "3600");
  233. resourceCacheTime = Integer.parseInt(rct);
  234. } catch (NumberFormatException nfe) {
  235. // Default is 1h
  236. resourceCacheTime = 3600;
  237. System.err.println(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC);
  238. }
  239. }
  240. /**
  241. * Gets an application property value.
  242. *
  243. * @param parameterName
  244. * the Name or the parameter.
  245. * @return String value or null if not found
  246. */
  247. protected String getApplicationProperty(String parameterName) {
  248. String val = applicationProperties.getProperty(parameterName);
  249. if (val != null) {
  250. return val;
  251. }
  252. // Try lower case application properties for backward compatibility with
  253. // 3.0.2 and earlier
  254. val = applicationProperties.getProperty(parameterName.toLowerCase());
  255. return val;
  256. }
  257. /**
  258. * Gets an system property value.
  259. *
  260. * @param parameterName
  261. * the Name or the parameter.
  262. * @return String value or null if not found
  263. */
  264. protected String getSystemProperty(String parameterName) {
  265. String val = null;
  266. String pkgName;
  267. final Package pkg = getClass().getPackage();
  268. if (pkg != null) {
  269. pkgName = pkg.getName();
  270. } else {
  271. final String className = getClass().getName();
  272. pkgName = new String(className.toCharArray(), 0,
  273. className.lastIndexOf('.'));
  274. }
  275. val = System.getProperty(pkgName + "." + parameterName);
  276. if (val != null) {
  277. return val;
  278. }
  279. // Try lowercased system properties
  280. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  281. return val;
  282. }
  283. /**
  284. * Gets an application or system property value.
  285. *
  286. * @param parameterName
  287. * the Name or the parameter.
  288. * @param defaultValue
  289. * the Default to be used.
  290. * @return String value or default if not found
  291. */
  292. private String getApplicationOrSystemProperty(String parameterName,
  293. String defaultValue) {
  294. String val = null;
  295. // Try application properties
  296. val = getApplicationProperty(parameterName);
  297. if (val != null) {
  298. return val;
  299. }
  300. // Try system properties
  301. val = getSystemProperty(parameterName);
  302. if (val != null) {
  303. return val;
  304. }
  305. return defaultValue;
  306. }
  307. /**
  308. * Returns true if the servlet is running in production mode. Production
  309. * mode disables all debug facilities.
  310. *
  311. * @return true if in production mode, false if in debug mode
  312. */
  313. public boolean isProductionMode() {
  314. return productionMode;
  315. }
  316. /**
  317. * Returns the amount of milliseconds the browser should cache a file.
  318. * Default is 1 hour (3600 ms).
  319. *
  320. * @return The amount of milliseconds files are cached in the browser
  321. */
  322. public int getResourceCacheTime() {
  323. return resourceCacheTime;
  324. }
  325. /**
  326. * Receives standard HTTP requests from the public service method and
  327. * dispatches them.
  328. *
  329. * @param request
  330. * the object that contains the request the client made of the
  331. * servlet.
  332. * @param response
  333. * the object that contains the response the servlet returns to
  334. * the client.
  335. * @throws ServletException
  336. * if an input or output error occurs while the servlet is
  337. * handling the TRACE request.
  338. * @throws IOException
  339. * if the request for the TRACE cannot be handled.
  340. */
  341. @SuppressWarnings("unchecked")
  342. @Override
  343. protected void service(HttpServletRequest request,
  344. HttpServletResponse response) throws ServletException, IOException {
  345. RequestType requestType = getRequestType(request);
  346. if (!ensureCookiesEnabled(requestType, request, response)) {
  347. return;
  348. }
  349. if (requestType == RequestType.STATIC_FILE) {
  350. serveStaticResources(request, response);
  351. return;
  352. }
  353. Application application = null;
  354. boolean transactionStarted = false;
  355. boolean requestStarted = false;
  356. try {
  357. // If a duplicate "close application" URL is received for an
  358. // application that is not open, redirect to the application's main
  359. // page.
  360. // This is needed as e.g. Spring Security remembers the last
  361. // URL from the application, which is the logout URL, and repeats
  362. // it.
  363. // We can tell apart a real onunload request from a repeated one
  364. // based on the real one having content (at least the UIDL security
  365. // key).
  366. if (requestType == RequestType.UIDL
  367. && request.getParameterMap().containsKey(
  368. ApplicationConnection.PARAM_UNLOADBURST)
  369. && request.getContentLength() < 1
  370. && getExistingApplication(request, false) == null) {
  371. redirectToApplication(request, response);
  372. return;
  373. }
  374. // Find out which application this request is related to
  375. application = findApplicationInstance(request, requestType);
  376. if (application == null) {
  377. return;
  378. }
  379. /*
  380. * Get or create a WebApplicationContext and an ApplicationManager
  381. * for the session
  382. */
  383. WebApplicationContext webApplicationContext = WebApplicationContext
  384. .getApplicationContext(request.getSession());
  385. CommunicationManager applicationManager = webApplicationContext
  386. .getApplicationManager(application, this);
  387. /* Update browser information from the request */
  388. updateBrowserProperties(webApplicationContext.getBrowser(), request);
  389. /*
  390. * Call application requestStart before Application.init() is called
  391. * (bypasses the limitation in TransactionListener)
  392. */
  393. if (application instanceof HttpServletRequestListener) {
  394. ((HttpServletRequestListener) application).onRequestStart(
  395. request, response);
  396. requestStarted = true;
  397. }
  398. // Start the newly created application
  399. startApplication(request, application, webApplicationContext);
  400. /*
  401. * Transaction starts. Call transaction listeners. Transaction end
  402. * is called in the finally block below.
  403. */
  404. webApplicationContext.startTransaction(application, request);
  405. transactionStarted = true;
  406. /* Handle the request */
  407. if (requestType == RequestType.FILE_UPLOAD) {
  408. applicationManager.handleFileUpload(request, response);
  409. return;
  410. } else if (requestType == RequestType.UIDL) {
  411. // Handles AJAX UIDL requests
  412. Window window = applicationManager.getApplicationWindow(
  413. request, this, application, null);
  414. applicationManager.handleUidlRequest(request, response, this,
  415. window);
  416. return;
  417. }
  418. // Removes application if it has stopped (mayby by thread or
  419. // transactionlistener)
  420. if (!application.isRunning()) {
  421. endApplication(request, response, application);
  422. return;
  423. }
  424. // Finds the window within the application
  425. Window window = getApplicationWindow(request, applicationManager,
  426. application);
  427. if (window == null) {
  428. throw new ServletException(ERROR_NO_WINDOW_FOUND);
  429. }
  430. // Sets terminal type for the window, if not already set
  431. if (window.getTerminal() == null) {
  432. window.setTerminal(webApplicationContext.getBrowser());
  433. }
  434. // Handle parameters
  435. final Map parameters = request.getParameterMap();
  436. if (window != null && parameters != null) {
  437. window.handleParameters(parameters);
  438. }
  439. /*
  440. * Call the URI handlers and if this turns out to be a download
  441. * request, send the file to the client
  442. */
  443. if (handleURI(applicationManager, window, request, response)) {
  444. return;
  445. }
  446. // Send initial AJAX page that kickstarts a Vaadin application
  447. writeAjaxPage(request, response, window, application);
  448. } catch (final SessionExpiredException e) {
  449. // Session has expired, notify user
  450. handleServiceSessionExpired(request, response);
  451. } catch (final GeneralSecurityException e) {
  452. handleServiceSecurityException(request, response);
  453. } catch (final Throwable e) {
  454. handleServiceException(request, response, application, e);
  455. } finally {
  456. // Notifies transaction end
  457. try {
  458. if (transactionStarted) {
  459. ((WebApplicationContext) application.getContext())
  460. .endTransaction(application, request);
  461. }
  462. } finally {
  463. if (requestStarted) {
  464. ((HttpServletRequestListener) application).onRequestEnd(
  465. request, response);
  466. }
  467. }
  468. }
  469. }
  470. /**
  471. * Check that cookie support is enabled in the browser. Only checks UIDL
  472. * requests.
  473. *
  474. * @param requestType
  475. * Type of the request as returned by
  476. * {@link #getRequestType(HttpServletRequest)}
  477. * @param request
  478. * The request from the browser
  479. * @param response
  480. * The response to which an error can be written
  481. * @return false if cookies are disabled, true otherwise
  482. * @throws IOException
  483. */
  484. private boolean ensureCookiesEnabled(RequestType requestType,
  485. HttpServletRequest request, HttpServletResponse response)
  486. throws IOException {
  487. if (requestType == RequestType.UIDL && !isRepaintAll(request)) {
  488. // In all other but the first UIDL request a cookie should be
  489. // returned by the browser.
  490. // This can be removed if cookieless mode (#3228) is supported
  491. if (request.getRequestedSessionId() == null) {
  492. // User has cookies disabled
  493. criticalNotification(request, response, getSystemMessages()
  494. .getCookiesDisabledCaption(), getSystemMessages()
  495. .getCookiesDisabledMessage(), null, getSystemMessages()
  496. .getCookiesDisabledURL());
  497. return false;
  498. }
  499. }
  500. return true;
  501. }
  502. private void updateBrowserProperties(WebBrowser browser,
  503. HttpServletRequest request) {
  504. browser.updateBrowserProperties(request.getLocale(),
  505. request.getRemoteAddr(), request.isSecure(),
  506. request.getHeader("user-agent"), request.getParameter("sw"),
  507. request.getParameter("sh"));
  508. }
  509. protected ClassLoader getClassLoader() throws ServletException {
  510. // Gets custom class loader
  511. final String classLoaderName = getApplicationOrSystemProperty(
  512. "ClassLoader", null);
  513. ClassLoader classLoader;
  514. if (classLoaderName == null) {
  515. classLoader = getClass().getClassLoader();
  516. } else {
  517. try {
  518. final Class<?> classLoaderClass = getClass().getClassLoader()
  519. .loadClass(classLoaderName);
  520. final Constructor<?> c = classLoaderClass
  521. .getConstructor(new Class[] { ClassLoader.class });
  522. classLoader = (ClassLoader) c
  523. .newInstance(new Object[] { getClass().getClassLoader() });
  524. } catch (final Exception e) {
  525. throw new ServletException(
  526. "Could not find specified class loader: "
  527. + classLoaderName, e);
  528. }
  529. }
  530. return classLoader;
  531. }
  532. /**
  533. * Send a notification to client's application. Used to notify client of
  534. * critical errors, session expiration and more. Server has no knowledge of
  535. * what application client refers to.
  536. *
  537. * @param request
  538. * the HTTP request instance.
  539. * @param response
  540. * the HTTP response to write to.
  541. * @param caption
  542. * the notification caption
  543. * @param message
  544. * to notification body
  545. * @param details
  546. * a detail message to show in addition to the message. Currently
  547. * shown directly below the message but could be hidden behind a
  548. * details drop down in the future. Mainly used to give
  549. * additional information not necessarily useful to the end user.
  550. * @param url
  551. * url to load when the message is dismissed. Null will reload
  552. * the current page.
  553. * @throws IOException
  554. * if the writing failed due to input/output error.
  555. */
  556. protected final void criticalNotification(HttpServletRequest request,
  557. HttpServletResponse response, String caption, String message,
  558. String details, String url) throws IOException {
  559. if (isUIDLRequest(request)) {
  560. if (caption != null) {
  561. caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\"";
  562. }
  563. if (details != null) {
  564. if (message == null) {
  565. message = details;
  566. } else {
  567. message += "<br/><br/>" + details;
  568. }
  569. }
  570. if (message != null) {
  571. message = "\"" + JsonPaintTarget.escapeJSON(message) + "\"";
  572. }
  573. if (url != null) {
  574. url = "\"" + JsonPaintTarget.escapeJSON(url) + "\"";
  575. }
  576. String output = "for(;;);[{\"changes\":[], \"meta\" : {"
  577. + "\"appError\": {" + "\"caption\":" + caption + ","
  578. + "\"message\" : " + message + "," + "\"url\" : " + url
  579. + "}}, \"resources\": {}, \"locales\":[]}]";
  580. writeResponse(response, "application/json; charset=UTF-8", output);
  581. } else {
  582. // Create an HTML reponse with the error
  583. String output = "";
  584. if (url != null) {
  585. output += "<a href=\"" + url + "\">";
  586. }
  587. if (caption != null) {
  588. output += "<b>" + caption + "</b><br/>";
  589. }
  590. if (message != null) {
  591. output += message;
  592. output += "<br/><br/>";
  593. }
  594. if (details != null) {
  595. output += details;
  596. output += "<br/><br/>";
  597. }
  598. if (url != null) {
  599. output += "</a>";
  600. }
  601. writeResponse(response, "text/html; charset=UTF-8", output);
  602. }
  603. }
  604. /**
  605. * Writes the response in {@code output} using the contentType given in
  606. * {@code contentType} to the provided {@link HttpServletResponse}
  607. *
  608. * @param response
  609. * @param contentType
  610. * @param output
  611. * Output to write (UTF-8 encoded)
  612. * @throws IOException
  613. */
  614. private void writeResponse(HttpServletResponse response,
  615. String contentType, String output) throws IOException {
  616. response.setContentType(contentType);
  617. final ServletOutputStream out = response.getOutputStream();
  618. // Set the response type
  619. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  620. new OutputStreamWriter(out, "UTF-8")));
  621. outWriter.print(output);
  622. outWriter.flush();
  623. outWriter.close();
  624. out.flush();
  625. }
  626. /**
  627. * Returns the application instance to be used for the request. If an
  628. * existing instance is not found a new one is created or null is returned
  629. * to indicate that the application is not available.
  630. *
  631. * @param request
  632. * @param requestType
  633. * @return
  634. * @throws MalformedURLException
  635. * @throws IllegalAccessException
  636. * @throws InstantiationException
  637. * @throws ServletException
  638. * @throws SessionExpiredException
  639. */
  640. private Application findApplicationInstance(HttpServletRequest request,
  641. RequestType requestType) throws MalformedURLException,
  642. ServletException, SessionExpiredException {
  643. boolean requestCanCreateApplication = requestCanCreateApplication(
  644. request, requestType);
  645. /* Find an existing application for this request. */
  646. Application application = getExistingApplication(request,
  647. requestCanCreateApplication);
  648. if (application != null) {
  649. /*
  650. * There is an existing application. We can use this as long as the
  651. * user not specifically requested to close or restart it.
  652. */
  653. final boolean restartApplication = (request
  654. .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null);
  655. final boolean closeApplication = (request
  656. .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null);
  657. if (restartApplication) {
  658. closeApplication(application, request.getSession(false));
  659. return createApplication(request);
  660. } else if (closeApplication) {
  661. closeApplication(application, request.getSession(false));
  662. return null;
  663. } else {
  664. return application;
  665. }
  666. }
  667. // No existing application was found
  668. if (requestCanCreateApplication) {
  669. /*
  670. * If the request is such that it should create a new application if
  671. * one as not found, we do that.
  672. */
  673. return createApplication(request);
  674. } else {
  675. /*
  676. * The application was not found and a new one should not be
  677. * created. Assume the session has expired.
  678. */
  679. throw new SessionExpiredException();
  680. }
  681. }
  682. /**
  683. * Check if the request should create an application if an existing
  684. * application is not found.
  685. *
  686. * @param request
  687. * @param requestType
  688. * @return true if an application should be created, false otherwise
  689. */
  690. boolean requestCanCreateApplication(HttpServletRequest request,
  691. RequestType requestType) {
  692. if (requestType == RequestType.UIDL && isRepaintAll(request)) {
  693. /*
  694. * UIDL request contains valid repaintAll=1 event, the user probably
  695. * wants to initiate a new application through a custom index.html
  696. * without using writeAjaxPage.
  697. */
  698. return true;
  699. } else if (requestType == RequestType.OTHER) {
  700. /*
  701. * I.e URIs that are not application resources or static (theme)
  702. * files.
  703. */
  704. return true;
  705. }
  706. return false;
  707. }
  708. /**
  709. * Gets resource path using different implementations. Required to
  710. * supporting different servlet container implementations (application
  711. * servers).
  712. *
  713. * @param servletContext
  714. * @param path
  715. * the resource path.
  716. * @return the resource path.
  717. */
  718. protected static String getResourcePath(ServletContext servletContext,
  719. String path) {
  720. String resultPath = null;
  721. resultPath = servletContext.getRealPath(path);
  722. if (resultPath != null) {
  723. return resultPath;
  724. } else {
  725. try {
  726. final URL url = servletContext.getResource(path);
  727. resultPath = url.getFile();
  728. } catch (final Exception e) {
  729. // FIXME: Handle exception
  730. e.printStackTrace();
  731. }
  732. }
  733. return resultPath;
  734. }
  735. /**
  736. * Handles the requested URI. An application can add handlers to do special
  737. * processing, when a certain URI is requested. The handlers are invoked
  738. * before any windows URIs are processed and if a DownloadStream is returned
  739. * it is sent to the client.
  740. *
  741. * @param stream
  742. * the download stream.
  743. *
  744. * @param request
  745. * the HTTP request instance.
  746. * @param response
  747. * the HTTP response to write to.
  748. * @throws IOException
  749. *
  750. * @see com.vaadin.terminal.URIHandler
  751. */
  752. @SuppressWarnings("unchecked")
  753. private void handleDownload(DownloadStream stream,
  754. HttpServletRequest request, HttpServletResponse response)
  755. throws IOException {
  756. if (stream.getParameter("Location") != null) {
  757. response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
  758. response.addHeader("Location", stream.getParameter("Location"));
  759. return;
  760. }
  761. // Download from given stream
  762. final InputStream data = stream.getStream();
  763. if (data != null) {
  764. // Sets content type
  765. response.setContentType(stream.getContentType());
  766. // Sets cache headers
  767. final long cacheTime = stream.getCacheTime();
  768. if (cacheTime <= 0) {
  769. response.setHeader("Cache-Control", "no-cache");
  770. response.setHeader("Pragma", "no-cache");
  771. response.setDateHeader("Expires", 0);
  772. } else {
  773. response.setHeader("Cache-Control", "max-age=" + cacheTime
  774. / 1000);
  775. response.setDateHeader("Expires", System.currentTimeMillis()
  776. + cacheTime);
  777. response.setHeader("Pragma", "cache"); // Required to apply
  778. // caching in some
  779. // Tomcats
  780. }
  781. // Copy download stream parameters directly
  782. // to HTTP headers.
  783. final Iterator i = stream.getParameterNames();
  784. if (i != null) {
  785. while (i.hasNext()) {
  786. final String param = (String) i.next();
  787. response.setHeader(param, stream.getParameter(param));
  788. }
  789. }
  790. // suggest local filename from DownloadStream if Content-Disposition
  791. // not explicitly set
  792. String contentDispositionValue = stream
  793. .getParameter("Content-Disposition");
  794. if (contentDispositionValue == null) {
  795. contentDispositionValue = "filename=\"" + stream.getFileName()
  796. + "\"";
  797. response.setHeader("Content-Disposition",
  798. contentDispositionValue);
  799. }
  800. int bufferSize = stream.getBufferSize();
  801. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) {
  802. bufferSize = DEFAULT_BUFFER_SIZE;
  803. }
  804. final byte[] buffer = new byte[bufferSize];
  805. int bytesRead = 0;
  806. final OutputStream out = response.getOutputStream();
  807. while ((bytesRead = data.read(buffer)) > 0) {
  808. out.write(buffer, 0, bytesRead);
  809. out.flush();
  810. }
  811. out.close();
  812. data.close();
  813. }
  814. }
  815. /**
  816. * Creates a new application and registers it into WebApplicationContext
  817. * (aka session). This is not meant to be overridden. Override
  818. * getNewApplication to create the application instance in a custom way.
  819. *
  820. * @param request
  821. * @return
  822. * @throws ServletException
  823. * @throws MalformedURLException
  824. */
  825. private Application createApplication(HttpServletRequest request)
  826. throws ServletException, MalformedURLException {
  827. Application newApplication = getNewApplication(request);
  828. final WebApplicationContext context = WebApplicationContext
  829. .getApplicationContext(request.getSession());
  830. context.addApplication(newApplication);
  831. return newApplication;
  832. }
  833. private void handleServiceException(HttpServletRequest request,
  834. HttpServletResponse response, Application application, Throwable e)
  835. throws IOException, ServletException {
  836. // if this was an UIDL request, response UIDL back to client
  837. if (getRequestType(request) == RequestType.UIDL) {
  838. Application.SystemMessages ci = getSystemMessages();
  839. criticalNotification(request, response,
  840. ci.getInternalErrorCaption(), ci.getInternalErrorMessage(),
  841. null, ci.getInternalErrorURL());
  842. if (application != null) {
  843. application.getErrorHandler()
  844. .terminalError(new RequestError(e));
  845. } else {
  846. throw new ServletException(e);
  847. }
  848. } else {
  849. // Re-throw other exceptions
  850. throw new ServletException(e);
  851. }
  852. }
  853. /**
  854. * Returns the theme for given request/window
  855. *
  856. * @param request
  857. * @param window
  858. * @return
  859. */
  860. private String getThemeForWindow(HttpServletRequest request, Window window) {
  861. // Finds theme name
  862. String themeName;
  863. if (request.getParameter(URL_PARAMETER_THEME) != null) {
  864. themeName = request.getParameter(URL_PARAMETER_THEME);
  865. } else {
  866. themeName = window.getTheme();
  867. }
  868. if (themeName == null) {
  869. // no explicit theme for window defined
  870. if (request.getAttribute(REQUEST_DEFAULT_THEME) != null) {
  871. // the default theme is defined in request (by portal)
  872. themeName = (String) request
  873. .getAttribute(REQUEST_DEFAULT_THEME);
  874. } else {
  875. // using the default theme defined by Vaadin
  876. themeName = getDefaultTheme();
  877. }
  878. }
  879. return themeName;
  880. }
  881. /**
  882. * Returns the default theme. Must never return null.
  883. *
  884. * @return
  885. */
  886. public static String getDefaultTheme() {
  887. return DEFAULT_THEME_NAME;
  888. }
  889. /**
  890. * Calls URI handlers for the request. If an URI handler returns a
  891. * DownloadStream the stream is passed to the client for downloading.
  892. *
  893. * @param applicationManager
  894. * @param window
  895. * @param request
  896. * @param response
  897. * @return true if an DownloadStream was sent to the client, false otherwise
  898. * @throws IOException
  899. */
  900. protected boolean handleURI(CommunicationManager applicationManager,
  901. Window window, HttpServletRequest request,
  902. HttpServletResponse response) throws IOException {
  903. // Handles the URI
  904. DownloadStream download = applicationManager.handleURI(window, request,
  905. response, this);
  906. // A download request
  907. if (download != null) {
  908. // Client downloads an resource
  909. handleDownload(download, request, response);
  910. return true;
  911. }
  912. return false;
  913. }
  914. void handleServiceSessionExpired(HttpServletRequest request,
  915. HttpServletResponse response) throws IOException, ServletException {
  916. if (isOnUnloadRequest(request)) {
  917. /*
  918. * Request was an unload request (e.g. window close event) and the
  919. * client expects no response if it fails.
  920. */
  921. return;
  922. }
  923. try {
  924. Application.SystemMessages ci = getSystemMessages();
  925. if (getRequestType(request) != RequestType.UIDL) {
  926. // 'plain' http req - e.g. browser reload;
  927. // just go ahead redirect the browser
  928. response.sendRedirect(ci.getSessionExpiredURL());
  929. } else {
  930. /*
  931. * Invalidate session (weird to have session if we're saying
  932. * that it's expired, and worse: portal integration will fail
  933. * since the session is not created by the portal.
  934. *
  935. * Session must be invalidated before criticalNotification as it
  936. * commits the response.
  937. */
  938. request.getSession().invalidate();
  939. // send uidl redirect
  940. criticalNotification(request, response,
  941. ci.getSessionExpiredCaption(),
  942. ci.getSessionExpiredMessage(), null,
  943. ci.getSessionExpiredURL());
  944. }
  945. } catch (SystemMessageException ee) {
  946. throw new ServletException(ee);
  947. }
  948. }
  949. private void handleServiceSecurityException(HttpServletRequest request,
  950. HttpServletResponse response) throws IOException, ServletException {
  951. if (isOnUnloadRequest(request)) {
  952. /*
  953. * Request was an unload request (e.g. window close event) and the
  954. * client expects no response if it fails.
  955. */
  956. return;
  957. }
  958. try {
  959. Application.SystemMessages ci = getSystemMessages();
  960. if (getRequestType(request) != RequestType.UIDL) {
  961. // 'plain' http req - e.g. browser reload;
  962. // just go ahead redirect the browser
  963. response.sendRedirect(ci.getCommunicationErrorURL());
  964. } else {
  965. // send uidl redirect
  966. criticalNotification(request, response,
  967. ci.getCommunicationErrorCaption(),
  968. ci.getCommunicationErrorMessage(),
  969. INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL());
  970. /*
  971. * Invalidate session. Portal integration will fail otherwise
  972. * since the session is not created by the portal.
  973. */
  974. request.getSession().invalidate();
  975. }
  976. } catch (SystemMessageException ee) {
  977. throw new ServletException(ee);
  978. }
  979. log("Invalid security key received from " + request.getRemoteHost());
  980. }
  981. /**
  982. * Creates a new application for the given request.
  983. *
  984. * @param request
  985. * the HTTP request.
  986. * @return A new Application instance.
  987. * @throws ServletException
  988. */
  989. protected abstract Application getNewApplication(HttpServletRequest request)
  990. throws ServletException;
  991. /**
  992. * Starts the application if it is not already running.
  993. *
  994. * @param request
  995. * @param application
  996. * @param webApplicationContext
  997. * @throws ServletException
  998. * @throws MalformedURLException
  999. */
  1000. private void startApplication(HttpServletRequest request,
  1001. Application application, WebApplicationContext webApplicationContext)
  1002. throws ServletException, MalformedURLException {
  1003. if (!application.isRunning()) {
  1004. // Create application
  1005. final URL applicationUrl = getApplicationUrl(request);
  1006. // Initial locale comes from the request
  1007. Locale locale = request.getLocale();
  1008. application.setLocale(locale);
  1009. application.start(applicationUrl, applicationProperties,
  1010. webApplicationContext);
  1011. }
  1012. }
  1013. /**
  1014. * Check if this is a request for a static resource and, if it is, serve the
  1015. * resource to the client.
  1016. *
  1017. * @param request
  1018. * @param response
  1019. * @return true if a file was served and the request has been handled, false
  1020. * otherwise.
  1021. * @throws IOException
  1022. * @throws ServletException
  1023. */
  1024. private boolean serveStaticResources(HttpServletRequest request,
  1025. HttpServletResponse response) throws IOException, ServletException {
  1026. // FIXME What does 10 refer to?
  1027. String pathInfo = request.getPathInfo();
  1028. if (pathInfo == null || pathInfo.length() <= 10) {
  1029. return false;
  1030. }
  1031. if ((request.getContextPath() != null)
  1032. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  1033. serveStaticResourcesInVAADIN(request.getRequestURI(), request,
  1034. response);
  1035. return true;
  1036. } else if (request.getRequestURI().startsWith(
  1037. request.getContextPath() + "/VAADIN/")) {
  1038. serveStaticResourcesInVAADIN(
  1039. request.getRequestURI().substring(
  1040. request.getContextPath().length()), request,
  1041. response);
  1042. return true;
  1043. }
  1044. return false;
  1045. }
  1046. /**
  1047. * Serve resources from VAADIN directory.
  1048. *
  1049. * @param filename
  1050. * The filename to serve. Should always start with /VAADIN/.
  1051. * @param request
  1052. * @param response
  1053. * @throws IOException
  1054. * @throws ServletException
  1055. */
  1056. private void serveStaticResourcesInVAADIN(String filename,
  1057. HttpServletRequest request, HttpServletResponse response)
  1058. throws IOException, ServletException {
  1059. final ServletContext sc = getServletContext();
  1060. URL resourceUrl = sc.getResource(filename);
  1061. if (resourceUrl == null) {
  1062. // try if requested file is found from classloader
  1063. // strip leading "/" otherwise stream from JAR wont work
  1064. filename = filename.substring(1);
  1065. resourceUrl = getClassLoader().getResource(filename);
  1066. if (resourceUrl == null) {
  1067. // cannot serve requested file
  1068. System.err
  1069. .println("Requested resource ["
  1070. + filename
  1071. + "] not found from filesystem or through class loader."
  1072. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.");
  1073. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  1074. return;
  1075. }
  1076. }
  1077. // Find the modification timestamp
  1078. long lastModifiedTime = 0;
  1079. try {
  1080. lastModifiedTime = resourceUrl.openConnection().getLastModified();
  1081. // Remove milliseconds to avoid comparison problems (milliseconds
  1082. // are not returned by the browser in the "If-Modified-Since"
  1083. // header).
  1084. lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
  1085. if (browserHasNewestVersion(request, lastModifiedTime)) {
  1086. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  1087. return;
  1088. }
  1089. } catch (Exception e) {
  1090. // Failed to find out last modified timestamp. Continue without it.
  1091. e.printStackTrace();
  1092. }
  1093. // Set type mime type if we can determine it based on the filename
  1094. final String mimetype = sc.getMimeType(filename);
  1095. if (mimetype != null) {
  1096. response.setContentType(mimetype);
  1097. }
  1098. // Provide modification timestamp to the browser if it is known.
  1099. if (lastModifiedTime > 0) {
  1100. response.setDateHeader("Last-Modified", lastModifiedTime);
  1101. /*
  1102. * The browser is allowed to cache for 1 hour without checking if
  1103. * the file has changed. This forces browsers to fetch a new version
  1104. * when the Vaadin version is updated. This will cause more requests
  1105. * to the servlet than without this but for high volume sites the
  1106. * static files should never be served through the servlet. The
  1107. * cache timeout can be configured by setting the resourceCacheTime
  1108. * parameter in web.xml
  1109. */
  1110. response.setHeader("Cache-Control",
  1111. "max-age: " + String.valueOf(resourceCacheTime));
  1112. }
  1113. // Write the resource to the client.
  1114. final OutputStream os = response.getOutputStream();
  1115. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  1116. int bytes;
  1117. InputStream is = resourceUrl.openStream();
  1118. while ((bytes = is.read(buffer)) >= 0) {
  1119. os.write(buffer, 0, bytes);
  1120. }
  1121. is.close();
  1122. }
  1123. /**
  1124. * Checks if the browser has an up to date cached version of requested
  1125. * resource. Currently the check is performed using the "If-Modified-Since"
  1126. * header. Could be expanded if needed.
  1127. *
  1128. * @param request
  1129. * The HttpServletRequest from the browser.
  1130. * @param resourceLastModifiedTimestamp
  1131. * The timestamp when the resource was last modified. 0 if the
  1132. * last modification time is unknown.
  1133. * @return true if the If-Modified-Since header tells the cached version in
  1134. * the browser is up to date, false otherwise
  1135. */
  1136. private boolean browserHasNewestVersion(HttpServletRequest request,
  1137. long resourceLastModifiedTimestamp) {
  1138. if (resourceLastModifiedTimestamp < 1) {
  1139. // We do not know when it was modified so the browser cannot have an
  1140. // up-to-date version
  1141. return false;
  1142. }
  1143. /*
  1144. * The browser can request the resource conditionally using an
  1145. * If-Modified-Since header. Check this against the last modification
  1146. * time.
  1147. */
  1148. try {
  1149. // If-Modified-Since represents the timestamp of the version cached
  1150. // in the browser
  1151. long headerIfModifiedSince = request
  1152. .getDateHeader("If-Modified-Since");
  1153. if (headerIfModifiedSince >= resourceLastModifiedTimestamp) {
  1154. // Browser has this an up-to-date version of the resource
  1155. return true;
  1156. }
  1157. } catch (Exception e) {
  1158. // Failed to parse header. Fail silently - the browser does not have
  1159. // an up-to-date version in its cache.
  1160. }
  1161. return false;
  1162. }
  1163. protected enum RequestType {
  1164. FILE_UPLOAD, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE;
  1165. }
  1166. protected RequestType getRequestType(HttpServletRequest request) {
  1167. if (isFileUploadRequest(request)) {
  1168. return RequestType.FILE_UPLOAD;
  1169. } else if (isUIDLRequest(request)) {
  1170. return RequestType.UIDL;
  1171. } else if (isStaticResourceRequest(request)) {
  1172. return RequestType.STATIC_FILE;
  1173. } else if (isApplicationRequest(request)) {
  1174. return RequestType.APPLICATION_RESOURCE;
  1175. } else if (request.getHeader("FileId") != null) {
  1176. return RequestType.FILE_UPLOAD;
  1177. }
  1178. return RequestType.OTHER;
  1179. }
  1180. private boolean isApplicationRequest(HttpServletRequest request) {
  1181. String path = getRequestPathInfo(request);
  1182. if (path != null && path.startsWith("/APP/")) {
  1183. return true;
  1184. }
  1185. return false;
  1186. }
  1187. private boolean isStaticResourceRequest(HttpServletRequest request) {
  1188. String pathInfo = request.getPathInfo();
  1189. if (pathInfo == null || pathInfo.length() <= 10) {
  1190. return false;
  1191. }
  1192. if ((request.getContextPath() != null)
  1193. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  1194. return true;
  1195. } else if (request.getRequestURI().startsWith(
  1196. request.getContextPath() + "/VAADIN/")) {
  1197. return true;
  1198. }
  1199. return false;
  1200. }
  1201. private boolean isUIDLRequest(HttpServletRequest request) {
  1202. String pathInfo = getRequestPathInfo(request);
  1203. if (pathInfo == null) {
  1204. return false;
  1205. }
  1206. String compare = AJAX_UIDL_URI;
  1207. if (pathInfo.startsWith(compare + "/") || pathInfo.endsWith(compare)) {
  1208. return true;
  1209. }
  1210. return false;
  1211. }
  1212. private boolean isFileUploadRequest(HttpServletRequest request) {
  1213. return ServletFileUpload.isMultipartContent(request);
  1214. }
  1215. private boolean isOnUnloadRequest(HttpServletRequest request) {
  1216. return request.getParameter(ApplicationConnection.PARAM_UNLOADBURST) != null;
  1217. }
  1218. /**
  1219. * Get system messages from the current application class
  1220. *
  1221. * @return
  1222. */
  1223. protected SystemMessages getSystemMessages() {
  1224. try {
  1225. Class<? extends Application> appCls = getApplicationClass();
  1226. Method m = appCls.getMethod("getSystemMessages", (Class[]) null);
  1227. return (Application.SystemMessages) m.invoke(null, (Object[]) null);
  1228. } catch (ClassNotFoundException e) {
  1229. // This should never happen
  1230. throw new SystemMessageException(e);
  1231. } catch (SecurityException e) {
  1232. throw new SystemMessageException(
  1233. "Application.getSystemMessage() should be static public", e);
  1234. } catch (NoSuchMethodException e) {
  1235. // This is completely ok and should be silently ignored
  1236. } catch (IllegalArgumentException e) {
  1237. // This should never happen
  1238. throw new SystemMessageException(e);
  1239. } catch (IllegalAccessException e) {
  1240. throw new SystemMessageException(
  1241. "Application.getSystemMessage() should be static public", e);
  1242. } catch (InvocationTargetException e) {
  1243. // This should never happen
  1244. throw new SystemMessageException(e);
  1245. }
  1246. return Application.getSystemMessages();
  1247. }
  1248. protected abstract Class<? extends Application> getApplicationClass()
  1249. throws ClassNotFoundException;
  1250. /**
  1251. * Return the URL from where static files, e.g. the widgetset and the theme,
  1252. * are served. In a standard configuration the VAADIN folder inside the
  1253. * returned folder is what is used for widgetsets and themes.
  1254. *
  1255. * The returned folder is usually the same as the context path and
  1256. * independent of the application.
  1257. *
  1258. * @param request
  1259. * @return The location of static resources (should contain the VAADIN
  1260. * directory). Never ends with a slash (/).
  1261. */
  1262. protected String getStaticFilesLocation(HttpServletRequest request) {
  1263. // request may have an attribute explicitly telling location (portal
  1264. // case)
  1265. String staticFileLocation = (String) request
  1266. .getAttribute(REQUEST_VAADIN_STATIC_FILE_PATH);
  1267. if (staticFileLocation != null) {
  1268. // TODO remove trailing slash if any?
  1269. return staticFileLocation;
  1270. }
  1271. return getWebApplicationsStaticFileLocation(request);
  1272. }
  1273. /**
  1274. * The default method to fetch static files location. This method does not
  1275. * check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH}.
  1276. *
  1277. * @param request
  1278. * @return
  1279. */
  1280. private String getWebApplicationsStaticFileLocation(
  1281. HttpServletRequest request) {
  1282. String staticFileLocation;
  1283. // if property is defined in configurations, use that
  1284. staticFileLocation = getApplicationOrSystemProperty(
  1285. PARAMETER_VAADIN_RESOURCES, null);
  1286. if (staticFileLocation != null) {
  1287. return staticFileLocation;
  1288. }
  1289. // the last (but most common) option is to generate default location
  1290. // from request
  1291. // if context is specified add it to widgetsetUrl
  1292. String ctxPath = request.getContextPath();
  1293. // FIXME: ctxPath.length() == 0 condition is probably unnecessary and
  1294. // might even be wrong.
  1295. if (ctxPath.length() == 0
  1296. && request.getAttribute("javax.servlet.include.context_path") != null) {
  1297. // include request (e.g portlet), get context path from
  1298. // attribute
  1299. ctxPath = (String) request
  1300. .getAttribute("javax.servlet.include.context_path");
  1301. }
  1302. // Remove heading and trailing slashes from the context path
  1303. ctxPath = removeHeadingOrTrailing(ctxPath, "/");
  1304. if (ctxPath.equals("")) {
  1305. return "";
  1306. } else {
  1307. return "/" + ctxPath;
  1308. }
  1309. }
  1310. /**
  1311. * Remove any heading or trailing "what" from the "string".
  1312. *
  1313. * @param string
  1314. * @param what
  1315. * @return
  1316. */
  1317. private static String removeHeadingOrTrailing(String string, String what) {
  1318. while (string.startsWith(what)) {
  1319. string = string.substring(1);
  1320. }
  1321. while (string.endsWith(what)) {
  1322. string = string.substring(0, string.length() - 1);
  1323. }
  1324. return string;
  1325. }
  1326. /**
  1327. * Write a redirect response to the main page of the application.
  1328. *
  1329. * @param request
  1330. * @param response
  1331. * @throws IOException
  1332. * if sending the redirect fails due to an input/output error or
  1333. * a bad application URL
  1334. */
  1335. private void redirectToApplication(HttpServletRequest request,
  1336. HttpServletResponse response) throws IOException {
  1337. String applicationUrl = getApplicationUrl(request).toExternalForm();
  1338. response.sendRedirect(response.encodeRedirectURL(applicationUrl));
  1339. }
  1340. /**
  1341. * This method writes the html host page (aka kickstart page) that starts
  1342. * the actual Vaadin application.
  1343. * <p>
  1344. * If one needs to override parts of the host page, it is suggested that one
  1345. * overrides on of several submethods which are called by this method:
  1346. * <ul>
  1347. * <li> {@link #setAjaxPageHeaders(HttpServletResponse)}
  1348. * <li> {@link #writeAjaxPageHtmlHeadStart(BufferedWriter)}
  1349. * <li> {@link #writeAjaxPageHtmlHeader(BufferedWriter, String, String)}
  1350. * <li> {@link #writeAjaxPageHtmlBodyStart(BufferedWriter)}
  1351. * <li>
  1352. * {@link #writeAjaxPageHtmlVaadinScripts(Window, String, Application, BufferedWriter, String, String, String, String, String, String)}
  1353. * <li>
  1354. * {@link #writeAjaxPageHtmlMainDiv(BufferedWriter, String, String, String)}
  1355. * <li> {@link #writeAjaxPageHtmlBodyEnd(BufferedWriter)}
  1356. * </ul>
  1357. *
  1358. * @param request
  1359. * the HTTP request.
  1360. * @param response
  1361. * the HTTP response to write to.
  1362. * @param out
  1363. * @param unhandledParameters
  1364. * @param window
  1365. * @param terminalType
  1366. * @param theme
  1367. * @throws IOException
  1368. * if the writing failed due to input/output error.
  1369. * @throws MalformedURLException
  1370. * if the application is denied access the persistent data store
  1371. * represented by the given URL.
  1372. */
  1373. protected void writeAjaxPage(HttpServletRequest request,
  1374. HttpServletResponse response, Window window, Application application)
  1375. throws IOException, MalformedURLException, ServletException {
  1376. // e.g portlets only want a html fragment
  1377. boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null);
  1378. if (fragment) {
  1379. // if this is a fragment request, the actual application is put to
  1380. // request so ApplicationPortlet can save it for a later use
  1381. request.setAttribute(Application.class.getName(), application);
  1382. }
  1383. final BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
  1384. response.getOutputStream(), "UTF-8"));
  1385. String title = ((window.getCaption() == null) ? "Vaadin 6" : window
  1386. .getCaption());
  1387. /* Fetch relative url to application */
  1388. // don't use server and port in uri. It may cause problems with some
  1389. // virtual server configurations which lose the server name
  1390. String appUrl = getApplicationUrl(request).getPath();
  1391. if (appUrl.endsWith("/")) {
  1392. appUrl = appUrl.substring(0, appUrl.length() - 1);
  1393. }
  1394. String themeName = getThemeForWindow(request, window);
  1395. String themeUri = getThemeUri(themeName, request);
  1396. if (!fragment) {
  1397. setAjaxPageHeaders(response);
  1398. writeAjaxPageHtmlHeadStart(page);
  1399. writeAjaxPageHtmlHeader(page, title, themeUri);
  1400. writeAjaxPageHtmlBodyStart(page);
  1401. }
  1402. String appId = appUrl;
  1403. if ("".equals(appUrl)) {
  1404. appId = "ROOT";
  1405. }
  1406. appId = appId.replaceAll("[^a-zA-Z0-9]", "");
  1407. // Add hashCode to the end, so that it is still (sort of) predictable,
  1408. // but indicates that it should not be used in CSS and such:
  1409. int hashCode = appId.hashCode();
  1410. if (hashCode < 0) {
  1411. hashCode = -hashCode;
  1412. }
  1413. appId = appId + "-" + hashCode;
  1414. writeAjaxPageHtmlVaadinScripts(window, themeName, application, page,
  1415. appUrl, themeUri, appId, request);
  1416. /*- Add classnames;
  1417. * .v-app
  1418. * .v-app-loading
  1419. * .v-app-<simpleName for app class>
  1420. * .v-theme-<themeName, remove non-alphanum>
  1421. */
  1422. String appClass = "v-app-" + getApplicationCSSClassName();
  1423. String themeClass = "";
  1424. if (themeName != null) {
  1425. themeClass = "v-theme-" + themeName.replaceAll("[^a-zA-Z0-9]", "");
  1426. } else {
  1427. themeClass = "v-theme-"
  1428. + getDefaultTheme().replaceAll("[^a-zA-Z0-9]", "");
  1429. }
  1430. String classNames = "v-app v-app-loading " + themeClass + " "
  1431. + appClass;
  1432. String divStyle = null;
  1433. if (request.getAttribute(REQUEST_APPSTYLE) != null) {
  1434. divStyle = "style=\"" + request.getAttribute(REQUEST_APPSTYLE)
  1435. + "\"";
  1436. }
  1437. writeAjaxPageHtmlMainDiv(page, appId, classNames, divStyle);
  1438. if (!fragment) {
  1439. page.write("</body>\n</html>\n");
  1440. }
  1441. page.close();
  1442. }
  1443. /**
  1444. * Returns the application class identifier for use in the application CSS
  1445. * class name in the root DIV. The application CSS class name is of form
  1446. * "v-app-"+getApplicationCSSClassName().
  1447. *
  1448. * This method should normally not be overridden.
  1449. *
  1450. * @return The CSS class name to use in combination with "v-app-".
  1451. */
  1452. protected String getApplicationCSSClassName() {
  1453. try {
  1454. return getApplicationClass().getSimpleName();
  1455. } catch (ClassNotFoundException e) {
  1456. e.printStackTrace();
  1457. return "unknown";
  1458. }
  1459. }
  1460. /**
  1461. * Get the URI for the application theme.
  1462. *
  1463. * A portal-wide default theme is fetched from the portal shared resource
  1464. * directory (if any), other themes from the portlet.
  1465. *
  1466. * @param themeName
  1467. * @param request
  1468. * @return
  1469. */
  1470. private String getThemeUri(String themeName, HttpServletRequest request) {
  1471. final String staticFilePath;
  1472. if (themeName.equals(request.getAttribute(REQUEST_DEFAULT_THEME))) {
  1473. // our window theme is the portal wide default theme, make it load
  1474. // from portals directory is defined
  1475. staticFilePath = getStaticFilesLocation(request);
  1476. } else {
  1477. /*
  1478. * theme is a custom theme, which is not necessarily located in
  1479. * portals VAADIN directory. Let the default servlet conf decide
  1480. * (omitting request parameter) the location. Note that theme can
  1481. * still be placed to portal directory with servlet parameter.
  1482. */
  1483. staticFilePath = getWebApplicationsStaticFileLocation(request);
  1484. }
  1485. return staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName;
  1486. }
  1487. /**
  1488. * Method to write the div element into which that actual Vaadin application
  1489. * is rendered.
  1490. * <p>
  1491. * Override this method if you want to add some custom html around around
  1492. * the div element into which the actual Vaadin application will be
  1493. * rendered.
  1494. *
  1495. * @param page
  1496. * @param appId
  1497. * @param classNames
  1498. * @param divStyle
  1499. * @throws IOException
  1500. */
  1501. protected void writeAjaxPageHtmlMainDiv(final BufferedWriter page,
  1502. String appId, String classNames, String divStyle)
  1503. throws IOException {
  1504. page.write("<div id=\"" + appId + "\" class=\"" + classNames + "\" "
  1505. + (divStyle != null ? divStyle : "") + "></div>\n");
  1506. page.write("<noscript>" + getNoScriptMessage() + "</noscript>");
  1507. }
  1508. /**
  1509. * Method to write the script part of the page which loads needed Vaadin
  1510. * scripts and themes.
  1511. * <p>
  1512. * Override this method if you want to add some custom html around scripts.
  1513. *
  1514. * @param window
  1515. * @param themeName
  1516. * @param application
  1517. * @param page
  1518. * @param appUrl
  1519. * @param themeUri
  1520. * @param appId
  1521. * @param request
  1522. * @throws ServletException
  1523. * @throws IOException
  1524. */
  1525. protected void writeAjaxPageHtmlVaadinScripts(Window window,
  1526. String themeName, Application application,
  1527. final BufferedWriter page, String appUrl, String themeUri,
  1528. String appId, HttpServletRequest request) throws ServletException,
  1529. IOException {
  1530. // request widgetset takes precedence (e.g portlet include)
  1531. String requestWidgetset = (String) request
  1532. .getAttribute(REQUEST_WIDGETSET);
  1533. String sharedWidgetset = (String) request
  1534. .getAttribute(REQUEST_SHARED_WIDGETSET);
  1535. if (requestWidgetset == null && sharedWidgetset == null) {
  1536. // Use the value from configuration or DEFAULT_WIDGETSET.
  1537. // If no shared widgetset is specified, the default widgetset is
  1538. // assumed to be in the servlet/portlet itself.
  1539. requestWidgetset = getApplicationOrSystemProperty(
  1540. PARAMETER_WIDGETSET, DEFAULT_WIDGETSET);
  1541. }
  1542. String widgetset;
  1543. String widgetsetBasePath;
  1544. if (requestWidgetset != null) {
  1545. widgetset = requestWidgetset;
  1546. widgetsetBasePath = getWebApplicationsStaticFileLocation(request);
  1547. } else {
  1548. widgetset = sharedWidgetset;
  1549. widgetsetBasePath = getStaticFilesLocation(request);
  1550. }
  1551. final String widgetsetFilePath = widgetsetBasePath + "/"
  1552. + WIDGETSET_DIRECTORY_PATH + widgetset + "/" + widgetset
  1553. + ".nocache.js?" + new Date().getTime();
  1554. // Get system messages
  1555. Application.SystemMessages systemMessages = null;
  1556. try {
  1557. systemMessages = getSystemMessages();
  1558. } catch (SystemMessageException e) {
  1559. // failing to get the system messages is always a problem
  1560. throw new ServletException("CommunicationError!", e);
  1561. }
  1562. page.write("<script type=\"text/javascript\">\n");
  1563. page.write("//<![CDATA[\n");
  1564. page.write("if(!vaadin || !vaadin.vaadinConfigurations) {\n "
  1565. + "if(!vaadin) { var vaadin = {}} \n"
  1566. + "vaadin.vaadinConfigurations = {};\n"
  1567. + "if (!vaadin.themesLoaded) { vaadin.themesLoaded = {}; }\n");
  1568. if (!isProductionMode()) {
  1569. page.write("vaadin.debug = true;\n");
  1570. }
  1571. page.write("document.write('<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" "
  1572. + "style=\"position:absolute;width:0;height:0;border:0;overflow:"
  1573. + "hidden;\" src=\"javascript:false\"></iframe>');\n");
  1574. page.write("document.write(\"<script language='javascript' src='"
  1575. + widgetsetFilePath + "'><\\/script>\");\n}\n");
  1576. page.write("vaadin.vaadinConfigurations[\"" + appId + "\"] = {");
  1577. page.write("appUri:'" + appUrl + "', ");
  1578. String pathInfo = getRequestPathInfo(request);
  1579. if (pathInfo == null) {
  1580. pathInfo = "/";
  1581. }
  1582. page.write("pathInfo: '" + pathInfo + "', ");
  1583. if (window != application.getMainWindow()) {
  1584. page.write("windowName: '" + window.getName() + "', ");
  1585. }
  1586. page.write("themeUri:");
  1587. page.write(themeUri != null ? "'" + themeUri + "'" : "null");
  1588. page.write(", versionInfo : {vaadinVersion:\"");
  1589. page.write(VERSION);
  1590. page.write("\",applicationVersion:\"");
  1591. page.write(application.getVersion());
  1592. page.write("\"}");
  1593. if (systemMessages != null) {
  1594. // Write the CommunicationError -message to client
  1595. String caption = systemMessages.getCommunicationErrorCaption();
  1596. if (caption != null) {
  1597. caption = "\"" + caption + "\"";
  1598. }
  1599. String message = systemMessages.getCommunicationErrorMessage();
  1600. if (message != null) {
  1601. message = "\"" + message + "\"";
  1602. }
  1603. String url = systemMessages.getCommunicationErrorURL();
  1604. if (url != null) {
  1605. url = "\"" + url + "\"";
  1606. }
  1607. page.write(",\"comErrMsg\": {" + "\"caption\":" + caption + ","
  1608. + "\"message\" : " + message + "," + "\"url\" : " + url
  1609. + "}");
  1610. // Write the AuthenticationError -message to client
  1611. caption = systemMessages.getAuthenticationErrorCaption();
  1612. if (caption != null) {
  1613. caption = "\"" + caption + "\"";
  1614. }
  1615. message = systemMessages.getAuthenticationErrorMessage();
  1616. if (message != null) {
  1617. message = "\"" + message + "\"";
  1618. }
  1619. url = systemMessages.getAuthenticationErrorURL();
  1620. if (url != null) {
  1621. url = "\"" + url + "\"";
  1622. }
  1623. page.write(",\"authErrMsg\": {" + "\"caption\":" + caption + ","
  1624. + "\"message\" : " + message + "," + "\"url\" : " + url
  1625. + "}");
  1626. }
  1627. page.write("};\n//]]>\n</script>\n");
  1628. if (themeName != null) {
  1629. // Custom theme's stylesheet, load only once, in different
  1630. // script
  1631. // tag to be dominate styles injected by widget
  1632. // set
  1633. page.write("<script type=\"text/javascript\">\n");
  1634. page.write("//<![CDATA[\n");
  1635. page.write("if(!vaadin.themesLoaded['" + themeName + "']) {\n");
  1636. page.write("var stylesheet = document.createElement('link');\n");
  1637. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  1638. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  1639. page.write("stylesheet.setAttribute('href', '" + themeUri
  1640. + "/styles.css');\n");
  1641. page.write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  1642. page.write("vaadin.themesLoaded['" + themeName + "'] = true;\n}\n");
  1643. page.write("//]]>\n</script>\n");
  1644. }
  1645. // Warn if the widgetset has not been loaded after 15 seconds on
  1646. // inactivity
  1647. page.write("<script type=\"text/javascript\">\n");
  1648. page.write("//<![CDATA[\n");
  1649. page.write("setTimeout('if (typeof " + widgetset.replace('.', '_')
  1650. + " == \"undefined\") {alert(\"Failed to load the widgetset: "
  1651. + widgetsetFilePath + "\")};',15000);\n" + "//]]>\n</script>\n");
  1652. }
  1653. /**
  1654. *
  1655. * Method to open the body tag of the html kickstart page.
  1656. * <p>
  1657. * This method is responsible for closing the head tag and opening the body
  1658. * tag.
  1659. * <p>
  1660. * Override this method if you want to add some custom html to the page.
  1661. *
  1662. * @param page
  1663. * @throws IOException
  1664. */
  1665. protected void writeAjaxPageHtmlBodyStart(final BufferedWriter page)
  1666. throws IOException {
  1667. page.write("\n</head>\n<body scroll=\"auto\" class=\""
  1668. + ApplicationConnection.GENERATED_BODY_CLASSNAME + "\">\n");
  1669. }
  1670. /**
  1671. * Method to write the contents of head element in html kickstart page.
  1672. * <p>
  1673. * Override this method if you want to add some custom html to the header of
  1674. * the page.
  1675. *
  1676. * @param page
  1677. * @param title
  1678. * @param themeUri
  1679. * @throws IOException
  1680. */
  1681. protected void writeAjaxPageHtmlHeader(final BufferedWriter page,
  1682. String title, String themeUri) throws IOException {
  1683. page.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n");
  1684. page.write("<style type=\"text/css\">"
  1685. + "html, body {height:100%;margin:0;}</style>");
  1686. // Add favicon links
  1687. page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\""
  1688. + themeUri + "/favicon.ico\" />");
  1689. page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\""
  1690. + themeUri + "/favicon.ico\" />");
  1691. page.write("<title>" + title + "</title>");
  1692. }
  1693. /**
  1694. * Method to write the beginning of the html page.
  1695. * <p>
  1696. * This method is responsible for writing appropriate doc type declarations
  1697. * and to open html and head tags.
  1698. * <p>
  1699. * Override this method if you want to add some custom html to the very
  1700. * beginning of the page.
  1701. *
  1702. * @param page
  1703. * @throws IOException
  1704. */
  1705. protected void writeAjaxPageHtmlHeadStart(final BufferedWriter page)
  1706. throws IOException {
  1707. // write html header
  1708. page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
  1709. + "XHTML 1.0 Transitional//EN\" "
  1710. + "\"http://www.w3.org/TR/xhtml1/"
  1711. + "DTD/xhtml1-transitional.dtd\">\n");
  1712. page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
  1713. + ">\n<head>\n");
  1714. }
  1715. /**
  1716. * Method to set http request headers for the Vaadin kickstart page.
  1717. * <p>
  1718. * Override this method if you need to customize http headers of the page.
  1719. *
  1720. * @param response
  1721. */
  1722. protected void setAjaxPageHeaders(HttpServletResponse response) {
  1723. // Window renders are not cacheable
  1724. response.setHeader("Cache-Control", "no-cache");
  1725. response.setHeader("Pragma", "no-cache");
  1726. response.setDateHeader("Expires", 0);
  1727. response.setContentType("text/html; charset=UTF-8");
  1728. }
  1729. /**
  1730. * Returns a message printed for browsers without scripting support or if
  1731. * browsers scripting support is disabled.
  1732. */
  1733. protected String getNoScriptMessage() {
  1734. return "You have to enable javascript in your browser to use an application built with Vaadin.";
  1735. }
  1736. /**
  1737. * Gets the current application URL from request.
  1738. *
  1739. * @param request
  1740. * the HTTP request.
  1741. * @throws MalformedURLException
  1742. * if the application is denied access to the persistent data
  1743. * store represented by the given URL.
  1744. */
  1745. URL getApplicationUrl(HttpServletRequest request)
  1746. throws MalformedURLException {
  1747. final URL reqURL = new URL(
  1748. (request.isSecure() ? "https://" : "http://")
  1749. + request.getServerName()
  1750. + ((request.isSecure() && request.getServerPort() == 443)
  1751. || (!request.isSecure() && request
  1752. .getServerPort() == 80) ? "" : ":"
  1753. + request.getServerPort())
  1754. + request.getRequestURI());
  1755. String servletPath = "";
  1756. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  1757. // this is an include request
  1758. servletPath = request.getAttribute(
  1759. "javax.servlet.include.context_path").toString()
  1760. + request
  1761. .getAttribute("javax.servlet.include.servlet_path");
  1762. } else {
  1763. servletPath = request.getContextPath() + request.getServletPath();
  1764. }
  1765. if (servletPath.length() == 0
  1766. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1767. servletPath = servletPath + "/";
  1768. }
  1769. URL u = new URL(reqURL, servletPath);
  1770. return u;
  1771. }
  1772. /**
  1773. * Gets the existing application for given request. Looks for application
  1774. * instance for given request based on the requested URL.
  1775. *
  1776. * @param request
  1777. * the HTTP request.
  1778. * @param allowSessionCreation
  1779. * true if a session should be created if no session exists,
  1780. * false if no session should be created
  1781. * @return Application instance, or null if the URL does not map to valid
  1782. * application.
  1783. * @throws MalformedURLException
  1784. * if the application is denied access to the persistent data
  1785. * store represented by the given URL.
  1786. * @throws IllegalAccessException
  1787. * @throws InstantiationException
  1788. * @throws SessionExpiredException
  1789. */
  1790. private Application getExistingApplication(HttpServletRequest request,
  1791. boolean allowSessionCreation) throws MalformedURLException,
  1792. SessionExpiredException {
  1793. // Ensures that the session is still valid
  1794. final HttpSession session = request.getSession(allowSessionCreation);
  1795. if (session == null) {
  1796. throw new SessionExpiredException();
  1797. }
  1798. WebApplicationContext context = WebApplicationContext
  1799. .getApplicationContext(session);
  1800. // Gets application list for the session.
  1801. final Collection<Application> applications = context.getApplications();
  1802. // Search for the application (using the application URI) from the list
  1803. for (final Iterator<Application> i = applications.iterator(); i
  1804. .hasNext();) {
  1805. final Application sessionApplication = i.next();
  1806. final String sessionApplicationPath = sessionApplication.getURL()
  1807. .getPath();
  1808. String requestApplicationPath = getApplicationUrl(request)
  1809. .getPath();
  1810. if (requestApplicationPath.equals(sessionApplicationPath)) {
  1811. // Found a running application
  1812. if (sessionApplication.isRunning()) {
  1813. return sessionApplication;
  1814. }
  1815. // Application has stopped, so remove it before creating a new
  1816. // application
  1817. WebApplicationContext.getApplicationContext(session)
  1818. .removeApplication(sessionApplication);
  1819. break;
  1820. }
  1821. }
  1822. // Existing application not found
  1823. return null;
  1824. }
  1825. /**
  1826. * Ends the application.
  1827. *
  1828. * @param request
  1829. * the HTTP request.
  1830. * @param response
  1831. * the HTTP response to write to.
  1832. * @param application
  1833. * the application to end.
  1834. * @throws IOException
  1835. * if the writing failed due to input/output error.
  1836. */
  1837. private void endApplication(HttpServletRequest request,
  1838. HttpServletResponse response, Application application)
  1839. throws IOException {
  1840. String logoutUrl = application.getLogoutURL();
  1841. if (logoutUrl == null) {
  1842. logoutUrl = application.getURL().toString();
  1843. }
  1844. final HttpSession session = request.getSession();
  1845. if (session != null) {
  1846. WebApplicationContext.getApplicationContext(session)
  1847. .removeApplication(application);
  1848. }
  1849. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  1850. }
  1851. /**
  1852. * Gets the existing application or create a new one. Get a window within an
  1853. * application based on the requested URI.
  1854. *
  1855. * @param request
  1856. * the HTTP Request.
  1857. * @param application
  1858. * the Application to query for window.
  1859. * @return Window matching the given URI or null if not found.
  1860. * @throws ServletException
  1861. * if an exception has occurred that interferes with the
  1862. * servlet's normal operation.
  1863. */
  1864. private Window getApplicationWindow(HttpServletRequest request,
  1865. CommunicationManager applicationManager, Application application)
  1866. throws ServletException {
  1867. // Finds the window where the request is handled
  1868. Window assumedWindow = null;
  1869. String path = getRequestPathInfo(request);
  1870. // Main window as the URI is empty
  1871. if (!(path == null || path.length() == 0 || path.equals("/"))) {
  1872. if (path.startsWith("/APP/")) {
  1873. // Use main window for application resources
  1874. return application.getMainWindow();
  1875. }
  1876. String windowName = null;
  1877. if (path.charAt(0) == '/') {
  1878. path = path.substring(1);
  1879. }
  1880. final int index = path.indexOf('/');
  1881. if (index < 0) {
  1882. windowName = path;
  1883. path = "";
  1884. } else {
  1885. windowName = path.substring(0, index);
  1886. }
  1887. assumedWindow = application.getWindow(windowName);
  1888. }
  1889. return applicationManager.getApplicationWindow(request, this,
  1890. application, assumedWindow);
  1891. }
  1892. /**
  1893. * Returns the path info; note that this _can_ be different than
  1894. * request.getPathInfo() (e.g application runner).
  1895. *
  1896. * @param request
  1897. * @return
  1898. */
  1899. String getRequestPathInfo(HttpServletRequest request) {
  1900. return request.getPathInfo();
  1901. }
  1902. /**
  1903. * Gets relative location of a theme resource.
  1904. *
  1905. * @param theme
  1906. * the Theme name.
  1907. * @param resource
  1908. * the Theme resource.
  1909. * @return External URI specifying the resource
  1910. */
  1911. public String getResourceLocation(String theme, ThemeResource resource) {
  1912. if (resourcePath == null) {
  1913. return resource.getResourceId();
  1914. }
  1915. return resourcePath + theme + "/" + resource.getResourceId();
  1916. }
  1917. private boolean isRepaintAll(HttpServletRequest request) {
  1918. return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null)
  1919. && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1"));
  1920. }
  1921. private void closeApplication(Application application, HttpSession session) {
  1922. if (application == null) {
  1923. return;
  1924. }
  1925. application.close();
  1926. if (session != null) {
  1927. WebApplicationContext context = WebApplicationContext
  1928. .getApplicationContext(session);
  1929. context.removeApplication(application);
  1930. }
  1931. }
  1932. /**
  1933. * Implementation of ParameterHandler.ErrorEvent interface.
  1934. */
  1935. public class ParameterHandlerErrorImpl implements
  1936. ParameterHandler.ErrorEvent, Serializable {
  1937. private ParameterHandler owner;
  1938. private Throwable throwable;
  1939. /**
  1940. * Gets the contained throwable.
  1941. *
  1942. * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable()
  1943. */
  1944. public Throwable getThrowable() {
  1945. return throwable;
  1946. }
  1947. /**
  1948. * Gets the source ParameterHandler.
  1949. *
  1950. * @see com.vaadin.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
  1951. */
  1952. public ParameterHandler getParameterHandler() {
  1953. return owner;
  1954. }
  1955. }
  1956. /**
  1957. * Implementation of URIHandler.ErrorEvent interface.
  1958. */
  1959. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent,
  1960. Serializable {
  1961. private final URIHandler owner;
  1962. private final Throwable throwable;
  1963. /**
  1964. *
  1965. * @param owner
  1966. * @param throwable
  1967. */
  1968. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  1969. this.owner = owner;
  1970. this.throwable = throwable;
  1971. }
  1972. /**
  1973. * Gets the contained throwable.
  1974. *
  1975. * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable()
  1976. */
  1977. public Throwable getThrowable() {
  1978. return throwable;
  1979. }
  1980. /**
  1981. * Gets the source URIHandler.
  1982. *
  1983. * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler()
  1984. */
  1985. public URIHandler getURIHandler() {
  1986. return owner;
  1987. }
  1988. }
  1989. public class RequestError implements Terminal.ErrorEvent, Serializable {
  1990. private final Throwable throwable;
  1991. public RequestError(Throwable throwable) {
  1992. this.throwable = throwable;
  1993. }
  1994. public Throwable getThrowable() {
  1995. return throwable;
  1996. }
  1997. }
  1998. }