Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

AbstractApplicationServlet.java 58KB

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