Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

BootstrapHandler.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.server;
  5. import java.io.BufferedWriter;
  6. import java.io.IOException;
  7. import java.io.OutputStreamWriter;
  8. import java.io.Serializable;
  9. import java.io.Writer;
  10. import java.util.Map;
  11. import javax.servlet.http.HttpServletResponse;
  12. import com.vaadin.Application;
  13. import com.vaadin.RootRequiresMoreInformationException;
  14. import com.vaadin.Version;
  15. import com.vaadin.external.json.JSONException;
  16. import com.vaadin.external.json.JSONObject;
  17. import com.vaadin.terminal.DeploymentConfiguration;
  18. import com.vaadin.terminal.PaintException;
  19. import com.vaadin.terminal.RequestHandler;
  20. import com.vaadin.terminal.WrappedRequest;
  21. import com.vaadin.terminal.WrappedResponse;
  22. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  23. import com.vaadin.ui.Root;
  24. public abstract class BootstrapHandler implements RequestHandler {
  25. protected class BootstrapContext implements Serializable {
  26. private final WrappedResponse response;
  27. private final WrappedRequest request;
  28. private final Application application;
  29. private final Integer rootId;
  30. private Writer writer;
  31. private Root root;
  32. private String widgetsetName;
  33. private String themeName;
  34. private String appId;
  35. private boolean rootFetched = false;
  36. public BootstrapContext(WrappedResponse response,
  37. WrappedRequest request, Application application, Integer rootId) {
  38. this.response = response;
  39. this.request = request;
  40. this.application = application;
  41. this.rootId = rootId;
  42. }
  43. public WrappedResponse getResponse() {
  44. return response;
  45. }
  46. public WrappedRequest getRequest() {
  47. return request;
  48. }
  49. public Application getApplication() {
  50. return application;
  51. }
  52. public Writer getWriter() throws IOException {
  53. if (writer == null) {
  54. response.setContentType("text/html");
  55. writer = new BufferedWriter(new OutputStreamWriter(
  56. response.getOutputStream(), "UTF-8"));
  57. }
  58. return writer;
  59. }
  60. public Integer getRootId() {
  61. return rootId;
  62. }
  63. public Root getRoot() {
  64. if (!rootFetched) {
  65. root = Root.getCurrent();
  66. rootFetched = true;
  67. }
  68. return root;
  69. }
  70. public String getWidgetsetName() {
  71. if (widgetsetName == null) {
  72. Root root = getRoot();
  73. if (root != null) {
  74. widgetsetName = getWidgetsetForRoot(this);
  75. }
  76. }
  77. return widgetsetName;
  78. }
  79. public String getThemeName() {
  80. if (themeName == null) {
  81. Root root = getRoot();
  82. if (root != null) {
  83. themeName = findAndEscapeThemeName(this);
  84. }
  85. }
  86. return themeName;
  87. }
  88. public String getAppId() {
  89. if (appId == null) {
  90. appId = getApplicationId(this);
  91. }
  92. return appId;
  93. }
  94. }
  95. public boolean handleRequest(Application application,
  96. WrappedRequest request, WrappedResponse response)
  97. throws IOException {
  98. // TODO Should all urls be handled here?
  99. Integer rootId = null;
  100. try {
  101. Root root = application.getRootForRequest(request);
  102. if (root == null) {
  103. writeError(response, new Throwable("No Root found"));
  104. return true;
  105. }
  106. rootId = Integer.valueOf(root.getRootId());
  107. } catch (RootRequiresMoreInformationException e) {
  108. // Just keep going without rootId
  109. }
  110. try {
  111. writeBootstrapPage(request, response, application, rootId);
  112. } catch (JSONException e) {
  113. writeError(response, e);
  114. }
  115. return true;
  116. }
  117. protected final void writeBootstrapPage(WrappedRequest request,
  118. WrappedResponse response, Application application, Integer rootId)
  119. throws IOException, JSONException {
  120. BootstrapContext context = createContext(request, response,
  121. application, rootId);
  122. DeploymentConfiguration deploymentConfiguration = request
  123. .getDeploymentConfiguration();
  124. boolean standalone = deploymentConfiguration.isStandalone(request);
  125. if (standalone) {
  126. setBootstrapPageHeaders(context);
  127. writeBootstrapPageHtmlHeadStart(context);
  128. writeBootstrapPageHtmlHeader(context);
  129. writeBootstrapPageHtmlBodyStart(context);
  130. }
  131. // TODO include initial UIDL in the scripts?
  132. writeBootstrapPageHtmlVaadinScripts(context);
  133. writeBootstrapPageHtmlMainDiv(context);
  134. Writer page = context.getWriter();
  135. if (standalone) {
  136. page.write("</body>\n</html>\n");
  137. }
  138. page.close();
  139. }
  140. public BootstrapContext createContext(WrappedRequest request,
  141. WrappedResponse response, Application application, Integer rootId) {
  142. BootstrapContext context = new BootstrapContext(response, request,
  143. application, rootId);
  144. return context;
  145. }
  146. protected String getMainDivStyle(BootstrapContext context) {
  147. return null;
  148. }
  149. /**
  150. * Creates and returns a unique ID for the DIV where the application is to
  151. * be rendered.
  152. *
  153. * @param context
  154. *
  155. * @return the id to use in the DOM
  156. */
  157. protected abstract String getApplicationId(BootstrapContext context);
  158. public String getWidgetsetForRoot(BootstrapContext context) {
  159. Root root = context.getRoot();
  160. WrappedRequest request = context.getRequest();
  161. String widgetset = root.getApplication().getWidgetsetForRoot(root);
  162. if (widgetset == null) {
  163. widgetset = request.getDeploymentConfiguration()
  164. .getConfiguredWidgetset(request);
  165. }
  166. widgetset = AbstractApplicationServlet.stripSpecialChars(widgetset);
  167. return widgetset;
  168. }
  169. /**
  170. * Method to write the div element into which that actual Vaadin application
  171. * is rendered.
  172. * <p>
  173. * Override this method if you want to add some custom html around around
  174. * the div element into which the actual Vaadin application will be
  175. * rendered.
  176. *
  177. * @param context
  178. *
  179. * @throws IOException
  180. */
  181. protected void writeBootstrapPageHtmlMainDiv(BootstrapContext context)
  182. throws IOException {
  183. Writer page = context.getWriter();
  184. String style = getMainDivStyle(context);
  185. /*- Add classnames;
  186. * .v-app
  187. * .v-app-loading
  188. * .v-app-<simpleName for app class>
  189. *- Additionally added from javascript:
  190. * .v-theme-<themeName, remove non-alphanum>
  191. */
  192. String appClass = "v-app-"
  193. + getApplicationCSSClassName(context.getApplication());
  194. String classNames = "v-app " + appClass;
  195. if (style != null && style.length() != 0) {
  196. style = " style=\"" + style + "\"";
  197. }
  198. page.write("<div id=\"" + context.getAppId() + "\" class=\""
  199. + classNames + "\"" + style + ">");
  200. page.write("<div class=\"v-app-loading\"></div>");
  201. page.write("</div>\n");
  202. page.write("<noscript>" + getNoScriptMessage() + "</noscript>");
  203. }
  204. /**
  205. * Returns a message printed for browsers without scripting support or if
  206. * browsers scripting support is disabled.
  207. */
  208. protected String getNoScriptMessage() {
  209. return "You have to enable javascript in your browser to use an application built with Vaadin.";
  210. }
  211. /**
  212. * Returns the application class identifier for use in the application CSS
  213. * class name in the root DIV. The application CSS class name is of form
  214. * "v-app-"+getApplicationCSSClassName().
  215. *
  216. * This method should normally not be overridden.
  217. *
  218. * @return The CSS class name to use in combination with "v-app-".
  219. */
  220. protected String getApplicationCSSClassName(Application application) {
  221. return application.getClass().getSimpleName();
  222. }
  223. /**
  224. *
  225. * Method to open the body tag of the html kickstart page.
  226. * <p>
  227. * This method is responsible for closing the head tag and opening the body
  228. * tag.
  229. * <p>
  230. * Override this method if you want to add some custom html to the page.
  231. *
  232. * @throws IOException
  233. */
  234. protected void writeBootstrapPageHtmlBodyStart(BootstrapContext context)
  235. throws IOException {
  236. Writer page = context.getWriter();
  237. page.write("\n</head>\n<body scroll=\"auto\" class=\""
  238. + ApplicationConnection.GENERATED_BODY_CLASSNAME + "\">\n");
  239. }
  240. /**
  241. * Method to write the script part of the page which loads needed Vaadin
  242. * scripts and themes.
  243. * <p>
  244. * Override this method if you want to add some custom html around scripts.
  245. *
  246. * @param context
  247. *
  248. * @throws IOException
  249. * @throws JSONException
  250. */
  251. protected void writeBootstrapPageHtmlVaadinScripts(BootstrapContext context)
  252. throws IOException, JSONException {
  253. WrappedRequest request = context.getRequest();
  254. Writer page = context.getWriter();
  255. DeploymentConfiguration deploymentConfiguration = request
  256. .getDeploymentConfiguration();
  257. String staticFileLocation = deploymentConfiguration
  258. .getStaticFileLocation(request);
  259. page.write("<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" "
  260. + "style=\"position:absolute;width:0;height:0;border:0;overflow:"
  261. + "hidden;\" src=\"javascript:false\"></iframe>");
  262. String bootstrapLocation = staticFileLocation
  263. + "/VAADIN/vaadinBootstrap.js";
  264. page.write("<script type=\"text/javascript\" src=\"");
  265. page.write(bootstrapLocation);
  266. page.write("\"></script>\n");
  267. page.write("<script type=\"text/javascript\">\n");
  268. page.write("//<![CDATA[\n");
  269. page.write("if (!window.vaadin) alert("
  270. + JSONObject.quote("Failed to load the bootstrap javascript: "
  271. + bootstrapLocation) + ");\n");
  272. writeMainScriptTagContents(context);
  273. page.write("//]]>\n</script>\n");
  274. }
  275. protected void writeMainScriptTagContents(BootstrapContext context)
  276. throws JSONException, IOException {
  277. JSONObject defaults = getDefaultParameters(context);
  278. JSONObject appConfig = getApplicationParameters(context);
  279. boolean isDebug = !context.getApplication().isProductionMode();
  280. Writer page = context.getWriter();
  281. page.write("vaadin.setDefaults(");
  282. printJsonObject(page, defaults, isDebug);
  283. page.write(");\n");
  284. page.write("vaadin.initApplication(\"");
  285. page.write(context.getAppId());
  286. page.write("\",");
  287. printJsonObject(page, appConfig, isDebug);
  288. page.write(");\n");
  289. }
  290. private static void printJsonObject(Writer page, JSONObject jsonObject,
  291. boolean isDebug) throws IOException, JSONException {
  292. if (isDebug) {
  293. page.write(jsonObject.toString(4));
  294. } else {
  295. page.write(jsonObject.toString());
  296. }
  297. }
  298. protected JSONObject getApplicationParameters(BootstrapContext context)
  299. throws JSONException, PaintException {
  300. Application application = context.getApplication();
  301. Integer rootId = context.getRootId();
  302. JSONObject appConfig = new JSONObject();
  303. if (rootId != null) {
  304. appConfig.put(ApplicationConnection.ROOT_ID_PARAMETER, rootId);
  305. }
  306. if (context.getThemeName() != null) {
  307. appConfig.put("themeUri",
  308. getThemeUri(context, context.getThemeName()));
  309. }
  310. JSONObject versionInfo = new JSONObject();
  311. versionInfo.put("vaadinVersion", Version.getFullVersion());
  312. versionInfo.put("applicationVersion", application.getVersion());
  313. appConfig.put("versionInfo", versionInfo);
  314. appConfig.put("widgetset", context.getWidgetsetName());
  315. if (rootId == null || application.isRootInitPending(rootId.intValue())) {
  316. appConfig.put("initialPath", context.getRequest()
  317. .getRequestPathInfo());
  318. Map<String, String[]> parameterMap = context.getRequest()
  319. .getParameterMap();
  320. appConfig.put("initialParams", parameterMap);
  321. } else {
  322. // write the initial UIDL into the config
  323. appConfig.put("uidl",
  324. getInitialUIDL(context.getRequest(), context.getRoot()));
  325. }
  326. return appConfig;
  327. }
  328. protected JSONObject getDefaultParameters(BootstrapContext context)
  329. throws JSONException {
  330. JSONObject defaults = new JSONObject();
  331. WrappedRequest request = context.getRequest();
  332. Application application = context.getApplication();
  333. // Get system messages
  334. Application.SystemMessages systemMessages = AbstractApplicationServlet
  335. .getSystemMessages(application.getClass());
  336. if (systemMessages != null) {
  337. // Write the CommunicationError -message to client
  338. JSONObject comErrMsg = new JSONObject();
  339. comErrMsg.put("caption",
  340. systemMessages.getCommunicationErrorCaption());
  341. comErrMsg.put("message",
  342. systemMessages.getCommunicationErrorMessage());
  343. comErrMsg.put("url", systemMessages.getCommunicationErrorURL());
  344. defaults.put("comErrMsg", comErrMsg);
  345. JSONObject authErrMsg = new JSONObject();
  346. authErrMsg.put("caption",
  347. systemMessages.getAuthenticationErrorCaption());
  348. authErrMsg.put("message",
  349. systemMessages.getAuthenticationErrorMessage());
  350. authErrMsg.put("url", systemMessages.getAuthenticationErrorURL());
  351. defaults.put("authErrMsg", authErrMsg);
  352. }
  353. DeploymentConfiguration deploymentConfiguration = request
  354. .getDeploymentConfiguration();
  355. String staticFileLocation = deploymentConfiguration
  356. .getStaticFileLocation(request);
  357. String widgetsetBase = staticFileLocation + "/"
  358. + AbstractApplicationServlet.WIDGETSET_DIRECTORY_PATH;
  359. defaults.put("widgetsetBase", widgetsetBase);
  360. if (!application.isProductionMode()) {
  361. defaults.put("debug", true);
  362. }
  363. if (deploymentConfiguration.isStandalone(request)) {
  364. defaults.put("standalone", true);
  365. }
  366. defaults.put("appUri", getAppUri(context));
  367. return defaults;
  368. }
  369. protected abstract String getAppUri(BootstrapContext context);
  370. /**
  371. * Method to write the contents of head element in html kickstart page.
  372. * <p>
  373. * Override this method if you want to add some custom html to the header of
  374. * the page.
  375. *
  376. * @throws IOException
  377. */
  378. protected void writeBootstrapPageHtmlHeader(BootstrapContext context)
  379. throws IOException {
  380. Writer page = context.getWriter();
  381. String themeName = context.getThemeName();
  382. page.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n");
  383. // Chrome frame in all versions of IE (only if Chrome frame is
  384. // installed)
  385. page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n");
  386. page.write("<style type=\"text/css\">"
  387. + "html, body {height:100%;margin:0;}</style>\n");
  388. // Add favicon links
  389. if (themeName != null) {
  390. String themeUri = getThemeUri(context, themeName);
  391. page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\""
  392. + themeUri + "/favicon.ico\" />\n");
  393. page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\""
  394. + themeUri + "/favicon.ico\" />\n");
  395. }
  396. Root root = context.getRoot();
  397. String title = ((root == null || root.getCaption() == null) ? "" : root
  398. .getCaption());
  399. page.write("<title>"
  400. + AbstractApplicationServlet.safeEscapeForHtml(title)
  401. + "</title>\n");
  402. }
  403. /**
  404. * Method to set http request headers for the Vaadin kickstart page.
  405. * <p>
  406. * Override this method if you need to customize http headers of the page.
  407. *
  408. * @param context
  409. */
  410. protected void setBootstrapPageHeaders(BootstrapContext context) {
  411. WrappedResponse response = context.getResponse();
  412. // Window renders are not cacheable
  413. response.setHeader("Cache-Control", "no-cache");
  414. response.setHeader("Pragma", "no-cache");
  415. response.setDateHeader("Expires", 0);
  416. response.setContentType("text/html; charset=UTF-8");
  417. }
  418. /**
  419. * Method to write the beginning of the html page.
  420. * <p>
  421. * This method is responsible for writing appropriate doc type declarations
  422. * and to open html and head tags.
  423. * <p>
  424. * Override this method if you want to add some custom html to the very
  425. * beginning of the page.
  426. *
  427. * @param context
  428. * @throws IOException
  429. */
  430. protected void writeBootstrapPageHtmlHeadStart(BootstrapContext context)
  431. throws IOException {
  432. Writer page = context.getWriter();
  433. // write html header
  434. page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
  435. + "XHTML 1.0 Transitional//EN\" "
  436. + "\"http://www.w3.org/TR/xhtml1/"
  437. + "DTD/xhtml1-transitional.dtd\">\n");
  438. page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
  439. + ">\n<head>\n");
  440. }
  441. /**
  442. * Get the URI for the application theme.
  443. *
  444. * A portal-wide default theme is fetched from the portal shared resource
  445. * directory (if any), other themes from the portlet.
  446. *
  447. * @param context
  448. * @param themeName
  449. *
  450. * @return
  451. */
  452. public String getThemeUri(BootstrapContext context, String themeName) {
  453. WrappedRequest request = context.getRequest();
  454. final String staticFilePath = request.getDeploymentConfiguration()
  455. .getStaticFileLocation(request);
  456. return staticFilePath + "/"
  457. + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName;
  458. }
  459. /**
  460. * Override if required
  461. *
  462. * @param context
  463. * @return
  464. */
  465. public String getThemeName(BootstrapContext context) {
  466. return context.getApplication().getThemeForRoot(context.getRoot());
  467. }
  468. /**
  469. * Don not override.
  470. *
  471. * @param context
  472. * @return
  473. */
  474. public String findAndEscapeThemeName(BootstrapContext context) {
  475. String themeName = getThemeName(context);
  476. if (themeName == null) {
  477. WrappedRequest request = context.getRequest();
  478. themeName = request.getDeploymentConfiguration()
  479. .getConfiguredTheme(request);
  480. }
  481. // XSS preventation, theme names shouldn't contain special chars anyway.
  482. // The servlet denies them via url parameter.
  483. themeName = AbstractApplicationServlet.stripSpecialChars(themeName);
  484. return themeName;
  485. }
  486. protected void writeError(WrappedResponse response, Throwable e)
  487. throws IOException {
  488. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
  489. e.getLocalizedMessage());
  490. }
  491. /**
  492. * Gets the initial UIDL message to send to the client.
  493. *
  494. * @param request
  495. * the originating request
  496. * @param root
  497. * the root for which the UIDL should be generated
  498. * @return a string with the initial UIDL message
  499. * @throws PaintException
  500. * if an exception occurs while painting the components
  501. * @throws JSONException
  502. * if an exception occurs while formatting the output
  503. */
  504. protected abstract String getInitialUIDL(WrappedRequest request, Root root)
  505. throws PaintException, JSONException;
  506. }