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

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