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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  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.net.URLConnection;
  18. import java.security.GeneralSecurityException;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Enumeration;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.Locale;
  25. import java.util.Properties;
  26. import java.util.logging.Level;
  27. import java.util.logging.Logger;
  28. import javax.servlet.ServletContext;
  29. import javax.servlet.ServletException;
  30. import javax.servlet.ServletOutputStream;
  31. import javax.servlet.http.HttpServlet;
  32. import javax.servlet.http.HttpServletRequest;
  33. import javax.servlet.http.HttpServletResponse;
  34. import javax.servlet.http.HttpSession;
  35. import com.vaadin.Application;
  36. import com.vaadin.Application.ApplicationStartEvent;
  37. import com.vaadin.Application.SystemMessages;
  38. import com.vaadin.terminal.DeploymentConfiguration;
  39. import com.vaadin.terminal.Terminal;
  40. import com.vaadin.terminal.ThemeResource;
  41. import com.vaadin.terminal.WrappedRequest;
  42. import com.vaadin.terminal.WrappedResponse;
  43. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  44. import com.vaadin.terminal.gwt.server.AbstractCommunicationManager.Callback;
  45. import com.vaadin.ui.Root;
  46. /**
  47. * Abstract implementation of the ApplicationServlet which handles all
  48. * communication between the client and the server.
  49. *
  50. * It is possible to extend this class to provide own functionality but in most
  51. * cases this is unnecessary.
  52. *
  53. *
  54. * @author Vaadin Ltd.
  55. * @version
  56. * @VERSION@
  57. * @since 6.0
  58. */
  59. @SuppressWarnings("serial")
  60. public abstract class AbstractApplicationServlet extends HttpServlet implements
  61. Constants {
  62. private static class AbstractApplicationServletWrapper implements Callback {
  63. private final AbstractApplicationServlet servlet;
  64. public AbstractApplicationServletWrapper(
  65. AbstractApplicationServlet servlet) {
  66. this.servlet = servlet;
  67. }
  68. public void criticalNotification(WrappedRequest request,
  69. WrappedResponse response, String cap, String msg,
  70. String details, String outOfSyncURL) throws IOException {
  71. servlet.criticalNotification(
  72. WrappedHttpServletRequest.cast(request),
  73. ((WrappedHttpServletResponse) response), cap, msg, details,
  74. outOfSyncURL);
  75. }
  76. }
  77. // TODO Move some (all?) of the constants to a separate interface (shared
  78. // with portlet)
  79. private static final Logger logger = Logger
  80. .getLogger(AbstractApplicationServlet.class.getName());
  81. private Properties applicationProperties;
  82. private boolean productionMode = false;
  83. private final String resourcePath = null;
  84. private int resourceCacheTime = 3600;
  85. private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() {
  86. public String getStaticFileLocation(WrappedRequest request) {
  87. HttpServletRequest servletRequest = WrappedHttpServletRequest
  88. .cast(request);
  89. return AbstractApplicationServlet.this
  90. .getStaticFilesLocation(servletRequest);
  91. }
  92. public String getConfiguredWidgetset(WrappedRequest request) {
  93. return getApplicationOrSystemProperty(
  94. AbstractApplicationServlet.PARAMETER_WIDGETSET,
  95. AbstractApplicationServlet.DEFAULT_WIDGETSET);
  96. }
  97. public String getConfiguredTheme(WrappedRequest request) {
  98. // Use the default
  99. return AbstractApplicationServlet.getDefaultTheme();
  100. }
  101. public String getApplicationOrSystemProperty(String propertyName,
  102. String defaultValue) {
  103. return AbstractApplicationServlet.this
  104. .getApplicationOrSystemProperty(propertyName, defaultValue);
  105. }
  106. public boolean isStandalone(WrappedRequest request) {
  107. return true;
  108. }
  109. public ClassLoader getClassLoader() {
  110. try {
  111. return AbstractApplicationServlet.this.getClassLoader();
  112. } catch (ServletException e) {
  113. throw new RuntimeException(e);
  114. }
  115. }
  116. };
  117. static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/";
  118. /**
  119. * Called by the servlet container to indicate to a servlet that the servlet
  120. * is being placed into service.
  121. *
  122. * @param servletConfig
  123. * the object containing the servlet's configuration and
  124. * initialization parameters
  125. * @throws javax.servlet.ServletException
  126. * if an exception has occurred that interferes with the
  127. * servlet's normal operation.
  128. */
  129. @SuppressWarnings("unchecked")
  130. @Override
  131. public void init(javax.servlet.ServletConfig servletConfig)
  132. throws javax.servlet.ServletException {
  133. super.init(servletConfig);
  134. applicationProperties = new Properties();
  135. // Read default parameters from server.xml
  136. final ServletContext context = servletConfig.getServletContext();
  137. for (final Enumeration<String> e = context.getInitParameterNames(); e
  138. .hasMoreElements();) {
  139. final String name = e.nextElement();
  140. applicationProperties.setProperty(name,
  141. context.getInitParameter(name));
  142. }
  143. // Override with application config from web.xml
  144. for (final Enumeration<String> e = servletConfig
  145. .getInitParameterNames(); e.hasMoreElements();) {
  146. final String name = e.nextElement();
  147. applicationProperties.setProperty(name,
  148. servletConfig.getInitParameter(name));
  149. }
  150. checkProductionMode();
  151. checkCrossSiteProtection();
  152. checkResourceCacheTime();
  153. }
  154. private void checkCrossSiteProtection() {
  155. if (getApplicationOrSystemProperty(
  156. SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, "false").equals(
  157. "true")) {
  158. /*
  159. * Print an information/warning message about running with xsrf
  160. * protection disabled
  161. */
  162. logger.warning(WARNING_XSRF_PROTECTION_DISABLED);
  163. }
  164. }
  165. private void checkProductionMode() {
  166. // Check if the application is in production mode.
  167. // We are in production mode if productionMode=true
  168. if (getApplicationOrSystemProperty(SERVLET_PARAMETER_PRODUCTION_MODE,
  169. "false").equals("true")) {
  170. productionMode = true;
  171. }
  172. if (!productionMode) {
  173. /* Print an information/warning message about running in debug mode */
  174. logger.warning(NOT_PRODUCTION_MODE_INFO);
  175. }
  176. }
  177. private void checkResourceCacheTime() {
  178. // Check if the browser caching time has been set in web.xml
  179. try {
  180. String rct = getApplicationOrSystemProperty(
  181. SERVLET_PARAMETER_RESOURCE_CACHE_TIME, "3600");
  182. resourceCacheTime = Integer.parseInt(rct);
  183. } catch (NumberFormatException nfe) {
  184. // Default is 1h
  185. resourceCacheTime = 3600;
  186. logger.warning(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC);
  187. }
  188. }
  189. /**
  190. * Gets an application property value.
  191. *
  192. * @param parameterName
  193. * the Name or the parameter.
  194. * @return String value or null if not found
  195. */
  196. protected String getApplicationProperty(String parameterName) {
  197. String val = applicationProperties.getProperty(parameterName);
  198. if (val != null) {
  199. return val;
  200. }
  201. // Try lower case application properties for backward compatibility with
  202. // 3.0.2 and earlier
  203. val = applicationProperties.getProperty(parameterName.toLowerCase());
  204. return val;
  205. }
  206. /**
  207. * Gets an system property value.
  208. *
  209. * @param parameterName
  210. * the Name or the parameter.
  211. * @return String value or null if not found
  212. */
  213. protected String getSystemProperty(String parameterName) {
  214. String val = null;
  215. String pkgName;
  216. final Package pkg = getClass().getPackage();
  217. if (pkg != null) {
  218. pkgName = pkg.getName();
  219. } else {
  220. final String className = getClass().getName();
  221. pkgName = new String(className.toCharArray(), 0,
  222. className.lastIndexOf('.'));
  223. }
  224. val = System.getProperty(pkgName + "." + parameterName);
  225. if (val != null) {
  226. return val;
  227. }
  228. // Try lowercased system properties
  229. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  230. return val;
  231. }
  232. /**
  233. * Gets an application or system property value.
  234. *
  235. * @param parameterName
  236. * the Name or the parameter.
  237. * @param defaultValue
  238. * the Default to be used.
  239. * @return String value or default if not found
  240. */
  241. String getApplicationOrSystemProperty(String parameterName,
  242. String defaultValue) {
  243. String val = null;
  244. // Try application properties
  245. val = getApplicationProperty(parameterName);
  246. if (val != null) {
  247. return val;
  248. }
  249. // Try system properties
  250. val = getSystemProperty(parameterName);
  251. if (val != null) {
  252. return val;
  253. }
  254. return defaultValue;
  255. }
  256. /**
  257. * Returns true if the servlet is running in production mode. Production
  258. * mode disables all debug facilities.
  259. *
  260. * @return true if in production mode, false if in debug mode
  261. */
  262. public boolean isProductionMode() {
  263. return productionMode;
  264. }
  265. /**
  266. * Returns the amount of milliseconds the browser should cache a file.
  267. * Default is 1 hour (3600 ms).
  268. *
  269. * @return The amount of milliseconds files are cached in the browser
  270. */
  271. public int getResourceCacheTime() {
  272. return resourceCacheTime;
  273. }
  274. /**
  275. * Receives standard HTTP requests from the public service method and
  276. * dispatches them.
  277. *
  278. * @param request
  279. * the object that contains the request the client made of the
  280. * servlet.
  281. * @param response
  282. * the object that contains the response the servlet returns to
  283. * the client.
  284. * @throws ServletException
  285. * if an input or output error occurs while the servlet is
  286. * handling the TRACE request.
  287. * @throws IOException
  288. * if the request for the TRACE cannot be handled.
  289. */
  290. @Override
  291. protected void service(HttpServletRequest request,
  292. HttpServletResponse response) throws ServletException, IOException {
  293. service(createWrappedRequest(request), createWrappedResponse(response));
  294. }
  295. private void service(WrappedHttpServletRequest request,
  296. WrappedHttpServletResponse response) throws ServletException,
  297. IOException {
  298. RequestTimer requestTimer = new RequestTimer();
  299. requestTimer.start();
  300. AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper(
  301. this);
  302. RequestType requestType = getRequestType(request);
  303. if (!ensureCookiesEnabled(requestType, request, response)) {
  304. return;
  305. }
  306. if (requestType == RequestType.STATIC_FILE) {
  307. serveStaticResources(request, response);
  308. return;
  309. }
  310. Application application = null;
  311. boolean transactionStarted = false;
  312. boolean requestStarted = false;
  313. try {
  314. // If a duplicate "close application" URL is received for an
  315. // application that is not open, redirect to the application's main
  316. // page.
  317. // This is needed as e.g. Spring Security remembers the last
  318. // URL from the application, which is the logout URL, and repeats
  319. // it.
  320. // We can tell apart a real onunload request from a repeated one
  321. // based on the real one having content (at least the UIDL security
  322. // key).
  323. if (requestType == RequestType.UIDL
  324. && request.getParameterMap().containsKey(
  325. ApplicationConnection.PARAM_UNLOADBURST)
  326. && request.getContentLength() < 1
  327. && getExistingApplication(request, false) == null) {
  328. redirectToApplication(request, response);
  329. return;
  330. }
  331. // Find out which application this request is related to
  332. application = findApplicationInstance(request, requestType);
  333. if (application == null) {
  334. return;
  335. }
  336. Application.setCurrentApplication(application);
  337. /*
  338. * Get or create a WebApplicationContext and an ApplicationManager
  339. * for the session
  340. */
  341. WebApplicationContext webApplicationContext = getApplicationContext(request
  342. .getSession());
  343. CommunicationManager applicationManager = webApplicationContext
  344. .getApplicationManager(application, this);
  345. /* Update browser information from the request */
  346. webApplicationContext.getBrowser().updateRequestDetails(request);
  347. /*
  348. * Call application requestStart before Application.init() is called
  349. * (bypasses the limitation in TransactionListener)
  350. */
  351. if (application instanceof HttpServletRequestListener) {
  352. ((HttpServletRequestListener) application).onRequestStart(
  353. request, response);
  354. requestStarted = true;
  355. }
  356. // Start the application if it's newly created
  357. startApplication(request, application, webApplicationContext);
  358. /*
  359. * Transaction starts. Call transaction listeners. Transaction end
  360. * is called in the finally block below.
  361. */
  362. webApplicationContext.startTransaction(application, request);
  363. transactionStarted = true;
  364. /* Handle the request */
  365. if (requestType == RequestType.FILE_UPLOAD) {
  366. applicationManager.handleFileUpload(application, request,
  367. response);
  368. return;
  369. } else if (requestType == RequestType.UIDL) {
  370. // Handles AJAX UIDL requests
  371. Root root = application.getRootForRequest(request);
  372. if (root == null) {
  373. throw new ServletException(ERROR_NO_WINDOW_FOUND);
  374. }
  375. applicationManager.handleUidlRequest(request, response,
  376. servletWrapper, root);
  377. return;
  378. } else if (requestType == RequestType.BROWSER_DETAILS) {
  379. applicationManager.handleBrowserDetailsRequest(request,
  380. response, application);
  381. return;
  382. }
  383. // Removes application if it has stopped (mayby by thread or
  384. // transactionlistener)
  385. if (!application.isRunning()) {
  386. endApplication(request, response, application);
  387. return;
  388. }
  389. if (applicationManager.handleApplicationRequest(request, response)) {
  390. return;
  391. }
  392. // TODO Should return 404 error here and not do anything more
  393. } catch (final SessionExpiredException e) {
  394. // Session has expired, notify user
  395. handleServiceSessionExpired(request, response);
  396. } catch (final GeneralSecurityException e) {
  397. handleServiceSecurityException(request, response);
  398. } catch (final Throwable e) {
  399. handleServiceException(request, response, application, e);
  400. } finally {
  401. // Notifies transaction end
  402. try {
  403. if (transactionStarted) {
  404. ((WebApplicationContext) application.getContext())
  405. .endTransaction(application, request);
  406. }
  407. } finally {
  408. try {
  409. if (requestStarted) {
  410. ((HttpServletRequestListener) application)
  411. .onRequestEnd(request, response);
  412. }
  413. } finally {
  414. Root.setCurrentRoot(null);
  415. Application.setCurrentApplication(null);
  416. requestTimer
  417. .stop((AbstractWebApplicationContext) application
  418. .getContext());
  419. }
  420. }
  421. }
  422. }
  423. private WrappedHttpServletResponse createWrappedResponse(
  424. HttpServletResponse response) {
  425. WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse(
  426. response, getDeploymentConfiguration());
  427. return wrappedResponse;
  428. }
  429. /**
  430. * Create a wrapped request for a http servlet request. This method can be
  431. * overridden if the wrapped request should have special properties.
  432. *
  433. * @param request
  434. * the original http servlet request
  435. * @return a wrapped request for the original request
  436. */
  437. protected WrappedHttpServletRequest createWrappedRequest(
  438. HttpServletRequest request) {
  439. return new WrappedHttpServletRequest(request,
  440. getDeploymentConfiguration());
  441. }
  442. /**
  443. * Gets a the deployment configuration for this servlet.
  444. *
  445. * @return the deployment configuration
  446. */
  447. protected DeploymentConfiguration getDeploymentConfiguration() {
  448. return deploymentConfiguration;
  449. }
  450. /**
  451. * Check that cookie support is enabled in the browser. Only checks UIDL
  452. * requests.
  453. *
  454. * @param requestType
  455. * Type of the request as returned by
  456. * {@link #getRequestType(HttpServletRequest)}
  457. * @param request
  458. * The request from the browser
  459. * @param response
  460. * The response to which an error can be written
  461. * @return false if cookies are disabled, true otherwise
  462. * @throws IOException
  463. */
  464. private boolean ensureCookiesEnabled(RequestType requestType,
  465. HttpServletRequest request, HttpServletResponse response)
  466. throws IOException {
  467. if (requestType == RequestType.UIDL && !isRepaintAll(request)) {
  468. // In all other but the first UIDL request a cookie should be
  469. // returned by the browser.
  470. // This can be removed if cookieless mode (#3228) is supported
  471. if (request.getRequestedSessionId() == null) {
  472. // User has cookies disabled
  473. criticalNotification(request, response, getSystemMessages()
  474. .getCookiesDisabledCaption(), getSystemMessages()
  475. .getCookiesDisabledMessage(), null, getSystemMessages()
  476. .getCookiesDisabledURL());
  477. return false;
  478. }
  479. }
  480. return true;
  481. }
  482. protected ClassLoader getClassLoader() throws ServletException {
  483. // Gets custom class loader
  484. final String classLoaderName = getApplicationOrSystemProperty(
  485. "ClassLoader", null);
  486. ClassLoader classLoader;
  487. if (classLoaderName == null) {
  488. classLoader = getClass().getClassLoader();
  489. } else {
  490. try {
  491. final Class<?> classLoaderClass = getClass().getClassLoader()
  492. .loadClass(classLoaderName);
  493. final Constructor<?> c = classLoaderClass
  494. .getConstructor(new Class[] { ClassLoader.class });
  495. classLoader = (ClassLoader) c
  496. .newInstance(new Object[] { getClass().getClassLoader() });
  497. } catch (final Exception e) {
  498. throw new ServletException(
  499. "Could not find specified class loader: "
  500. + classLoaderName, e);
  501. }
  502. }
  503. return classLoader;
  504. }
  505. /**
  506. * Send a notification to client's application. Used to notify client of
  507. * critical errors, session expiration and more. Server has no knowledge of
  508. * what application client refers to.
  509. *
  510. * @param request
  511. * the HTTP request instance.
  512. * @param response
  513. * the HTTP response to write to.
  514. * @param caption
  515. * the notification caption
  516. * @param message
  517. * to notification body
  518. * @param details
  519. * a detail message to show in addition to the message. Currently
  520. * shown directly below the message but could be hidden behind a
  521. * details drop down in the future. Mainly used to give
  522. * additional information not necessarily useful to the end user.
  523. * @param url
  524. * url to load when the message is dismissed. Null will reload
  525. * the current page.
  526. * @throws IOException
  527. * if the writing failed due to input/output error.
  528. */
  529. protected void criticalNotification(HttpServletRequest request,
  530. HttpServletResponse response, String caption, String message,
  531. String details, String url) throws IOException {
  532. if (isUIDLRequest(request)) {
  533. if (caption != null) {
  534. caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\"";
  535. }
  536. if (details != null) {
  537. if (message == null) {
  538. message = details;
  539. } else {
  540. message += "<br/><br/>" + details;
  541. }
  542. }
  543. if (message != null) {
  544. message = "\"" + JsonPaintTarget.escapeJSON(message) + "\"";
  545. }
  546. if (url != null) {
  547. url = "\"" + JsonPaintTarget.escapeJSON(url) + "\"";
  548. }
  549. String output = "for(;;);[{\"changes\":[], \"meta\" : {"
  550. + "\"appError\": {" + "\"caption\":" + caption + ","
  551. + "\"message\" : " + message + "," + "\"url\" : " + url
  552. + "}}, \"resources\": {}, \"locales\":[]}]";
  553. writeResponse(response, "application/json; charset=UTF-8", output);
  554. } else {
  555. // Create an HTML reponse with the error
  556. String output = "";
  557. if (url != null) {
  558. output += "<a href=\"" + url + "\">";
  559. }
  560. if (caption != null) {
  561. output += "<b>" + caption + "</b><br/>";
  562. }
  563. if (message != null) {
  564. output += message;
  565. output += "<br/><br/>";
  566. }
  567. if (details != null) {
  568. output += details;
  569. output += "<br/><br/>";
  570. }
  571. if (url != null) {
  572. output += "</a>";
  573. }
  574. writeResponse(response, "text/html; charset=UTF-8", output);
  575. }
  576. }
  577. /**
  578. * Writes the response in {@code output} using the contentType given in
  579. * {@code contentType} to the provided {@link HttpServletResponse}
  580. *
  581. * @param response
  582. * @param contentType
  583. * @param output
  584. * Output to write (UTF-8 encoded)
  585. * @throws IOException
  586. */
  587. private void writeResponse(HttpServletResponse response,
  588. String contentType, String output) throws IOException {
  589. response.setContentType(contentType);
  590. final ServletOutputStream out = response.getOutputStream();
  591. // Set the response type
  592. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  593. new OutputStreamWriter(out, "UTF-8")));
  594. outWriter.print(output);
  595. outWriter.flush();
  596. outWriter.close();
  597. out.flush();
  598. }
  599. /**
  600. * Returns the application instance to be used for the request. If an
  601. * existing instance is not found a new one is created or null is returned
  602. * to indicate that the application is not available.
  603. *
  604. * @param request
  605. * @param requestType
  606. * @return
  607. * @throws MalformedURLException
  608. * @throws IllegalAccessException
  609. * @throws InstantiationException
  610. * @throws ServletException
  611. * @throws SessionExpiredException
  612. */
  613. private Application findApplicationInstance(HttpServletRequest request,
  614. RequestType requestType) throws MalformedURLException,
  615. ServletException, SessionExpiredException {
  616. boolean requestCanCreateApplication = requestCanCreateApplication(
  617. request, requestType);
  618. /* Find an existing application for this request. */
  619. Application application = getExistingApplication(request,
  620. requestCanCreateApplication);
  621. if (application != null) {
  622. /*
  623. * There is an existing application. We can use this as long as the
  624. * user not specifically requested to close or restart it.
  625. */
  626. final boolean restartApplication = (request
  627. .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null);
  628. final boolean closeApplication = (request
  629. .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null);
  630. if (restartApplication) {
  631. closeApplication(application, request.getSession(false));
  632. return createApplication(request);
  633. } else if (closeApplication) {
  634. closeApplication(application, request.getSession(false));
  635. return null;
  636. } else {
  637. return application;
  638. }
  639. }
  640. // No existing application was found
  641. if (requestCanCreateApplication) {
  642. /*
  643. * If the request is such that it should create a new application if
  644. * one as not found, we do that.
  645. */
  646. return createApplication(request);
  647. } else {
  648. /*
  649. * The application was not found and a new one should not be
  650. * created. Assume the session has expired.
  651. */
  652. throw new SessionExpiredException();
  653. }
  654. }
  655. /**
  656. * Check if the request should create an application if an existing
  657. * application is not found.
  658. *
  659. * @param request
  660. * @param requestType
  661. * @return true if an application should be created, false otherwise
  662. */
  663. boolean requestCanCreateApplication(HttpServletRequest request,
  664. RequestType requestType) {
  665. if (requestType == RequestType.UIDL && isRepaintAll(request)) {
  666. /*
  667. * UIDL request contains valid repaintAll=1 event, the user probably
  668. * wants to initiate a new application through a custom index.html
  669. * without using the bootstrap page.
  670. */
  671. return true;
  672. } else if (requestType == RequestType.OTHER) {
  673. /*
  674. * I.e URIs that are not application resources or static (theme)
  675. * files.
  676. */
  677. return true;
  678. }
  679. return false;
  680. }
  681. /**
  682. * Gets resource path using different implementations. Required to
  683. * supporting different servlet container implementations (application
  684. * servers).
  685. *
  686. * @param servletContext
  687. * @param path
  688. * the resource path.
  689. * @return the resource path.
  690. */
  691. protected static String getResourcePath(ServletContext servletContext,
  692. String path) {
  693. String resultPath = null;
  694. resultPath = servletContext.getRealPath(path);
  695. if (resultPath != null) {
  696. return resultPath;
  697. } else {
  698. try {
  699. final URL url = servletContext.getResource(path);
  700. resultPath = url.getFile();
  701. } catch (final Exception e) {
  702. // FIXME: Handle exception
  703. logger.log(Level.INFO, "Could not find resource path " + path,
  704. e);
  705. }
  706. }
  707. return resultPath;
  708. }
  709. /**
  710. * Creates a new application and registers it into WebApplicationContext
  711. * (aka session). This is not meant to be overridden. Override
  712. * getNewApplication to create the application instance in a custom way.
  713. *
  714. * @param request
  715. * @return
  716. * @throws ServletException
  717. * @throws MalformedURLException
  718. */
  719. private Application createApplication(HttpServletRequest request)
  720. throws ServletException, MalformedURLException {
  721. Application newApplication = getNewApplication(request);
  722. final WebApplicationContext context = getApplicationContext(request
  723. .getSession());
  724. context.addApplication(newApplication);
  725. return newApplication;
  726. }
  727. private void handleServiceException(HttpServletRequest request,
  728. HttpServletResponse response, Application application, Throwable e)
  729. throws IOException, ServletException {
  730. // if this was an UIDL request, response UIDL back to client
  731. if (getRequestType(request) == RequestType.UIDL) {
  732. Application.SystemMessages ci = getSystemMessages();
  733. criticalNotification(request, response,
  734. ci.getInternalErrorCaption(), ci.getInternalErrorMessage(),
  735. null, ci.getInternalErrorURL());
  736. if (application != null) {
  737. application.getErrorHandler()
  738. .terminalError(new RequestError(e));
  739. } else {
  740. throw new ServletException(e);
  741. }
  742. } else {
  743. // Re-throw other exceptions
  744. throw new ServletException(e);
  745. }
  746. }
  747. /**
  748. * A helper method to strip away characters that might somehow be used for
  749. * XSS attacs. Leaves at least alphanumeric characters intact. Also removes
  750. * eg. ( and ), so values should be safe in javascript too.
  751. *
  752. * @param themeName
  753. * @return
  754. */
  755. protected static String stripSpecialChars(String themeName) {
  756. StringBuilder sb = new StringBuilder();
  757. char[] charArray = themeName.toCharArray();
  758. for (int i = 0; i < charArray.length; i++) {
  759. char c = charArray[i];
  760. if (!CHAR_BLACKLIST.contains(c)) {
  761. sb.append(c);
  762. }
  763. }
  764. return sb.toString();
  765. }
  766. private static final Collection<Character> CHAR_BLACKLIST = new HashSet<Character>(
  767. Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')',
  768. ';' }));
  769. /**
  770. * Returns the default theme. Must never return null.
  771. *
  772. * @return
  773. */
  774. public static String getDefaultTheme() {
  775. return DEFAULT_THEME_NAME;
  776. }
  777. void handleServiceSessionExpired(HttpServletRequest request,
  778. HttpServletResponse response) throws IOException, ServletException {
  779. if (isOnUnloadRequest(request)) {
  780. /*
  781. * Request was an unload request (e.g. window close event) and the
  782. * client expects no response if it fails.
  783. */
  784. return;
  785. }
  786. try {
  787. Application.SystemMessages ci = getSystemMessages();
  788. if (getRequestType(request) != RequestType.UIDL) {
  789. // 'plain' http req - e.g. browser reload;
  790. // just go ahead redirect the browser
  791. response.sendRedirect(ci.getSessionExpiredURL());
  792. } else {
  793. /*
  794. * Invalidate session (weird to have session if we're saying
  795. * that it's expired, and worse: portal integration will fail
  796. * since the session is not created by the portal.
  797. *
  798. * Session must be invalidated before criticalNotification as it
  799. * commits the response.
  800. */
  801. request.getSession().invalidate();
  802. // send uidl redirect
  803. criticalNotification(request, response,
  804. ci.getSessionExpiredCaption(),
  805. ci.getSessionExpiredMessage(), null,
  806. ci.getSessionExpiredURL());
  807. }
  808. } catch (SystemMessageException ee) {
  809. throw new ServletException(ee);
  810. }
  811. }
  812. private void handleServiceSecurityException(HttpServletRequest request,
  813. HttpServletResponse response) throws IOException, ServletException {
  814. if (isOnUnloadRequest(request)) {
  815. /*
  816. * Request was an unload request (e.g. window close event) and the
  817. * client expects no response if it fails.
  818. */
  819. return;
  820. }
  821. try {
  822. Application.SystemMessages ci = getSystemMessages();
  823. if (getRequestType(request) != RequestType.UIDL) {
  824. // 'plain' http req - e.g. browser reload;
  825. // just go ahead redirect the browser
  826. response.sendRedirect(ci.getCommunicationErrorURL());
  827. } else {
  828. // send uidl redirect
  829. criticalNotification(request, response,
  830. ci.getCommunicationErrorCaption(),
  831. ci.getCommunicationErrorMessage(),
  832. INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL());
  833. /*
  834. * Invalidate session. Portal integration will fail otherwise
  835. * since the session is not created by the portal.
  836. */
  837. request.getSession().invalidate();
  838. }
  839. } catch (SystemMessageException ee) {
  840. throw new ServletException(ee);
  841. }
  842. log("Invalid security key received from " + request.getRemoteHost());
  843. }
  844. /**
  845. * Creates a new application for the given request.
  846. *
  847. * @param request
  848. * the HTTP request.
  849. * @return A new Application instance.
  850. * @throws ServletException
  851. */
  852. protected abstract Application getNewApplication(HttpServletRequest request)
  853. throws ServletException;
  854. /**
  855. * Starts the application if it is not already running.
  856. *
  857. * @param request
  858. * @param application
  859. * @param webApplicationContext
  860. * @throws ServletException
  861. * @throws MalformedURLException
  862. */
  863. private void startApplication(HttpServletRequest request,
  864. Application application, WebApplicationContext webApplicationContext)
  865. throws ServletException, MalformedURLException {
  866. if (!application.isRunning()) {
  867. // Create application
  868. final URL applicationUrl = getApplicationUrl(request);
  869. // Initial locale comes from the request
  870. Locale locale = request.getLocale();
  871. application.setLocale(locale);
  872. application.start(new ApplicationStartEvent(applicationUrl,
  873. applicationProperties, webApplicationContext,
  874. isProductionMode()));
  875. }
  876. }
  877. /**
  878. * Check if this is a request for a static resource and, if it is, serve the
  879. * resource to the client.
  880. *
  881. * @param request
  882. * @param response
  883. * @return true if a file was served and the request has been handled, false
  884. * otherwise.
  885. * @throws IOException
  886. * @throws ServletException
  887. */
  888. private boolean serveStaticResources(HttpServletRequest request,
  889. HttpServletResponse response) throws IOException, ServletException {
  890. // FIXME What does 10 refer to?
  891. String pathInfo = request.getPathInfo();
  892. if (pathInfo == null || pathInfo.length() <= 10) {
  893. return false;
  894. }
  895. if ((request.getContextPath() != null)
  896. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  897. serveStaticResourcesInVAADIN(request.getRequestURI(), request,
  898. response);
  899. return true;
  900. } else if (request.getRequestURI().startsWith(
  901. request.getContextPath() + "/VAADIN/")) {
  902. serveStaticResourcesInVAADIN(
  903. request.getRequestURI().substring(
  904. request.getContextPath().length()), request,
  905. response);
  906. return true;
  907. }
  908. return false;
  909. }
  910. /**
  911. * Serve resources from VAADIN directory.
  912. *
  913. * @param filename
  914. * The filename to serve. Should always start with /VAADIN/.
  915. * @param request
  916. * @param response
  917. * @throws IOException
  918. * @throws ServletException
  919. */
  920. private void serveStaticResourcesInVAADIN(String filename,
  921. HttpServletRequest request, HttpServletResponse response)
  922. throws IOException, ServletException {
  923. final ServletContext sc = getServletContext();
  924. URL resourceUrl = sc.getResource(filename);
  925. if (resourceUrl == null) {
  926. // try if requested file is found from classloader
  927. // strip leading "/" otherwise stream from JAR wont work
  928. filename = filename.substring(1);
  929. resourceUrl = getClassLoader().getResource(filename);
  930. if (resourceUrl == null) {
  931. // cannot serve requested file
  932. logger.info("Requested resource ["
  933. + filename
  934. + "] not found from filesystem or through class loader."
  935. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.");
  936. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  937. return;
  938. }
  939. // security check: do not permit navigation out of the VAADIN
  940. // directory
  941. if (!isAllowedVAADINResourceUrl(request, resourceUrl)) {
  942. logger.info("Requested resource ["
  943. + filename
  944. + "] not accessible in the VAADIN directory or access to it is forbidden.");
  945. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  946. return;
  947. }
  948. }
  949. // Find the modification timestamp
  950. long lastModifiedTime = 0;
  951. URLConnection connection = null;
  952. try {
  953. connection = resourceUrl.openConnection();
  954. lastModifiedTime = connection.getLastModified();
  955. // Remove milliseconds to avoid comparison problems (milliseconds
  956. // are not returned by the browser in the "If-Modified-Since"
  957. // header).
  958. lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
  959. if (browserHasNewestVersion(request, lastModifiedTime)) {
  960. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  961. return;
  962. }
  963. } catch (Exception e) {
  964. // Failed to find out last modified timestamp. Continue without it.
  965. logger.log(
  966. Level.FINEST,
  967. "Failed to find out last modified timestamp. Continuing without it.",
  968. e);
  969. } finally {
  970. if (connection instanceof URLConnection) {
  971. try {
  972. // Explicitly close the input stream to prevent it
  973. // from remaining hanging
  974. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700
  975. InputStream is = connection.getInputStream();
  976. if (is != null) {
  977. is.close();
  978. }
  979. } catch (IOException e) {
  980. logger.log(Level.INFO,
  981. "Error closing URLConnection input stream", e);
  982. }
  983. }
  984. }
  985. // Set type mime type if we can determine it based on the filename
  986. final String mimetype = sc.getMimeType(filename);
  987. if (mimetype != null) {
  988. response.setContentType(mimetype);
  989. }
  990. // Provide modification timestamp to the browser if it is known.
  991. if (lastModifiedTime > 0) {
  992. response.setDateHeader("Last-Modified", lastModifiedTime);
  993. /*
  994. * The browser is allowed to cache for 1 hour without checking if
  995. * the file has changed. This forces browsers to fetch a new version
  996. * when the Vaadin version is updated. This will cause more requests
  997. * to the servlet than without this but for high volume sites the
  998. * static files should never be served through the servlet. The
  999. * cache timeout can be configured by setting the resourceCacheTime
  1000. * parameter in web.xml
  1001. */
  1002. response.setHeader("Cache-Control",
  1003. "max-age: " + String.valueOf(resourceCacheTime));
  1004. }
  1005. // Write the resource to the client.
  1006. final OutputStream os = response.getOutputStream();
  1007. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  1008. int bytes;
  1009. InputStream is = resourceUrl.openStream();
  1010. while ((bytes = is.read(buffer)) >= 0) {
  1011. os.write(buffer, 0, bytes);
  1012. }
  1013. is.close();
  1014. }
  1015. /**
  1016. * Check whether a URL obtained from a classloader refers to a valid static
  1017. * resource in the directory VAADIN.
  1018. *
  1019. * Warning: Overriding of this method is not recommended, but is possible to
  1020. * support non-default classloaders or servers that may produce URLs
  1021. * different from the normal ones. The method prototype may change in the
  1022. * future. Care should be taken not to expose class files or other resources
  1023. * outside the VAADIN directory if the method is overridden.
  1024. *
  1025. * @param request
  1026. * @param resourceUrl
  1027. * @return
  1028. *
  1029. * @since 6.6.7
  1030. */
  1031. protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request,
  1032. URL resourceUrl) {
  1033. if ("jar".equals(resourceUrl.getProtocol())) {
  1034. // This branch is used for accessing resources directly from the
  1035. // Vaadin JAR in development environments and in similar cases.
  1036. // Inside a JAR, a ".." would mean a real directory named ".." so
  1037. // using it in paths should just result in the file not being found.
  1038. // However, performing a check in case some servers or class loaders
  1039. // try to normalize the path by collapsing ".." before the class
  1040. // loader sees it.
  1041. if (!resourceUrl.getPath().contains("!/VAADIN/")) {
  1042. logger.info("Blocked attempt to access a JAR entry not starting with /VAADIN/: "
  1043. + resourceUrl);
  1044. return false;
  1045. }
  1046. logger.fine("Accepted access to a JAR entry using a class loader: "
  1047. + resourceUrl);
  1048. return true;
  1049. } else {
  1050. // Some servers such as GlassFish extract files from JARs (file:)
  1051. // and e.g. JBoss 5+ use protocols vsf: and vfsfile: .
  1052. // Check that the URL is in a VAADIN directory and does not contain
  1053. // "/../"
  1054. if (!resourceUrl.getPath().contains("/VAADIN/")
  1055. || resourceUrl.getPath().contains("/../")) {
  1056. logger.info("Blocked attempt to access file: " + resourceUrl);
  1057. return false;
  1058. }
  1059. logger.fine("Accepted access to a file using a class loader: "
  1060. + resourceUrl);
  1061. return true;
  1062. }
  1063. }
  1064. /**
  1065. * Checks if the browser has an up to date cached version of requested
  1066. * resource. Currently the check is performed using the "If-Modified-Since"
  1067. * header. Could be expanded if needed.
  1068. *
  1069. * @param request
  1070. * The HttpServletRequest from the browser.
  1071. * @param resourceLastModifiedTimestamp
  1072. * The timestamp when the resource was last modified. 0 if the
  1073. * last modification time is unknown.
  1074. * @return true if the If-Modified-Since header tells the cached version in
  1075. * the browser is up to date, false otherwise
  1076. */
  1077. private boolean browserHasNewestVersion(HttpServletRequest request,
  1078. long resourceLastModifiedTimestamp) {
  1079. if (resourceLastModifiedTimestamp < 1) {
  1080. // We do not know when it was modified so the browser cannot have an
  1081. // up-to-date version
  1082. return false;
  1083. }
  1084. /*
  1085. * The browser can request the resource conditionally using an
  1086. * If-Modified-Since header. Check this against the last modification
  1087. * time.
  1088. */
  1089. try {
  1090. // If-Modified-Since represents the timestamp of the version cached
  1091. // in the browser
  1092. long headerIfModifiedSince = request
  1093. .getDateHeader("If-Modified-Since");
  1094. if (headerIfModifiedSince >= resourceLastModifiedTimestamp) {
  1095. // Browser has this an up-to-date version of the resource
  1096. return true;
  1097. }
  1098. } catch (Exception e) {
  1099. // Failed to parse header. Fail silently - the browser does not have
  1100. // an up-to-date version in its cache.
  1101. }
  1102. return false;
  1103. }
  1104. protected enum RequestType {
  1105. FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE;
  1106. }
  1107. protected RequestType getRequestType(HttpServletRequest request) {
  1108. if (isFileUploadRequest(request)) {
  1109. return RequestType.FILE_UPLOAD;
  1110. } else if (isBrowserDetailsRequest(request)) {
  1111. return RequestType.BROWSER_DETAILS;
  1112. } else if (isUIDLRequest(request)) {
  1113. return RequestType.UIDL;
  1114. } else if (isStaticResourceRequest(request)) {
  1115. return RequestType.STATIC_FILE;
  1116. } else if (isApplicationRequest(request)) {
  1117. return RequestType.APPLICATION_RESOURCE;
  1118. } else if (request.getHeader("FileId") != null) {
  1119. return RequestType.FILE_UPLOAD;
  1120. }
  1121. return RequestType.OTHER;
  1122. }
  1123. private static boolean isBrowserDetailsRequest(HttpServletRequest request) {
  1124. return "POST".equals(request.getMethod())
  1125. && request.getParameter("browserDetails") != null;
  1126. }
  1127. private boolean isApplicationRequest(HttpServletRequest request) {
  1128. String path = getRequestPathInfo(request);
  1129. if (path != null && path.startsWith("/APP/")) {
  1130. return true;
  1131. }
  1132. return false;
  1133. }
  1134. private boolean isStaticResourceRequest(HttpServletRequest request) {
  1135. String pathInfo = request.getPathInfo();
  1136. if (pathInfo == null || pathInfo.length() <= 10) {
  1137. return false;
  1138. }
  1139. if ((request.getContextPath() != null)
  1140. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  1141. return true;
  1142. } else if (request.getRequestURI().startsWith(
  1143. request.getContextPath() + "/VAADIN/")) {
  1144. return true;
  1145. }
  1146. return false;
  1147. }
  1148. private boolean isUIDLRequest(HttpServletRequest request) {
  1149. String pathInfo = getRequestPathInfo(request);
  1150. if (pathInfo == null) {
  1151. return false;
  1152. }
  1153. String compare = AJAX_UIDL_URI;
  1154. if (pathInfo.startsWith(compare + "/") || pathInfo.endsWith(compare)) {
  1155. return true;
  1156. }
  1157. return false;
  1158. }
  1159. private boolean isFileUploadRequest(HttpServletRequest request) {
  1160. String pathInfo = getRequestPathInfo(request);
  1161. if (pathInfo == null) {
  1162. return false;
  1163. }
  1164. if (pathInfo.startsWith("/" + UPLOAD_URL_PREFIX)) {
  1165. return true;
  1166. }
  1167. return false;
  1168. }
  1169. private boolean isOnUnloadRequest(HttpServletRequest request) {
  1170. return request.getParameter(ApplicationConnection.PARAM_UNLOADBURST) != null;
  1171. }
  1172. /**
  1173. * Get system messages from the current application class
  1174. *
  1175. * @return
  1176. */
  1177. protected SystemMessages getSystemMessages() {
  1178. Class<? extends Application> appCls = null;
  1179. try {
  1180. appCls = getApplicationClass();
  1181. } catch (ClassNotFoundException e) {
  1182. // Previous comment claimed that this should never happen
  1183. throw new SystemMessageException(e);
  1184. }
  1185. return getSystemMessages(appCls);
  1186. }
  1187. public static SystemMessages getSystemMessages(
  1188. Class<? extends Application> appCls) {
  1189. try {
  1190. if (appCls != null) {
  1191. Method m = appCls
  1192. .getMethod("getSystemMessages", (Class[]) null);
  1193. return (Application.SystemMessages) m.invoke(null,
  1194. (Object[]) null);
  1195. }
  1196. } catch (SecurityException e) {
  1197. throw new SystemMessageException(
  1198. "Application.getSystemMessage() should be static public", e);
  1199. } catch (NoSuchMethodException e) {
  1200. // This is completely ok and should be silently ignored
  1201. } catch (IllegalArgumentException e) {
  1202. // This should never happen
  1203. throw new SystemMessageException(e);
  1204. } catch (IllegalAccessException e) {
  1205. throw new SystemMessageException(
  1206. "Application.getSystemMessage() should be static public", e);
  1207. } catch (InvocationTargetException e) {
  1208. // This should never happen
  1209. throw new SystemMessageException(e);
  1210. }
  1211. return Application.getSystemMessages();
  1212. }
  1213. protected abstract Class<? extends Application> getApplicationClass()
  1214. throws ClassNotFoundException;
  1215. /**
  1216. * Return the URL from where static files, e.g. the widgetset and the theme,
  1217. * are served. In a standard configuration the VAADIN folder inside the
  1218. * returned folder is what is used for widgetsets and themes.
  1219. *
  1220. * The returned folder is usually the same as the context path and
  1221. * independent of the application.
  1222. *
  1223. * @param request
  1224. * @return The location of static resources (should contain the VAADIN
  1225. * directory). Never ends with a slash (/).
  1226. */
  1227. protected String getStaticFilesLocation(HttpServletRequest request) {
  1228. return getWebApplicationsStaticFileLocation(request);
  1229. }
  1230. /**
  1231. * The default method to fetch static files location (URL). This method does
  1232. * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH}
  1233. *
  1234. * @param request
  1235. * @return
  1236. */
  1237. private String getWebApplicationsStaticFileLocation(
  1238. HttpServletRequest request) {
  1239. String staticFileLocation;
  1240. // if property is defined in configurations, use that
  1241. staticFileLocation = getApplicationOrSystemProperty(
  1242. PARAMETER_VAADIN_RESOURCES, null);
  1243. if (staticFileLocation != null) {
  1244. return staticFileLocation;
  1245. }
  1246. // the last (but most common) option is to generate default location
  1247. // from request
  1248. // if context is specified add it to widgetsetUrl
  1249. String ctxPath = request.getContextPath();
  1250. // FIXME: ctxPath.length() == 0 condition is probably unnecessary and
  1251. // might even be wrong.
  1252. if (ctxPath.length() == 0
  1253. && request.getAttribute("javax.servlet.include.context_path") != null) {
  1254. // include request (e.g portlet), get context path from
  1255. // attribute
  1256. ctxPath = (String) request
  1257. .getAttribute("javax.servlet.include.context_path");
  1258. }
  1259. // Remove heading and trailing slashes from the context path
  1260. ctxPath = removeHeadingOrTrailing(ctxPath, "/");
  1261. if (ctxPath.equals("")) {
  1262. return "";
  1263. } else {
  1264. return "/" + ctxPath;
  1265. }
  1266. }
  1267. /**
  1268. * Remove any heading or trailing "what" from the "string".
  1269. *
  1270. * @param string
  1271. * @param what
  1272. * @return
  1273. */
  1274. private static String removeHeadingOrTrailing(String string, String what) {
  1275. while (string.startsWith(what)) {
  1276. string = string.substring(1);
  1277. }
  1278. while (string.endsWith(what)) {
  1279. string = string.substring(0, string.length() - 1);
  1280. }
  1281. return string;
  1282. }
  1283. /**
  1284. * Write a redirect response to the main page of the application.
  1285. *
  1286. * @param request
  1287. * @param response
  1288. * @throws IOException
  1289. * if sending the redirect fails due to an input/output error or
  1290. * a bad application URL
  1291. */
  1292. private void redirectToApplication(HttpServletRequest request,
  1293. HttpServletResponse response) throws IOException {
  1294. String applicationUrl = getApplicationUrl(request).toExternalForm();
  1295. response.sendRedirect(response.encodeRedirectURL(applicationUrl));
  1296. }
  1297. /**
  1298. * Gets the current application URL from request.
  1299. *
  1300. * @param request
  1301. * the HTTP request.
  1302. * @throws MalformedURLException
  1303. * if the application is denied access to the persistent data
  1304. * store represented by the given URL.
  1305. */
  1306. protected URL getApplicationUrl(HttpServletRequest request)
  1307. throws MalformedURLException {
  1308. final URL reqURL = new URL(
  1309. (request.isSecure() ? "https://" : "http://")
  1310. + request.getServerName()
  1311. + ((request.isSecure() && request.getServerPort() == 443)
  1312. || (!request.isSecure() && request
  1313. .getServerPort() == 80) ? "" : ":"
  1314. + request.getServerPort())
  1315. + request.getRequestURI());
  1316. String servletPath = "";
  1317. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  1318. // this is an include request
  1319. servletPath = request.getAttribute(
  1320. "javax.servlet.include.context_path").toString()
  1321. + request
  1322. .getAttribute("javax.servlet.include.servlet_path");
  1323. } else {
  1324. servletPath = request.getContextPath() + request.getServletPath();
  1325. }
  1326. if (servletPath.length() == 0
  1327. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1328. servletPath = servletPath + "/";
  1329. }
  1330. URL u = new URL(reqURL, servletPath);
  1331. return u;
  1332. }
  1333. /**
  1334. * Gets the existing application for given request. Looks for application
  1335. * instance for given request based on the requested URL.
  1336. *
  1337. * @param request
  1338. * the HTTP request.
  1339. * @param allowSessionCreation
  1340. * true if a session should be created if no session exists,
  1341. * false if no session should be created
  1342. * @return Application instance, or null if the URL does not map to valid
  1343. * application.
  1344. * @throws MalformedURLException
  1345. * if the application is denied access to the persistent data
  1346. * store represented by the given URL.
  1347. * @throws IllegalAccessException
  1348. * @throws InstantiationException
  1349. * @throws SessionExpiredException
  1350. */
  1351. protected Application getExistingApplication(HttpServletRequest request,
  1352. boolean allowSessionCreation) throws MalformedURLException,
  1353. SessionExpiredException {
  1354. // Ensures that the session is still valid
  1355. final HttpSession session = request.getSession(allowSessionCreation);
  1356. if (session == null) {
  1357. throw new SessionExpiredException();
  1358. }
  1359. WebApplicationContext context = getApplicationContext(session);
  1360. // Gets application list for the session.
  1361. final Collection<Application> applications = context.getApplications();
  1362. // Search for the application (using the application URI) from the list
  1363. for (final Iterator<Application> i = applications.iterator(); i
  1364. .hasNext();) {
  1365. final Application sessionApplication = i.next();
  1366. final String sessionApplicationPath = sessionApplication.getURL()
  1367. .getPath();
  1368. String requestApplicationPath = getApplicationUrl(request)
  1369. .getPath();
  1370. if (requestApplicationPath.equals(sessionApplicationPath)) {
  1371. // Found a running application
  1372. if (sessionApplication.isRunning()) {
  1373. return sessionApplication;
  1374. }
  1375. // Application has stopped, so remove it before creating a new
  1376. // application
  1377. getApplicationContext(session).removeApplication(
  1378. sessionApplication);
  1379. break;
  1380. }
  1381. }
  1382. // Existing application not found
  1383. return null;
  1384. }
  1385. /**
  1386. * Ends the application.
  1387. *
  1388. * @param request
  1389. * the HTTP request.
  1390. * @param response
  1391. * the HTTP response to write to.
  1392. * @param application
  1393. * the application to end.
  1394. * @throws IOException
  1395. * if the writing failed due to input/output error.
  1396. */
  1397. private void endApplication(HttpServletRequest request,
  1398. HttpServletResponse response, Application application)
  1399. throws IOException {
  1400. String logoutUrl = application.getLogoutURL();
  1401. if (logoutUrl == null) {
  1402. logoutUrl = application.getURL().toString();
  1403. }
  1404. final HttpSession session = request.getSession();
  1405. if (session != null) {
  1406. getApplicationContext(session).removeApplication(application);
  1407. }
  1408. response.sendRedirect(response.encodeRedirectURL(logoutUrl));
  1409. }
  1410. /**
  1411. * Returns the path info; note that this _can_ be different than
  1412. * request.getPathInfo(). Examples where this might be useful:
  1413. * <ul>
  1414. * <li>An application runner servlet that runs different Vaadin applications
  1415. * based on an identifier.</li>
  1416. * <li>Providing a REST interface in the context root, while serving a
  1417. * Vaadin UI on a sub-URI using only one servlet (e.g. REST on
  1418. * http://example.com/foo, UI on http://example.com/foo/vaadin)</li>
  1419. *
  1420. * @param request
  1421. * @return
  1422. */
  1423. protected String getRequestPathInfo(HttpServletRequest request) {
  1424. return request.getPathInfo();
  1425. }
  1426. /**
  1427. * Gets relative location of a theme resource.
  1428. *
  1429. * @param theme
  1430. * the Theme name.
  1431. * @param resource
  1432. * the Theme resource.
  1433. * @return External URI specifying the resource
  1434. */
  1435. public String getResourceLocation(String theme, ThemeResource resource) {
  1436. if (resourcePath == null) {
  1437. return resource.getResourceId();
  1438. }
  1439. return resourcePath + theme + "/" + resource.getResourceId();
  1440. }
  1441. private boolean isRepaintAll(HttpServletRequest request) {
  1442. return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null)
  1443. && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1"));
  1444. }
  1445. private void closeApplication(Application application, HttpSession session) {
  1446. if (application == null) {
  1447. return;
  1448. }
  1449. application.close();
  1450. if (session != null) {
  1451. WebApplicationContext context = getApplicationContext(session);
  1452. context.removeApplication(application);
  1453. }
  1454. }
  1455. /**
  1456. *
  1457. * Gets the application context from an HttpSession. If no context is
  1458. * currently stored in a session a new context is created and stored in the
  1459. * session.
  1460. *
  1461. * @param session
  1462. * the HTTP session.
  1463. * @return the application context for HttpSession.
  1464. */
  1465. protected WebApplicationContext getApplicationContext(HttpSession session) {
  1466. /*
  1467. * TODO the ApplicationContext.getApplicationContext() should be removed
  1468. * and logic moved here. Now overriding context type is possible, but
  1469. * the whole creation logic should be here. MT 1101
  1470. */
  1471. return WebApplicationContext.getApplicationContext(session);
  1472. }
  1473. public class RequestError implements Terminal.ErrorEvent, Serializable {
  1474. private final Throwable throwable;
  1475. public RequestError(Throwable throwable) {
  1476. this.throwable = throwable;
  1477. }
  1478. public Throwable getThrowable() {
  1479. return throwable;
  1480. }
  1481. }
  1482. /**
  1483. * Override this method if you need to use a specialized communicaiton
  1484. * mananger implementation.
  1485. *
  1486. * @deprecated Instead of overriding this method, override
  1487. * {@link WebApplicationContext} implementation via
  1488. * {@link AbstractApplicationServlet#getApplicationContext(HttpSession)}
  1489. * method and in that customized implementation return your
  1490. * CommunicationManager in
  1491. * {@link WebApplicationContext#getApplicationManager(Application, AbstractApplicationServlet)}
  1492. * method.
  1493. *
  1494. * @param application
  1495. * @return
  1496. */
  1497. @Deprecated
  1498. public CommunicationManager createCommunicationManager(
  1499. Application application) {
  1500. return new CommunicationManager(application);
  1501. }
  1502. /**
  1503. * Escapes characters to html entities. An exception is made for some
  1504. * "safe characters" to keep the text somewhat readable.
  1505. *
  1506. * @param unsafe
  1507. * @return a safe string to be added inside an html tag
  1508. */
  1509. public static final String safeEscapeForHtml(String unsafe) {
  1510. if (null == unsafe) {
  1511. return null;
  1512. }
  1513. StringBuilder safe = new StringBuilder();
  1514. char[] charArray = unsafe.toCharArray();
  1515. for (int i = 0; i < charArray.length; i++) {
  1516. char c = charArray[i];
  1517. if (isSafe(c)) {
  1518. safe.append(c);
  1519. } else {
  1520. safe.append("&#");
  1521. safe.append((int) c);
  1522. safe.append(";");
  1523. }
  1524. }
  1525. return safe.toString();
  1526. }
  1527. private static boolean isSafe(char c) {
  1528. return //
  1529. c > 47 && c < 58 || // alphanum
  1530. c > 64 && c < 91 || // A-Z
  1531. c > 96 && c < 123 // a-z
  1532. ;
  1533. }
  1534. }