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.

AbstractApplicationPortlet.java 46KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. package com.vaadin.terminal.gwt.server;
  2. import java.io.BufferedWriter;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.OutputStream;
  6. import java.io.OutputStreamWriter;
  7. import java.io.PrintWriter;
  8. import java.io.Serializable;
  9. import java.lang.reflect.InvocationTargetException;
  10. import java.lang.reflect.Method;
  11. import java.net.MalformedURLException;
  12. import java.security.GeneralSecurityException;
  13. import java.util.Date;
  14. import java.util.Enumeration;
  15. import java.util.Iterator;
  16. import java.util.Locale;
  17. import java.util.Map;
  18. import java.util.Properties;
  19. import javax.portlet.ActionRequest;
  20. import javax.portlet.ActionResponse;
  21. import javax.portlet.EventRequest;
  22. import javax.portlet.EventResponse;
  23. import javax.portlet.GenericPortlet;
  24. import javax.portlet.MimeResponse;
  25. import javax.portlet.PortalContext;
  26. import javax.portlet.PortletConfig;
  27. import javax.portlet.PortletContext;
  28. import javax.portlet.PortletException;
  29. import javax.portlet.PortletMode;
  30. import javax.portlet.PortletRequest;
  31. import javax.portlet.PortletResponse;
  32. import javax.portlet.PortletSession;
  33. import javax.portlet.PortletURL;
  34. import javax.portlet.RenderMode;
  35. import javax.portlet.RenderRequest;
  36. import javax.portlet.RenderResponse;
  37. import javax.portlet.ResourceRequest;
  38. import javax.portlet.ResourceResponse;
  39. import javax.portlet.ResourceURL;
  40. import javax.servlet.http.HttpServletResponse;
  41. import com.liferay.portal.kernel.util.PropsUtil;
  42. import com.vaadin.Application;
  43. import com.vaadin.Application.SystemMessages;
  44. import com.vaadin.external.org.apache.commons.fileupload.portlet.PortletFileUpload;
  45. import com.vaadin.terminal.DownloadStream;
  46. import com.vaadin.terminal.Terminal;
  47. import com.vaadin.ui.Window;
  48. /**
  49. * TODO Document me!
  50. *
  51. * @author peholmst
  52. */
  53. public abstract class AbstractApplicationPortlet extends GenericPortlet
  54. implements Constants {
  55. /**
  56. * This portlet parameter is used to add styles to the main element. E.g
  57. * "height:500px" generates a style="height:500px" to the main element.
  58. */
  59. public static final String PORTLET_PARAMETER_STYLE = "style";
  60. private static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme";
  61. // TODO some parts could be shared with AbstractApplicationServlet
  62. // TODO Can we close the application when the portlet is removed? Do we know
  63. // when the portlet is removed?
  64. // TODO What happens when the portlet window is resized? Do we know when the
  65. // window is resized?
  66. private Properties applicationProperties;
  67. private boolean productionMode = false;
  68. @SuppressWarnings("unchecked")
  69. @Override
  70. public void init(PortletConfig config) throws PortletException {
  71. super.init(config);
  72. // Stores the application parameters into Properties object
  73. applicationProperties = new Properties();
  74. for (final Enumeration e = config.getInitParameterNames(); e
  75. .hasMoreElements();) {
  76. final String name = (String) e.nextElement();
  77. applicationProperties.setProperty(name, config
  78. .getInitParameter(name));
  79. }
  80. // Overrides with server.xml parameters
  81. final PortletContext context = config.getPortletContext();
  82. for (final Enumeration e = context.getInitParameterNames(); e
  83. .hasMoreElements();) {
  84. final String name = (String) e.nextElement();
  85. applicationProperties.setProperty(name, context
  86. .getInitParameter(name));
  87. }
  88. checkProductionMode();
  89. checkCrossSiteProtection();
  90. }
  91. private void checkCrossSiteProtection() {
  92. if (getApplicationOrSystemProperty(
  93. SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, "false").equals(
  94. "true")) {
  95. /*
  96. * Print an information/warning message about running with xsrf
  97. * protection disabled
  98. */
  99. System.err.println(WARNING_XSRF_PROTECTION_DISABLED);
  100. }
  101. }
  102. private void checkProductionMode() {
  103. // Check if the application is in production mode.
  104. // We are in production mode if Debug=false or productionMode=true
  105. if (getApplicationOrSystemProperty(SERVLET_PARAMETER_DEBUG, "true")
  106. .equals("false")) {
  107. // "Debug=true" is the old way and should no longer be used
  108. productionMode = true;
  109. } else if (getApplicationOrSystemProperty(
  110. SERVLET_PARAMETER_PRODUCTION_MODE, "false").equals("true")) {
  111. // "productionMode=true" is the real way to do it
  112. productionMode = true;
  113. }
  114. if (!productionMode) {
  115. /* Print an information/warning message about running in debug mode */
  116. // TODO Maybe we need a different message for portlets?
  117. System.err.println(NOT_PRODUCTION_MODE_INFO);
  118. }
  119. }
  120. /**
  121. * Gets an application property value.
  122. *
  123. * @param parameterName
  124. * the Name or the parameter.
  125. * @return String value or null if not found
  126. */
  127. protected String getApplicationProperty(String parameterName) {
  128. String val = applicationProperties.getProperty(parameterName);
  129. if (val != null) {
  130. return val;
  131. }
  132. // Try lower case application properties for backward compatibility with
  133. // 3.0.2 and earlier
  134. val = applicationProperties.getProperty(parameterName.toLowerCase());
  135. return val;
  136. }
  137. /**
  138. * Gets an system property value.
  139. *
  140. * @param parameterName
  141. * the Name or the parameter.
  142. * @return String value or null if not found
  143. */
  144. protected String getSystemProperty(String parameterName) {
  145. String val = null;
  146. String pkgName;
  147. final Package pkg = getClass().getPackage();
  148. if (pkg != null) {
  149. pkgName = pkg.getName();
  150. } else {
  151. final String className = getClass().getName();
  152. pkgName = new String(className.toCharArray(), 0, className
  153. .lastIndexOf('.'));
  154. }
  155. val = System.getProperty(pkgName + "." + parameterName);
  156. if (val != null) {
  157. return val;
  158. }
  159. // Try lowercased system properties
  160. val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
  161. return val;
  162. }
  163. /**
  164. * Gets an application or system property value.
  165. *
  166. * @param parameterName
  167. * the Name or the parameter.
  168. * @param defaultValue
  169. * the Default to be used.
  170. * @return String value or default if not found
  171. */
  172. private String getApplicationOrSystemProperty(String parameterName,
  173. String defaultValue) {
  174. String val = null;
  175. // Try application properties
  176. val = getApplicationProperty(parameterName);
  177. if (val != null) {
  178. return val;
  179. }
  180. // Try system properties
  181. val = getSystemProperty(parameterName);
  182. if (val != null) {
  183. return val;
  184. }
  185. return defaultValue;
  186. }
  187. /**
  188. * Return the URL from where static files, e.g. the widgetset and the theme,
  189. * are served. In a standard configuration the VAADIN folder inside the
  190. * returned folder is what is used for widgetsets and themes.
  191. *
  192. * @param request
  193. * @return The location of static resources (inside which there should be a
  194. * VAADIN directory). Does not end with a slash (/).
  195. */
  196. protected String getStaticFilesLocation(PortletRequest request) {
  197. // TODO allow overriding on portlet level?
  198. String staticFileLocation = getPortalProperty(
  199. Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH, request
  200. .getPortalContext());
  201. if (staticFileLocation != null) {
  202. // remove trailing slash if any
  203. while (staticFileLocation.endsWith(".")) {
  204. staticFileLocation = staticFileLocation.substring(0,
  205. staticFileLocation.length() - 1);
  206. }
  207. return staticFileLocation;
  208. } else {
  209. // default for Liferay
  210. return "/html";
  211. }
  212. }
  213. enum RequestType {
  214. FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN;
  215. }
  216. protected RequestType getRequestType(PortletRequest request) {
  217. if (request instanceof RenderRequest) {
  218. return RequestType.RENDER;
  219. } else if (request instanceof ResourceRequest) {
  220. if (isUIDLRequest((ResourceRequest) request)) {
  221. return RequestType.UIDL;
  222. } else if (isApplicationResourceRequest((ResourceRequest) request)) {
  223. return RequestType.APPLICATION_RESOURCE;
  224. } else if (isDummyRequest((ResourceRequest) request)) {
  225. return RequestType.DUMMY;
  226. } else {
  227. return RequestType.STATIC_FILE;
  228. }
  229. } else if (request instanceof ActionRequest) {
  230. if (isFileUploadRequest((ActionRequest) request)) {
  231. return RequestType.FILE_UPLOAD;
  232. } else {
  233. // action other than upload
  234. return RequestType.ACTION;
  235. }
  236. } else if (request instanceof EventRequest) {
  237. return RequestType.EVENT;
  238. }
  239. return RequestType.UNKNOWN;
  240. }
  241. private boolean isApplicationResourceRequest(ResourceRequest request) {
  242. return request.getResourceID() != null
  243. && request.getResourceID().startsWith("APP");
  244. }
  245. private boolean isUIDLRequest(ResourceRequest request) {
  246. return request.getResourceID() != null
  247. && request.getResourceID().equals("UIDL");
  248. }
  249. private boolean isDummyRequest(ResourceRequest request) {
  250. return request.getResourceID() != null
  251. && request.getResourceID().equals("DUMMY");
  252. }
  253. private boolean isFileUploadRequest(ActionRequest request) {
  254. return PortletFileUpload.isMultipartContent(request);
  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. protected void handleRequest(PortletRequest request,
  266. PortletResponse response) throws PortletException, IOException {
  267. RequestType requestType = getRequestType(request);
  268. if (requestType == RequestType.UNKNOWN) {
  269. System.err.println("Unknown request type");
  270. } else if (requestType == RequestType.DUMMY) {
  271. /*
  272. * This dummy page is used by action responses to redirect to, in
  273. * order to prevent the boot strap code from being rendered into
  274. * strange places such as iframes.
  275. */
  276. ((ResourceResponse) response).setContentType("text/html");
  277. final OutputStream out = ((ResourceResponse) response)
  278. .getPortletOutputStream();
  279. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  280. new OutputStreamWriter(out, "UTF-8")));
  281. outWriter.print("<html><body>dummy page</body></html>");
  282. outWriter.flush();
  283. out.close();
  284. } else if (requestType == RequestType.STATIC_FILE) {
  285. serveStaticResources((ResourceRequest) request,
  286. (ResourceResponse) response);
  287. } else {
  288. Application application = null;
  289. boolean transactionStarted = false;
  290. boolean requestStarted = false;
  291. try {
  292. // TODO What about PARAM_UNLOADBURST & redirectToApplication??
  293. /* Find out which application this request is related to */
  294. application = findApplicationInstance(request, requestType);
  295. if (application == null) {
  296. return;
  297. }
  298. /*
  299. * Get or create an application context and an application
  300. * manager for the session
  301. */
  302. PortletApplicationContext2 applicationContext = PortletApplicationContext2
  303. .getApplicationContext(request.getPortletSession());
  304. applicationContext.setResponse(response);
  305. PortletCommunicationManager applicationManager = applicationContext
  306. .getApplicationManager(application);
  307. if (response instanceof RenderResponse
  308. && applicationManager.dummyURL == null) {
  309. /*
  310. * The application manager needs an URL to the dummy page.
  311. * See the PortletCommunicationManager.sendUploadResponse
  312. * method for more information.
  313. */
  314. ResourceURL dummyURL = ((RenderResponse) response)
  315. .createResourceURL();
  316. dummyURL.setResourceID("DUMMY");
  317. applicationManager.dummyURL = dummyURL.toString();
  318. }
  319. /* Update browser information from request */
  320. updateBrowserProperties(applicationContext.getBrowser(),
  321. request);
  322. /*
  323. * Call application requestStart before Application.init() is
  324. * called (bypasses the limitation in TransactionListener)
  325. */
  326. if (application instanceof PortletRequestListener) {
  327. ((PortletRequestListener) application).onRequestStart(
  328. request, response);
  329. requestStarted = true;
  330. }
  331. /* Start the newly created application */
  332. startApplication(request, application, applicationContext);
  333. /*
  334. * Transaction starts. Call transaction listeners. Transaction
  335. * end is called in the finally block below.
  336. */
  337. applicationContext.startTransaction(application, request);
  338. transactionStarted = true;
  339. /* Notify listeners */
  340. // TODO Should this happen before or after the transaction
  341. // starts?
  342. if (request instanceof RenderRequest) {
  343. applicationContext.firePortletRenderRequest(application,
  344. (RenderRequest) request, (RenderResponse) response);
  345. } else if (request instanceof ActionRequest) {
  346. applicationContext.firePortletActionRequest(application,
  347. (ActionRequest) request, (ActionResponse) response);
  348. } else if (request instanceof EventRequest) {
  349. applicationContext.firePortletEventRequest(application,
  350. (EventRequest) request, (EventResponse) response);
  351. } else if (request instanceof ResourceRequest) {
  352. applicationContext.firePortletResourceRequest(application,
  353. (ResourceRequest) request,
  354. (ResourceResponse) response);
  355. }
  356. /* Handle the request */
  357. if (requestType == RequestType.FILE_UPLOAD) {
  358. applicationManager.handleFileUpload(
  359. (ActionRequest) request, (ActionResponse) response);
  360. return;
  361. } else if (requestType == RequestType.UIDL) {
  362. // Handles AJAX UIDL requests
  363. applicationManager.handleUidlRequest(
  364. (ResourceRequest) request,
  365. (ResourceResponse) response, this);
  366. return;
  367. } else {
  368. /*
  369. * Removes the application if it has stopped
  370. */
  371. if (!application.isRunning()) {
  372. endApplication(request, response, application);
  373. return;
  374. }
  375. /*
  376. * Always use the main window when running inside a portlet.
  377. */
  378. Window window = getPortletWindow(request, application);
  379. if (window == null) {
  380. throw new PortletException(ERROR_NO_WINDOW_FOUND);
  381. }
  382. /*
  383. * Sets terminal type for the window, if not already set
  384. */
  385. if (window.getTerminal() == null) {
  386. window.setTerminal(applicationContext.getBrowser());
  387. }
  388. /*
  389. * Handle parameters
  390. */
  391. final Map<String, String[]> parameters = request
  392. .getParameterMap();
  393. if (window != null && parameters != null) {
  394. window.handleParameters(parameters);
  395. }
  396. if (requestType == RequestType.APPLICATION_RESOURCE) {
  397. handleURI(applicationManager, window,
  398. (ResourceRequest) request,
  399. (ResourceResponse) response);
  400. } else if (requestType == RequestType.RENDER) {
  401. writeAjaxPage((RenderRequest) request,
  402. (RenderResponse) response, window, application);
  403. } else if (requestType == RequestType.EVENT) {
  404. // nothing to do, listeners do all the work
  405. } else if (requestType == RequestType.ACTION) {
  406. // nothing to do, listeners do all the work
  407. } else {
  408. throw new IllegalStateException(
  409. "handleRequest() without anything to do - should never happen!");
  410. }
  411. }
  412. } catch (final SessionExpiredException e) {
  413. // TODO Figure out a better way to deal with
  414. // SessionExpiredExceptions
  415. System.err.println("Session has expired");
  416. e.printStackTrace(System.err);
  417. } catch (final GeneralSecurityException e) {
  418. // TODO Figure out a better way to deal with
  419. // GeneralSecurityExceptions
  420. System.err
  421. .println("General security exception, should never happen");
  422. e.printStackTrace(System.err);
  423. } catch (final Throwable e) {
  424. handleServiceException(request, response, application, e);
  425. } finally {
  426. // Notifies transaction end
  427. try {
  428. if (transactionStarted) {
  429. ((PortletApplicationContext2) application.getContext())
  430. .endTransaction(application, request);
  431. }
  432. } finally {
  433. if (requestStarted) {
  434. ((PortletRequestListener) application).onRequestEnd(
  435. request, response);
  436. }
  437. }
  438. }
  439. }
  440. }
  441. /**
  442. * Returns a window for a portlet mode. To define custom content for a
  443. * portlet mode, add (in the application) a window whose name matches the
  444. * portlet mode name. By default, the main window is returned.
  445. *
  446. * Alternatively, a PortletListener can change the main window content.
  447. *
  448. * @param request
  449. * @param application
  450. * @return Window to show in the portlet for the given portlet mode
  451. */
  452. protected Window getPortletWindow(PortletRequest request,
  453. Application application) {
  454. PortletMode mode = request.getPortletMode();
  455. Window window = application.getWindow(mode.toString());
  456. if (window != null) {
  457. return window;
  458. }
  459. // no specific window found
  460. return application.getMainWindow();
  461. }
  462. private void updateBrowserProperties(WebBrowser browser,
  463. PortletRequest request) {
  464. browser.updateBrowserProperties(request.getLocale(), null, request
  465. .isSecure(), request.getProperty("user-agent"), request
  466. .getParameter("sw"), request.getParameter("sh"));
  467. }
  468. @Override
  469. public void processEvent(EventRequest request, EventResponse response)
  470. throws PortletException, IOException {
  471. handleRequest(request, response);
  472. }
  473. private boolean handleURI(PortletCommunicationManager applicationManager,
  474. Window window, ResourceRequest request, ResourceResponse response)
  475. throws IOException {
  476. // Handles the URI
  477. DownloadStream download = applicationManager.handleURI(window, request,
  478. response, this);
  479. // A download request
  480. if (download != null) {
  481. // Client downloads an resource
  482. handleDownload(download, request, response);
  483. return true;
  484. }
  485. return false;
  486. }
  487. @SuppressWarnings("unchecked")
  488. private void handleDownload(DownloadStream stream, ResourceRequest request,
  489. ResourceResponse response) throws IOException {
  490. if (stream.getParameter("Location") != null) {
  491. response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer
  492. .toString(HttpServletResponse.SC_MOVED_TEMPORARILY));
  493. response.setProperty("Location", stream.getParameter("Location"));
  494. return;
  495. }
  496. // Download from given stream
  497. final InputStream data = stream.getStream();
  498. if (data != null) {
  499. // Sets content type
  500. response.setContentType(stream.getContentType());
  501. // Sets cache headers
  502. final long cacheTime = stream.getCacheTime();
  503. if (cacheTime <= 0) {
  504. response.setProperty("Cache-Control", "no-cache");
  505. response.setProperty("Pragma", "no-cache");
  506. response.setProperty("Expires", "0");
  507. } else {
  508. response.setProperty("Cache-Control", "max-age=" + cacheTime
  509. / 1000);
  510. response.setProperty("Expires", "" + System.currentTimeMillis()
  511. + cacheTime);
  512. // Required to apply caching in some Tomcats
  513. response.setProperty("Pragma", "cache");
  514. }
  515. // Copy download stream parameters directly
  516. // to HTTP headers.
  517. final Iterator i = stream.getParameterNames();
  518. if (i != null) {
  519. while (i.hasNext()) {
  520. final String param = (String) i.next();
  521. response.setProperty(param, stream.getParameter(param));
  522. }
  523. }
  524. // suggest local filename from DownloadStream if Content-Disposition
  525. // not explicitly set
  526. String contentDispositionValue = stream
  527. .getParameter("Content-Disposition");
  528. if (contentDispositionValue == null) {
  529. contentDispositionValue = "filename=\"" + stream.getFileName()
  530. + "\"";
  531. response.setProperty("Content-Disposition",
  532. contentDispositionValue);
  533. }
  534. int bufferSize = stream.getBufferSize();
  535. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) {
  536. bufferSize = DEFAULT_BUFFER_SIZE;
  537. }
  538. final byte[] buffer = new byte[bufferSize];
  539. int bytesRead = 0;
  540. final OutputStream out = response.getPortletOutputStream();
  541. while ((bytesRead = data.read(buffer)) > 0) {
  542. out.write(buffer, 0, bytesRead);
  543. out.flush();
  544. }
  545. out.close();
  546. }
  547. }
  548. private void serveStaticResources(ResourceRequest request,
  549. ResourceResponse response) throws IOException, PortletException {
  550. final String resourceID = request.getResourceID();
  551. final PortletContext pc = getPortletContext();
  552. InputStream is = pc.getResourceAsStream(resourceID);
  553. if (is != null) {
  554. final String mimetype = pc.getMimeType(resourceID);
  555. if (mimetype != null) {
  556. response.setContentType(mimetype);
  557. }
  558. final OutputStream os = response.getPortletOutputStream();
  559. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  560. int bytes;
  561. while ((bytes = is.read(buffer)) >= 0) {
  562. os.write(buffer, 0, bytes);
  563. }
  564. } else {
  565. System.err.println("Requested resource [" + resourceID
  566. + "] could not be found");
  567. response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer
  568. .toString(HttpServletResponse.SC_NOT_FOUND));
  569. }
  570. }
  571. @Override
  572. public void processAction(ActionRequest request, ActionResponse response)
  573. throws PortletException, IOException {
  574. handleRequest(request, response);
  575. }
  576. /**
  577. * Handles a request for the "view" (default) portlet mode. In Vaadin, the
  578. * basic portlet modes ("view", "edit" and "help") are handled identically,
  579. * and their behavior can be changed by registering windows in the
  580. * application with window names identical to the portlet mode names.
  581. * Alternatively, a PortletListener can change the application main window
  582. * contents.
  583. *
  584. * To implement custom portlet modes, subclass the portlet class and
  585. * implement a method annotated with {@link RenderMode} for the custom mode,
  586. * calling {@link #handleRequest(PortletRequest, PortletResponse)} directly
  587. * from it.
  588. *
  589. * Note that the portlet class in the portlet configuration needs to be
  590. * changed when overriding methods of this class.
  591. *
  592. * @param request
  593. * @param response
  594. * @throws PortletException
  595. * @throws IOException
  596. */
  597. @Override
  598. protected void doView(RenderRequest request, RenderResponse response)
  599. throws PortletException, IOException {
  600. handleRequest(request, response);
  601. }
  602. /**
  603. * Handle a request for the "edit" portlet mode.
  604. *
  605. * @see #doView(RenderRequest, RenderResponse)
  606. */
  607. @Override
  608. protected void doEdit(RenderRequest request, RenderResponse response)
  609. throws PortletException, IOException {
  610. handleRequest(request, response);
  611. }
  612. /**
  613. * Handle a request for the "help" portlet mode.
  614. *
  615. * @see #doView(RenderRequest, RenderResponse)
  616. */
  617. @Override
  618. protected void doHelp(RenderRequest request, RenderResponse response)
  619. throws PortletException, IOException {
  620. handleRequest(request, response);
  621. }
  622. @Override
  623. public void serveResource(ResourceRequest request, ResourceResponse response)
  624. throws PortletException, IOException {
  625. handleRequest(request, response);
  626. }
  627. boolean requestCanCreateApplication(PortletRequest request,
  628. RequestType requestType) {
  629. if (requestType == RequestType.UIDL && isRepaintAll(request)) {
  630. return true;
  631. } else if (requestType == RequestType.RENDER) {
  632. return true;
  633. }
  634. return false;
  635. }
  636. private boolean isRepaintAll(PortletRequest request) {
  637. return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null)
  638. && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1"));
  639. }
  640. private void startApplication(PortletRequest request,
  641. Application application, PortletApplicationContext2 context)
  642. throws PortletException, MalformedURLException {
  643. if (!application.isRunning()) {
  644. Locale locale = request.getLocale();
  645. application.setLocale(locale);
  646. // No application URL when running inside a portlet
  647. application.start(null, applicationProperties, context);
  648. }
  649. }
  650. private void endApplication(PortletRequest request,
  651. PortletResponse response, Application application)
  652. throws IOException {
  653. final PortletSession session = request.getPortletSession();
  654. if (session != null) {
  655. PortletApplicationContext2.getApplicationContext(session)
  656. .removeApplication(application);
  657. }
  658. // Do not send any redirects when running inside a portlet.
  659. }
  660. private Application findApplicationInstance(PortletRequest request,
  661. RequestType requestType) throws PortletException,
  662. SessionExpiredException, MalformedURLException {
  663. boolean requestCanCreateApplication = requestCanCreateApplication(
  664. request, requestType);
  665. /* Find an existing application for this request. */
  666. Application application = getExistingApplication(request,
  667. requestCanCreateApplication);
  668. if (application != null) {
  669. /*
  670. * There is an existing application. We can use this as long as the
  671. * user not specifically requested to close or restart it.
  672. */
  673. final boolean restartApplication = (request
  674. .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null);
  675. final boolean closeApplication = (request
  676. .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null);
  677. if (restartApplication) {
  678. closeApplication(application, request.getPortletSession(false));
  679. return createApplication(request);
  680. } else if (closeApplication) {
  681. closeApplication(application, request.getPortletSession(false));
  682. return null;
  683. } else {
  684. return application;
  685. }
  686. }
  687. // No existing application was found
  688. if (requestCanCreateApplication) {
  689. return createApplication(request);
  690. } else {
  691. throw new SessionExpiredException();
  692. }
  693. }
  694. private void closeApplication(Application application,
  695. PortletSession session) {
  696. if (application == null) {
  697. return;
  698. }
  699. application.close();
  700. if (session != null) {
  701. PortletApplicationContext2 context = PortletApplicationContext2
  702. .getApplicationContext(session);
  703. context.removeApplication(application);
  704. }
  705. }
  706. private Application createApplication(PortletRequest request)
  707. throws PortletException, MalformedURLException {
  708. Application newApplication = getNewApplication(request);
  709. final PortletApplicationContext2 context = PortletApplicationContext2
  710. .getApplicationContext(request.getPortletSession());
  711. context.addApplication(newApplication, request.getWindowID());
  712. return newApplication;
  713. }
  714. private Application getExistingApplication(PortletRequest request,
  715. boolean allowSessionCreation) throws MalformedURLException,
  716. SessionExpiredException {
  717. final PortletSession session = request
  718. .getPortletSession(allowSessionCreation);
  719. if (session == null) {
  720. throw new SessionExpiredException();
  721. }
  722. PortletApplicationContext2 context = PortletApplicationContext2
  723. .getApplicationContext(session);
  724. Application application = context.getApplicationForWindowId(request
  725. .getWindowID());
  726. if (application == null) {
  727. return null;
  728. }
  729. if (application.isRunning()) {
  730. return application;
  731. }
  732. // application found but not running
  733. PortletApplicationContext2.getApplicationContext(session)
  734. .removeApplication(application);
  735. return null;
  736. }
  737. protected String getWidgetsetURL(String widgetset, PortletRequest request) {
  738. return getStaticFilesLocation(request) + "/" + WIDGETSET_DIRECTORY_PATH
  739. + widgetset + "/" + widgetset + ".nocache.js?"
  740. + new Date().getTime();
  741. }
  742. protected String getThemeURI(String themeName, PortletRequest request) {
  743. return getStaticFilesLocation(request) + "/" + THEME_DIRECTORY_PATH
  744. + themeName;
  745. }
  746. protected void writeAjaxPage(RenderRequest request,
  747. RenderResponse response, Window window, Application application)
  748. throws IOException, MalformedURLException, PortletException {
  749. response.setContentType("text/html");
  750. final BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
  751. response.getPortletOutputStream(), "UTF-8"));
  752. // TODO check
  753. String requestWidgetset = getApplicationOrSystemProperty(
  754. PARAMETER_WIDGETSET, null);
  755. String sharedWidgetset = getPortalProperty(
  756. PORTAL_PARAMETER_VAADIN_WIDGETSET, request.getPortalContext());
  757. String widgetset;
  758. if (requestWidgetset != null) {
  759. widgetset = requestWidgetset;
  760. } else if (sharedWidgetset != null) {
  761. widgetset = sharedWidgetset;
  762. } else {
  763. widgetset = DEFAULT_WIDGETSET;
  764. }
  765. // TODO Currently, we can only load widgetsets and themes from the
  766. // portal
  767. String themeName = getThemeForWindow(request, window);
  768. String widgetsetURL = getWidgetsetURL(widgetset, request);
  769. String themeURI = getThemeURI(themeName, request);
  770. // fixed base theme to use - all portal pages with Vaadin
  771. // applications will load this exactly once
  772. String portalTheme = getPortalProperty(PORTAL_PARAMETER_VAADIN_THEME,
  773. request.getPortalContext());
  774. // Get system messages
  775. Application.SystemMessages systemMessages = null;
  776. try {
  777. systemMessages = getSystemMessages();
  778. } catch (SystemMessageException e) {
  779. // failing to get the system messages is always a problem
  780. throw new PortletException("Failed to obtain system messages!", e);
  781. }
  782. page.write("<script type=\"text/javascript\">\n");
  783. page.write("if(!vaadin || !vaadin.vaadinConfigurations) {\n "
  784. + "if(!vaadin) { var vaadin = {}} \n"
  785. + "vaadin.vaadinConfigurations = {};\n"
  786. + "if (!vaadin.themesLoaded) { vaadin.themesLoaded = {}; }\n");
  787. if (!isProductionMode()) {
  788. page.write("vaadin.debug = true;\n");
  789. }
  790. page
  791. .write("document.write('<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" "
  792. + "style=\"width:0;height:0;border:0;overflow:"
  793. + "hidden\" src=\"javascript:false\"></iframe>');\n");
  794. page.write("document.write(\"<script language='javascript' src='"
  795. + widgetsetURL + "'><\\/script>\");\n}\n");
  796. page.write("vaadin.vaadinConfigurations[\"" + request.getWindowID()
  797. + "\"] = {");
  798. /*
  799. * We need this in order to get uploads to work.
  800. */
  801. PortletURL appUri = response.createActionURL();
  802. page.write("appUri: '" + appUri.toString() + "', ");
  803. page.write("usePortletURLs: true, ");
  804. ResourceURL uidlUrlBase = response.createResourceURL();
  805. uidlUrlBase.setResourceID("UIDL");
  806. page.write("portletUidlURLBase: '" + uidlUrlBase.toString() + "', ");
  807. page.write("pathInfo: '', ");
  808. page.write("themeUri: '" + themeURI + "', ");
  809. page.write("versionInfo : {vaadinVersion:\"");
  810. page.write(AbstractApplicationServlet.VERSION);
  811. page.write("\",applicationVersion:\"");
  812. page.write(application.getVersion());
  813. page.write("\"},");
  814. if (systemMessages != null) {
  815. // Write the CommunicationError -message to client
  816. String caption = systemMessages.getCommunicationErrorCaption();
  817. if (caption != null) {
  818. caption = "\"" + caption + "\"";
  819. }
  820. String message = systemMessages.getCommunicationErrorMessage();
  821. if (message != null) {
  822. message = "\"" + message + "\"";
  823. }
  824. String url = systemMessages.getCommunicationErrorURL();
  825. if (url != null) {
  826. url = "\"" + url + "\"";
  827. }
  828. page.write("\"comErrMsg\": {" + "\"caption\":" + caption + ","
  829. + "\"message\" : " + message + "," + "\"url\" : " + url
  830. + "}");
  831. }
  832. page.write("};\n</script>\n");
  833. page.write("<script type=\"text/javascript\">\n");
  834. if (portalTheme == null) {
  835. portalTheme = DEFAULT_THEME_NAME;
  836. }
  837. page.write("if(!vaadin.themesLoaded['" + portalTheme + "']) {\n");
  838. page.write("var defaultStylesheet = document.createElement('link');\n");
  839. page.write("defaultStylesheet.setAttribute('rel', 'stylesheet');\n");
  840. page.write("defaultStylesheet.setAttribute('type', 'text/css');\n");
  841. page.write("defaultStylesheet.setAttribute('href', '"
  842. + getThemeURI(portalTheme, request) + "/styles.css');\n");
  843. page
  844. .write("document.getElementsByTagName('head')[0].appendChild(defaultStylesheet);\n");
  845. page.write("vaadin.themesLoaded['" + portalTheme + "'] = true;\n}\n");
  846. if (!portalTheme.equals(themeName)) {
  847. page.write("if(!vaadin.themesLoaded['" + themeName + "']) {\n");
  848. page.write("var stylesheet = document.createElement('link');\n");
  849. page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
  850. page.write("stylesheet.setAttribute('type', 'text/css');\n");
  851. page.write("stylesheet.setAttribute('href', '" + themeURI
  852. + "/styles.css');\n");
  853. page
  854. .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
  855. page.write("vaadin.themesLoaded['" + themeName + "'] = true;\n}\n");
  856. }
  857. page.write("</script>\n");
  858. // TODO Warn if widgetset has not been loaded after 15 seconds
  859. /*- Add classnames;
  860. * .v-app
  861. * .v-app-loading
  862. * .v-app-<simpleName for app class>
  863. * .v-theme-<themeName, remove non-alphanum>
  864. */
  865. String appClass = "v-app-";
  866. try {
  867. appClass += getApplicationClass().getSimpleName();
  868. } catch (ClassNotFoundException e) {
  869. appClass += "unknown";
  870. e.printStackTrace();
  871. }
  872. String themeClass = "v-theme-"
  873. + themeName.replaceAll("[^a-zA-Z0-9]", "");
  874. String classNames = "v-app v-app-loading " + themeClass + " "
  875. + appClass;
  876. String style = getApplicationProperty(PORTLET_PARAMETER_STYLE);
  877. String divStyle = "";
  878. if (style != null) {
  879. divStyle = "style=\"" + style + "\"";
  880. }
  881. page.write("<div id=\"" + request.getWindowID() + "\" class=\""
  882. + classNames + "\" " + divStyle + "></div>\n");
  883. page.close();
  884. }
  885. /**
  886. * Returns the theme for given request/window
  887. *
  888. * @param request
  889. * @param window
  890. * @return
  891. */
  892. private String getThemeForWindow(PortletRequest request, Window window) {
  893. // Finds theme name
  894. String themeName;
  895. // theme defined for the window?
  896. themeName = window.getTheme();
  897. if (themeName == null) {
  898. // no, is the default theme defined by the portal?
  899. themeName = getPortalProperty(
  900. Constants.PORTAL_PARAMETER_VAADIN_THEME, request
  901. .getPortalContext());
  902. }
  903. if (themeName == null) {
  904. // no, using the default theme defined by Vaadin
  905. themeName = DEFAULT_THEME_NAME;
  906. }
  907. return themeName;
  908. }
  909. protected abstract Class<? extends Application> getApplicationClass()
  910. throws ClassNotFoundException;
  911. protected Application getNewApplication(PortletRequest request)
  912. throws PortletException {
  913. try {
  914. final Application application = getApplicationClass().newInstance();
  915. return application;
  916. } catch (final IllegalAccessException e) {
  917. throw new PortletException("getNewApplication failed", e);
  918. } catch (final InstantiationException e) {
  919. throw new PortletException("getNewApplication failed", e);
  920. } catch (final ClassNotFoundException e) {
  921. throw new PortletException("getNewApplication failed", e);
  922. }
  923. }
  924. protected ClassLoader getClassLoader() throws PortletException {
  925. // TODO Add support for custom class loader
  926. return getClass().getClassLoader();
  927. }
  928. /**
  929. * Get system messages from the current application class
  930. *
  931. * @return
  932. */
  933. protected SystemMessages getSystemMessages() {
  934. try {
  935. Class<? extends Application> appCls = getApplicationClass();
  936. Method m = appCls.getMethod("getSystemMessages", (Class[]) null);
  937. return (Application.SystemMessages) m.invoke(null, (Object[]) null);
  938. } catch (ClassNotFoundException e) {
  939. // This should never happen
  940. throw new SystemMessageException(e);
  941. } catch (SecurityException e) {
  942. throw new SystemMessageException(
  943. "Application.getSystemMessage() should be static public", e);
  944. } catch (NoSuchMethodException e) {
  945. // This is completely ok and should be silently ignored
  946. } catch (IllegalArgumentException e) {
  947. // This should never happen
  948. throw new SystemMessageException(e);
  949. } catch (IllegalAccessException e) {
  950. throw new SystemMessageException(
  951. "Application.getSystemMessage() should be static public", e);
  952. } catch (InvocationTargetException e) {
  953. // This should never happen
  954. throw new SystemMessageException(e);
  955. }
  956. return Application.getSystemMessages();
  957. }
  958. private void handleServiceException(PortletRequest request,
  959. PortletResponse response, Application application, Throwable e)
  960. throws IOException, PortletException {
  961. // TODO Check that this error handler is working when running inside a
  962. // portlet
  963. // if this was an UIDL request, response UIDL back to client
  964. if (getRequestType(request) == RequestType.UIDL) {
  965. Application.SystemMessages ci = getSystemMessages();
  966. criticalNotification(request, (ResourceResponse) response, ci
  967. .getInternalErrorCaption(), ci.getInternalErrorMessage(),
  968. null, ci.getInternalErrorURL());
  969. if (application != null) {
  970. application.getErrorHandler()
  971. .terminalError(new RequestError(e));
  972. } else {
  973. throw new PortletException(e);
  974. }
  975. } else {
  976. // Re-throw other exceptions
  977. throw new PortletException(e);
  978. }
  979. }
  980. @SuppressWarnings("serial")
  981. public class RequestError implements Terminal.ErrorEvent, Serializable {
  982. private final Throwable throwable;
  983. public RequestError(Throwable throwable) {
  984. this.throwable = throwable;
  985. }
  986. public Throwable getThrowable() {
  987. return throwable;
  988. }
  989. }
  990. /**
  991. * Send notification to client's application. Used to notify client of
  992. * critical errors and session expiration due to long inactivity. Server has
  993. * no knowledge of what application client refers to.
  994. *
  995. * @param request
  996. * the Portlet request instance.
  997. * @param response
  998. * the Portlet response to write to.
  999. * @param caption
  1000. * for the notification
  1001. * @param message
  1002. * for the notification
  1003. * @param details
  1004. * a detail message to show in addition to the passed message.
  1005. * Currently shown directly but could be hidden behind a details
  1006. * drop down.
  1007. * @param url
  1008. * url to load after message, null for current page
  1009. * @throws IOException
  1010. * if the writing failed due to input/output error.
  1011. */
  1012. void criticalNotification(PortletRequest request, MimeResponse response,
  1013. String caption, String message, String details, String url)
  1014. throws IOException {
  1015. // clients JS app is still running, but server application either
  1016. // no longer exists or it might fail to perform reasonably.
  1017. // send a notification to client's application and link how
  1018. // to "restart" application.
  1019. if (caption != null) {
  1020. caption = "\"" + caption + "\"";
  1021. }
  1022. if (details != null) {
  1023. if (message == null) {
  1024. message = details;
  1025. } else {
  1026. message += "<br/><br/>" + details;
  1027. }
  1028. }
  1029. if (message != null) {
  1030. message = "\"" + message + "\"";
  1031. }
  1032. if (url != null) {
  1033. url = "\"" + url + "\"";
  1034. }
  1035. // Set the response type
  1036. response.setContentType("application/json; charset=UTF-8");
  1037. final OutputStream out = response.getPortletOutputStream();
  1038. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  1039. new OutputStreamWriter(out, "UTF-8")));
  1040. outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
  1041. + "\"appError\": {" + "\"caption\":" + caption + ","
  1042. + "\"message\" : " + message + "," + "\"url\" : " + url
  1043. + "}}, \"resources\": {}, \"locales\":[]}]");
  1044. outWriter.flush();
  1045. outWriter.close();
  1046. out.flush();
  1047. }
  1048. private static String getPortalProperty(String name, PortalContext context) {
  1049. boolean isLifeRay = context.getPortalInfo().toLowerCase().contains(
  1050. "liferay");
  1051. // TODO test on non-LifeRay platforms
  1052. String value;
  1053. if (isLifeRay) {
  1054. value = getLifeRayPortalProperty(name);
  1055. } else {
  1056. value = context.getProperty(name);
  1057. }
  1058. return value;
  1059. }
  1060. private static String getLifeRayPortalProperty(String name) {
  1061. String value;
  1062. try {
  1063. value = PropsUtil.get(name);
  1064. } catch (Exception e) {
  1065. value = null;
  1066. }
  1067. return value;
  1068. }
  1069. }