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.

BootstrapHandler.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. /*
  2. * Copyright 2000-2013 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.OutputStreamWriter;
  20. import java.io.Serializable;
  21. import java.util.ArrayList;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Locale;
  25. import java.util.Map;
  26. import java.util.Map.Entry;
  27. import java.util.Set;
  28. import javax.servlet.http.HttpServletResponse;
  29. import org.json.JSONException;
  30. import org.json.JSONObject;
  31. import org.jsoup.nodes.DataNode;
  32. import org.jsoup.nodes.Document;
  33. import org.jsoup.nodes.DocumentType;
  34. import org.jsoup.nodes.Element;
  35. import org.jsoup.nodes.Node;
  36. import org.jsoup.parser.Tag;
  37. import com.vaadin.shared.ApplicationConstants;
  38. import com.vaadin.shared.Version;
  39. import com.vaadin.ui.UI;
  40. /**
  41. *
  42. * @author Vaadin Ltd
  43. * @since 7.0.0
  44. *
  45. * @deprecated As of 7.0. Will likely change or be removed in a future version
  46. */
  47. @Deprecated
  48. public abstract class BootstrapHandler extends SynchronizedRequestHandler {
  49. /**
  50. * Parameter that is added to the UI init request if the session has already
  51. * been restarted when generating the bootstrap HTML and ?restartApplication
  52. * should thus be ignored when handling the UI init request.
  53. */
  54. public static final String IGNORE_RESTART_PARAM = "ignoreRestart";
  55. protected class BootstrapContext implements Serializable {
  56. private final VaadinResponse response;
  57. private final BootstrapFragmentResponse bootstrapResponse;
  58. private String widgetsetName;
  59. private String themeName;
  60. private String appId;
  61. public BootstrapContext(VaadinResponse response,
  62. BootstrapFragmentResponse bootstrapResponse) {
  63. this.response = response;
  64. this.bootstrapResponse = bootstrapResponse;
  65. }
  66. public VaadinResponse getResponse() {
  67. return response;
  68. }
  69. public VaadinRequest getRequest() {
  70. return bootstrapResponse.getRequest();
  71. }
  72. public VaadinSession getSession() {
  73. return bootstrapResponse.getSession();
  74. }
  75. public Class<? extends UI> getUIClass() {
  76. return bootstrapResponse.getUiClass();
  77. }
  78. public String getWidgetsetName() {
  79. if (widgetsetName == null) {
  80. widgetsetName = getWidgetsetForUI(this);
  81. }
  82. return widgetsetName;
  83. }
  84. public String getThemeName() {
  85. if (themeName == null) {
  86. themeName = findAndEscapeThemeName(this);
  87. }
  88. return themeName;
  89. }
  90. public String getAppId() {
  91. if (appId == null) {
  92. appId = getRequest().getService().getMainDivId(getSession(),
  93. getRequest(), getUIClass());
  94. }
  95. return appId;
  96. }
  97. public BootstrapFragmentResponse getBootstrapResponse() {
  98. return bootstrapResponse;
  99. }
  100. }
  101. @Override
  102. public boolean synchronizedHandleRequest(VaadinSession session,
  103. VaadinRequest request, VaadinResponse response) throws IOException {
  104. if (ServletPortletHelper.isAppRequest(request)) {
  105. // We do not want to handle /APP requests here, instead let it fall
  106. // through and produce a 404
  107. return false;
  108. }
  109. try {
  110. List<UIProvider> uiProviders = session.getUIProviders();
  111. UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
  112. request);
  113. // Find UI provider and UI class
  114. Class<? extends UI> uiClass = null;
  115. UIProvider provider = null;
  116. for (UIProvider p : uiProviders) {
  117. uiClass = p.getUIClass(classSelectionEvent);
  118. // If we found something
  119. if (uiClass != null) {
  120. provider = p;
  121. break;
  122. }
  123. }
  124. if (provider == null) {
  125. // Can't generate bootstrap if no UI provider matches
  126. return false;
  127. }
  128. BootstrapContext context = new BootstrapContext(response,
  129. new BootstrapFragmentResponse(this, request, session,
  130. uiClass, new ArrayList<Node>(), provider));
  131. setupMainDiv(context);
  132. BootstrapFragmentResponse fragmentResponse = context
  133. .getBootstrapResponse();
  134. session.modifyBootstrapResponse(fragmentResponse);
  135. String html = getBootstrapHtml(context);
  136. writeBootstrapPage(response, html);
  137. } catch (JSONException e) {
  138. writeError(response, e);
  139. }
  140. return true;
  141. }
  142. private String getBootstrapHtml(BootstrapContext context) {
  143. VaadinRequest request = context.getRequest();
  144. VaadinResponse response = context.getResponse();
  145. VaadinService vaadinService = request.getService();
  146. BootstrapFragmentResponse fragmentResponse = context
  147. .getBootstrapResponse();
  148. if (vaadinService.isStandalone(request)) {
  149. Map<String, Object> headers = new LinkedHashMap<String, Object>();
  150. Document document = Document.createShell("");
  151. BootstrapPageResponse pageResponse = new BootstrapPageResponse(
  152. this, request, context.getSession(), context.getUIClass(),
  153. document, headers, fragmentResponse.getUIProvider());
  154. List<Node> fragmentNodes = fragmentResponse.getFragmentNodes();
  155. Element body = document.body();
  156. for (Node node : fragmentNodes) {
  157. body.appendChild(node);
  158. }
  159. setupStandaloneDocument(context, pageResponse);
  160. context.getSession().modifyBootstrapResponse(pageResponse);
  161. sendBootstrapHeaders(response, headers);
  162. return document.outerHtml();
  163. } else {
  164. StringBuilder sb = new StringBuilder();
  165. for (Node node : fragmentResponse.getFragmentNodes()) {
  166. if (sb.length() != 0) {
  167. sb.append('\n');
  168. }
  169. sb.append(node.outerHtml());
  170. }
  171. return sb.toString();
  172. }
  173. }
  174. private void sendBootstrapHeaders(VaadinResponse response,
  175. Map<String, Object> headers) {
  176. Set<Entry<String, Object>> entrySet = headers.entrySet();
  177. for (Entry<String, Object> header : entrySet) {
  178. Object value = header.getValue();
  179. if (value instanceof String) {
  180. response.setHeader(header.getKey(), (String) value);
  181. } else if (value instanceof Long) {
  182. response.setDateHeader(header.getKey(),
  183. ((Long) value).longValue());
  184. } else {
  185. throw new RuntimeException("Unsupported header value: " + value);
  186. }
  187. }
  188. }
  189. private void writeBootstrapPage(VaadinResponse response, String html)
  190. throws IOException {
  191. response.setContentType("text/html");
  192. BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
  193. response.getOutputStream(), "UTF-8"));
  194. writer.append(html);
  195. writer.close();
  196. }
  197. private void setupStandaloneDocument(BootstrapContext context,
  198. BootstrapPageResponse response) {
  199. response.setHeader("Cache-Control", "no-cache");
  200. response.setHeader("Pragma", "no-cache");
  201. response.setDateHeader("Expires", 0);
  202. Document document = response.getDocument();
  203. DocumentType doctype = new DocumentType("html", "", "",
  204. document.baseUri());
  205. document.child(0).before(doctype);
  206. Element head = document.head();
  207. head.appendElement("meta").attr("http-equiv", "Content-Type")
  208. .attr("content", "text/html; charset=utf-8");
  209. /*
  210. * Enable Chrome Frame in all versions of IE if installed.
  211. */
  212. head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
  213. .attr("content", "IE=10;chrome=1");
  214. String title = response.getUIProvider().getPageTitle(
  215. new UICreateEvent(context.getRequest(), context.getUIClass()));
  216. if (title != null) {
  217. head.appendElement("title").appendText(title);
  218. }
  219. head.appendElement("style").attr("type", "text/css")
  220. .appendText("html, body {height:100%;margin:0;}");
  221. // Add favicon links
  222. String themeName = context.getThemeName();
  223. if (themeName != null) {
  224. String themeUri = getThemeUri(context, themeName);
  225. head.appendElement("link").attr("rel", "shortcut icon")
  226. .attr("type", "image/vnd.microsoft.icon")
  227. .attr("href", themeUri + "/favicon.ico");
  228. head.appendElement("link").attr("rel", "icon")
  229. .attr("type", "image/vnd.microsoft.icon")
  230. .attr("href", themeUri + "/favicon.ico");
  231. }
  232. Element body = document.body();
  233. body.attr("scroll", "auto");
  234. body.addClass(ApplicationConstants.GENERATED_BODY_CLASSNAME);
  235. }
  236. protected String getMainDivStyle(BootstrapContext context) {
  237. return null;
  238. }
  239. public String getWidgetsetForUI(BootstrapContext context) {
  240. VaadinRequest request = context.getRequest();
  241. UICreateEvent event = new UICreateEvent(context.getRequest(),
  242. context.getUIClass());
  243. String widgetset = context.getBootstrapResponse().getUIProvider()
  244. .getWidgetset(event);
  245. if (widgetset == null) {
  246. widgetset = request.getService().getConfiguredWidgetset(request);
  247. }
  248. widgetset = VaadinServlet.stripSpecialChars(widgetset);
  249. return widgetset;
  250. }
  251. /**
  252. * Method to write the div element into which that actual Vaadin application
  253. * is rendered.
  254. * <p>
  255. * Override this method if you want to add some custom html around around
  256. * the div element into which the actual Vaadin application will be
  257. * rendered.
  258. *
  259. * @param context
  260. *
  261. * @throws IOException
  262. * @throws JSONException
  263. */
  264. private void setupMainDiv(BootstrapContext context) throws IOException,
  265. JSONException {
  266. String style = getMainDivStyle(context);
  267. /*- Add classnames;
  268. * .v-app
  269. * .v-app-loading
  270. * .v-app-<simpleName for app class>
  271. *- Additionally added from javascript:
  272. * <themeName, remove non-alphanum>
  273. */
  274. List<Node> fragmentNodes = context.getBootstrapResponse()
  275. .getFragmentNodes();
  276. Element mainDiv = new Element(Tag.valueOf("div"), "");
  277. mainDiv.attr("id", context.getAppId());
  278. mainDiv.addClass("v-app");
  279. if (style != null && style.length() != 0) {
  280. mainDiv.attr("style", style);
  281. }
  282. mainDiv.appendElement("div").addClass("v-app-loading");
  283. mainDiv.appendElement("noscript")
  284. .append("You have to enable javascript in your browser to use an application built with Vaadin.");
  285. fragmentNodes.add(mainDiv);
  286. VaadinRequest request = context.getRequest();
  287. VaadinService vaadinService = request.getService();
  288. String vaadinLocation = vaadinService.getStaticFileLocation(request)
  289. + "/VAADIN/";
  290. fragmentNodes
  291. .add(new Element(Tag.valueOf("iframe"), "")
  292. .attr("tabIndex", "-1")
  293. .attr("id", "__gwt_historyFrame")
  294. .attr("style",
  295. "position:absolute;width:0;height:0;border:0;overflow:hidden")
  296. .attr("src", "javascript:false"));
  297. if (context.getSession().getPushMode().isEnabled()) {
  298. // Load client-side dependencies for push support
  299. fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr(
  300. "type", "text/javascript").attr("src",
  301. vaadinLocation + "vaadinPush.js"));
  302. }
  303. String bootstrapLocation = vaadinLocation + "vaadinBootstrap.js";
  304. fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr("type",
  305. "text/javascript").attr("src", bootstrapLocation));
  306. Element mainScriptTag = new Element(Tag.valueOf("script"), "").attr(
  307. "type", "text/javascript");
  308. StringBuilder builder = new StringBuilder();
  309. builder.append("//<![CDATA[\n");
  310. builder.append("if (!window.vaadin) alert("
  311. + JSONObject.quote("Failed to load the bootstrap javascript: "
  312. + bootstrapLocation) + ");\n");
  313. appendMainScriptTagContents(context, builder);
  314. builder.append("//]]>");
  315. mainScriptTag.appendChild(new DataNode(builder.toString(),
  316. mainScriptTag.baseUri()));
  317. fragmentNodes.add(mainScriptTag);
  318. }
  319. protected void appendMainScriptTagContents(BootstrapContext context,
  320. StringBuilder builder) throws JSONException, IOException {
  321. JSONObject appConfig = getApplicationParameters(context);
  322. boolean isDebug = !context.getSession().getConfiguration()
  323. .isProductionMode();
  324. if (isDebug) {
  325. /*
  326. * Add tracking needed for getting bootstrap metrics to the client
  327. * side Profiler if another implementation hasn't already been
  328. * added.
  329. */
  330. builder.append("if (typeof window.__gwtStatsEvent != 'function') {\n");
  331. builder.append("vaadin.gwtStatsEvents = [];\n");
  332. builder.append("window.__gwtStatsEvent = function(event) {vaadin.gwtStatsEvents.push(event); return true;};\n");
  333. builder.append("}\n");
  334. }
  335. builder.append("vaadin.initApplication(\"");
  336. builder.append(context.getAppId());
  337. builder.append("\",");
  338. appendJsonObject(builder, appConfig, isDebug);
  339. builder.append(");\n");
  340. }
  341. private static void appendJsonObject(StringBuilder builder,
  342. JSONObject jsonObject, boolean isDebug) throws JSONException {
  343. if (isDebug) {
  344. builder.append(jsonObject.toString(4));
  345. } else {
  346. builder.append(jsonObject.toString());
  347. }
  348. }
  349. protected JSONObject getApplicationParameters(BootstrapContext context)
  350. throws JSONException, PaintException {
  351. VaadinRequest request = context.getRequest();
  352. VaadinSession session = context.getSession();
  353. VaadinService vaadinService = request.getService();
  354. JSONObject appConfig = new JSONObject();
  355. String themeName = context.getThemeName();
  356. if (themeName != null) {
  357. appConfig.put("theme", themeName);
  358. }
  359. // Ignore restartApplication that might be passed to UI init
  360. if (request
  361. .getParameter(VaadinService.URL_PARAMETER_RESTART_APPLICATION) != null) {
  362. appConfig.put("extraParams", "&" + IGNORE_RESTART_PARAM + "=1");
  363. }
  364. JSONObject versionInfo = new JSONObject();
  365. versionInfo.put("vaadinVersion", Version.getFullVersion());
  366. appConfig.put("versionInfo", versionInfo);
  367. appConfig.put("widgetset", context.getWidgetsetName());
  368. // Use locale from session if set, else from the request
  369. Locale locale = ServletPortletHelper.findLocale(null,
  370. context.getSession(), context.getRequest());
  371. // Get system messages
  372. SystemMessages systemMessages = vaadinService.getSystemMessages(locale,
  373. request);
  374. if (systemMessages != null) {
  375. // Write the CommunicationError -message to client
  376. JSONObject comErrMsg = new JSONObject();
  377. comErrMsg.put("caption",
  378. systemMessages.getCommunicationErrorCaption());
  379. comErrMsg.put("message",
  380. systemMessages.getCommunicationErrorMessage());
  381. comErrMsg.put("url", systemMessages.getCommunicationErrorURL());
  382. appConfig.put("comErrMsg", comErrMsg);
  383. JSONObject authErrMsg = new JSONObject();
  384. authErrMsg.put("caption",
  385. systemMessages.getAuthenticationErrorCaption());
  386. authErrMsg.put("message",
  387. systemMessages.getAuthenticationErrorMessage());
  388. authErrMsg.put("url", systemMessages.getAuthenticationErrorURL());
  389. appConfig.put("authErrMsg", authErrMsg);
  390. JSONObject sessExpMsg = new JSONObject();
  391. sessExpMsg
  392. .put("caption", systemMessages.getSessionExpiredCaption());
  393. sessExpMsg
  394. .put("message", systemMessages.getSessionExpiredMessage());
  395. sessExpMsg.put("url", systemMessages.getSessionExpiredURL());
  396. appConfig.put("sessExpMsg", sessExpMsg);
  397. }
  398. // getStaticFileLocation documented to never end with a slash
  399. // vaadinDir should always end with a slash
  400. String vaadinDir = vaadinService.getStaticFileLocation(request)
  401. + "/VAADIN/";
  402. appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir);
  403. if (!session.getConfiguration().isProductionMode()) {
  404. appConfig.put("debug", true);
  405. }
  406. if (vaadinService.isStandalone(request)) {
  407. appConfig.put("standalone", true);
  408. }
  409. appConfig.put("heartbeatInterval", vaadinService
  410. .getDeploymentConfiguration().getHeartbeatInterval());
  411. appConfig.put("pushMode", session.getPushMode().toString());
  412. String serviceUrl = getServiceUrl(context);
  413. if (serviceUrl != null) {
  414. appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
  415. }
  416. return appConfig;
  417. }
  418. protected abstract String getServiceUrl(BootstrapContext context);
  419. /**
  420. * Get the URI for the application theme.
  421. *
  422. * A portal-wide default theme is fetched from the portal shared resource
  423. * directory (if any), other themes from the portlet.
  424. *
  425. * @param context
  426. * @param themeName
  427. *
  428. * @return
  429. */
  430. public String getThemeUri(BootstrapContext context, String themeName) {
  431. VaadinRequest request = context.getRequest();
  432. final String staticFilePath = request.getService()
  433. .getStaticFileLocation(request);
  434. return staticFilePath + "/" + VaadinServlet.THEME_DIR_PATH + '/'
  435. + themeName;
  436. }
  437. /**
  438. * Override if required
  439. *
  440. * @param context
  441. * @return
  442. */
  443. public String getThemeName(BootstrapContext context) {
  444. UICreateEvent event = new UICreateEvent(context.getRequest(),
  445. context.getUIClass());
  446. return context.getBootstrapResponse().getUIProvider().getTheme(event);
  447. }
  448. /**
  449. * Don not override.
  450. *
  451. * @param context
  452. * @return
  453. */
  454. public String findAndEscapeThemeName(BootstrapContext context) {
  455. String themeName = getThemeName(context);
  456. if (themeName == null) {
  457. VaadinRequest request = context.getRequest();
  458. themeName = request.getService().getConfiguredTheme(request);
  459. }
  460. // XSS preventation, theme names shouldn't contain special chars anyway.
  461. // The servlet denies them via url parameter.
  462. themeName = VaadinServlet.stripSpecialChars(themeName);
  463. return themeName;
  464. }
  465. protected void writeError(VaadinResponse response, Throwable e)
  466. throws IOException {
  467. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
  468. e.getLocalizedMessage());
  469. }
  470. }