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

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