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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. /*
  2. * Copyright 2000-2016 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.io.UnsupportedEncodingException;
  22. import java.net.URLEncoder;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.LinkedHashMap;
  27. import java.util.List;
  28. import java.util.Locale;
  29. import java.util.Map;
  30. import java.util.Map.Entry;
  31. import java.util.Set;
  32. import java.util.logging.Logger;
  33. import javax.servlet.http.HttpServletResponse;
  34. import org.jsoup.nodes.DataNode;
  35. import org.jsoup.nodes.Document;
  36. import org.jsoup.nodes.DocumentType;
  37. import org.jsoup.nodes.Element;
  38. import org.jsoup.nodes.Node;
  39. import org.jsoup.parser.Tag;
  40. import com.vaadin.annotations.Viewport;
  41. import com.vaadin.annotations.ViewportGeneratorClass;
  42. import com.vaadin.server.communication.AtmospherePushConnection;
  43. import com.vaadin.shared.ApplicationConstants;
  44. import com.vaadin.shared.VaadinUriResolver;
  45. import com.vaadin.shared.Version;
  46. import com.vaadin.shared.communication.PushMode;
  47. import com.vaadin.ui.Dependency;
  48. import com.vaadin.ui.Dependency.Type;
  49. import com.vaadin.ui.UI;
  50. import elemental.json.Json;
  51. import elemental.json.JsonException;
  52. import elemental.json.JsonObject;
  53. import elemental.json.impl.JsonUtil;
  54. /**
  55. *
  56. * @author Vaadin Ltd
  57. * @since 7.0.0
  58. *
  59. * @deprecated As of 7.0. Will likely change or be removed in a future version
  60. */
  61. @Deprecated
  62. public abstract class BootstrapHandler extends SynchronizedRequestHandler {
  63. /**
  64. * Parameter that is added to the UI init request if the session has already
  65. * been restarted when generating the bootstrap HTML and ?restartApplication
  66. * should thus be ignored when handling the UI init request.
  67. */
  68. public static final String IGNORE_RESTART_PARAM = "ignoreRestart";
  69. protected class BootstrapContext implements Serializable {
  70. private final VaadinResponse response;
  71. private final BootstrapFragmentResponse bootstrapResponse;
  72. private String themeName;
  73. private String appId;
  74. private PushMode pushMode;
  75. private JsonObject applicationParameters;
  76. private VaadinUriResolver uriResolver;
  77. private WidgetsetInfo widgetsetInfo;
  78. public BootstrapContext(VaadinResponse response,
  79. BootstrapFragmentResponse bootstrapResponse) {
  80. this.response = response;
  81. this.bootstrapResponse = bootstrapResponse;
  82. }
  83. public VaadinResponse getResponse() {
  84. return response;
  85. }
  86. public VaadinRequest getRequest() {
  87. return bootstrapResponse.getRequest();
  88. }
  89. public VaadinSession getSession() {
  90. return bootstrapResponse.getSession();
  91. }
  92. public Class<? extends UI> getUIClass() {
  93. return bootstrapResponse.getUiClass();
  94. }
  95. public WidgetsetInfo getWidgetsetInfo() {
  96. if (widgetsetInfo == null) {
  97. widgetsetInfo = getWidgetsetForUI(this);
  98. }
  99. return widgetsetInfo;
  100. }
  101. /**
  102. * @return returns the name of the widgetset to use
  103. * @deprecated use {@link #getWidgetsetInfo()} instead
  104. */
  105. @Deprecated
  106. public String getWidgetsetName() {
  107. return getWidgetsetInfo().getWidgetsetName();
  108. }
  109. public String getThemeName() {
  110. if (themeName == null) {
  111. themeName = findAndEscapeThemeName(this);
  112. }
  113. return themeName;
  114. }
  115. public PushMode getPushMode() {
  116. if (pushMode == null) {
  117. UICreateEvent event = new UICreateEvent(getRequest(),
  118. getUIClass());
  119. pushMode = getBootstrapResponse().getUIProvider()
  120. .getPushMode(event);
  121. if (pushMode == null) {
  122. pushMode = getRequest().getService()
  123. .getDeploymentConfiguration().getPushMode();
  124. }
  125. if (pushMode.isEnabled()
  126. && !getRequest().getService().ensurePushAvailable()) {
  127. /*
  128. * Fall back if not supported (ensurePushAvailable will log
  129. * information to the developer the first time this happens)
  130. */
  131. pushMode = PushMode.DISABLED;
  132. }
  133. }
  134. return pushMode;
  135. }
  136. public String getAppId() {
  137. if (appId == null) {
  138. appId = getRequest().getService().getMainDivId(getSession(),
  139. getRequest(), getUIClass());
  140. }
  141. return appId;
  142. }
  143. public BootstrapFragmentResponse getBootstrapResponse() {
  144. return bootstrapResponse;
  145. }
  146. public JsonObject getApplicationParameters() {
  147. if (applicationParameters == null) {
  148. applicationParameters = BootstrapHandler.this
  149. .getApplicationParameters(this);
  150. }
  151. return applicationParameters;
  152. }
  153. public VaadinUriResolver getUriResolver() {
  154. if (uriResolver == null) {
  155. uriResolver = new BootstrapUriResolver(this);
  156. }
  157. return uriResolver;
  158. }
  159. }
  160. private class BootstrapUriResolver extends VaadinUriResolver {
  161. private final BootstrapContext context;
  162. public BootstrapUriResolver(BootstrapContext bootstrapContext) {
  163. context = bootstrapContext;
  164. }
  165. @Override
  166. protected String getVaadinDirUrl() {
  167. return context.getApplicationParameters()
  168. .getString(ApplicationConstants.VAADIN_DIR_URL);
  169. }
  170. @Override
  171. protected String getThemeUri() {
  172. return getVaadinDirUrl() + "themes/" + context.getThemeName();
  173. }
  174. @Override
  175. protected String getServiceUrlParameterName() {
  176. return getConfigOrNull(
  177. ApplicationConstants.SERVICE_URL_PARAMETER_NAME);
  178. }
  179. @Override
  180. protected String getServiceUrl() {
  181. String serviceUrl = getConfigOrNull(
  182. ApplicationConstants.SERVICE_URL);
  183. if (serviceUrl == null) {
  184. return "./";
  185. } else if (!serviceUrl.endsWith("/")) {
  186. serviceUrl += "/";
  187. }
  188. return serviceUrl;
  189. }
  190. private String getConfigOrNull(String name) {
  191. JsonObject parameters = context.getApplicationParameters();
  192. if (parameters.hasKey(name)) {
  193. return parameters.getString(name);
  194. } else {
  195. return null;
  196. }
  197. }
  198. @Override
  199. protected String encodeQueryStringParameterValue(String queryString) {
  200. String encodedString = null;
  201. try {
  202. encodedString = URLEncoder.encode(queryString, "UTF-8");
  203. } catch (UnsupportedEncodingException e) {
  204. // should never happen
  205. throw new RuntimeException("Could not find UTF-8", e);
  206. }
  207. return encodedString;
  208. }
  209. @Override
  210. protected String getContextRootUrl() {
  211. String root = context.getApplicationParameters()
  212. .getString(ApplicationConstants.CONTEXT_ROOT_URL);
  213. assert root.endsWith("/");
  214. return root;
  215. }
  216. }
  217. @Override
  218. protected boolean canHandleRequest(VaadinRequest request) {
  219. // We do not want to handle /APP requests here, instead let it fall
  220. // through and produce a 404
  221. return !ServletPortletHelper.isAppRequest(request);
  222. }
  223. @Override
  224. public boolean synchronizedHandleRequest(VaadinSession session,
  225. VaadinRequest request, VaadinResponse response) throws IOException {
  226. try {
  227. List<UIProvider> uiProviders = session.getUIProviders();
  228. UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
  229. request);
  230. // Find UI provider and UI class
  231. Class<? extends UI> uiClass = null;
  232. UIProvider provider = null;
  233. for (UIProvider p : uiProviders) {
  234. uiClass = p.getUIClass(classSelectionEvent);
  235. // If we found something
  236. if (uiClass != null) {
  237. provider = p;
  238. break;
  239. }
  240. }
  241. if (provider == null) {
  242. // Can't generate bootstrap if no UI provider matches
  243. return false;
  244. }
  245. BootstrapContext context = new BootstrapContext(response,
  246. new BootstrapFragmentResponse(this, request, session,
  247. uiClass, new ArrayList<>(), provider));
  248. setupMainDiv(context);
  249. BootstrapFragmentResponse fragmentResponse = context
  250. .getBootstrapResponse();
  251. session.modifyBootstrapResponse(fragmentResponse);
  252. String html = getBootstrapHtml(context);
  253. writeBootstrapPage(response, html);
  254. } catch (JsonException e) {
  255. writeError(response, e);
  256. }
  257. return true;
  258. }
  259. private String getBootstrapHtml(BootstrapContext context) {
  260. VaadinRequest request = context.getRequest();
  261. VaadinResponse response = context.getResponse();
  262. VaadinService vaadinService = request.getService();
  263. BootstrapFragmentResponse fragmentResponse = context
  264. .getBootstrapResponse();
  265. if (vaadinService.isStandalone(request)) {
  266. Map<String, Object> headers = new LinkedHashMap<>();
  267. Document document = Document.createShell("");
  268. BootstrapPageResponse pageResponse = new BootstrapPageResponse(this,
  269. request, context.getSession(), context.getUIClass(),
  270. document, headers, fragmentResponse.getUIProvider());
  271. List<Node> fragmentNodes = fragmentResponse.getFragmentNodes();
  272. Element body = document.body();
  273. for (Node node : fragmentNodes) {
  274. body.appendChild(node);
  275. }
  276. setupStandaloneDocument(context, pageResponse);
  277. context.getSession().modifyBootstrapResponse(pageResponse);
  278. sendBootstrapHeaders(response, headers);
  279. return document.outerHtml();
  280. } else {
  281. StringBuilder sb = new StringBuilder();
  282. for (Node node : fragmentResponse.getFragmentNodes()) {
  283. if (sb.length() != 0) {
  284. sb.append('\n');
  285. }
  286. sb.append(node.outerHtml());
  287. }
  288. return sb.toString();
  289. }
  290. }
  291. private void sendBootstrapHeaders(VaadinResponse response,
  292. Map<String, Object> headers) {
  293. Set<Entry<String, Object>> entrySet = headers.entrySet();
  294. for (Entry<String, Object> header : entrySet) {
  295. Object value = header.getValue();
  296. if (value instanceof String) {
  297. response.setHeader(header.getKey(), (String) value);
  298. } else if (value instanceof Long) {
  299. response.setDateHeader(header.getKey(),
  300. ((Long) value).longValue());
  301. } else {
  302. throw new RuntimeException(
  303. "Unsupported header value: " + value);
  304. }
  305. }
  306. }
  307. private void writeBootstrapPage(VaadinResponse response, String html)
  308. throws IOException {
  309. response.setContentType(
  310. ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  311. try (BufferedWriter writer = new BufferedWriter(
  312. new OutputStreamWriter(response.getOutputStream(), "UTF-8"))) {
  313. writer.append(html);
  314. }
  315. }
  316. private void setupStandaloneDocument(BootstrapContext context,
  317. BootstrapPageResponse response) {
  318. response.setHeader("Cache-Control", "no-cache");
  319. response.setHeader("Pragma", "no-cache");
  320. response.setDateHeader("Expires", 0);
  321. Document document = response.getDocument();
  322. DocumentType doctype = new DocumentType("html", "", "",
  323. document.baseUri());
  324. document.child(0).before(doctype);
  325. Element head = document.head();
  326. head.appendElement("meta").attr("http-equiv", "Content-Type").attr(
  327. "content", ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  328. /*
  329. * Enable Chrome Frame in all versions of IE if installed.
  330. */
  331. head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
  332. .attr("content", "IE=11;chrome=1");
  333. Class<? extends UI> uiClass = context.getUIClass();
  334. String viewportContent = null;
  335. Viewport viewportAnnotation = uiClass.getAnnotation(Viewport.class);
  336. ViewportGeneratorClass viewportGeneratorClassAnnotation = uiClass
  337. .getAnnotation(ViewportGeneratorClass.class);
  338. if (viewportAnnotation != null
  339. && viewportGeneratorClassAnnotation != null) {
  340. throw new IllegalStateException(uiClass.getCanonicalName()
  341. + " cannot be annotated with both @"
  342. + Viewport.class.getSimpleName() + " and @"
  343. + ViewportGeneratorClass.class.getSimpleName());
  344. }
  345. if (viewportAnnotation != null) {
  346. viewportContent = viewportAnnotation.value();
  347. } else if (viewportGeneratorClassAnnotation != null) {
  348. Class<? extends ViewportGenerator> viewportGeneratorClass = viewportGeneratorClassAnnotation
  349. .value();
  350. try {
  351. viewportContent = viewportGeneratorClass.newInstance()
  352. .getViewport(context.getRequest());
  353. } catch (Exception e) {
  354. throw new RuntimeException(
  355. "Error processing viewport generator "
  356. + viewportGeneratorClass.getCanonicalName(),
  357. e);
  358. }
  359. }
  360. if (viewportContent != null) {
  361. head.appendElement("meta").attr("name", "viewport").attr("content",
  362. viewportContent);
  363. }
  364. String title = response.getUIProvider().getPageTitle(
  365. new UICreateEvent(context.getRequest(), context.getUIClass()));
  366. if (title != null) {
  367. head.appendElement("title").appendText(title);
  368. }
  369. head.appendElement("style").attr("type", "text/css")
  370. .appendText("html, body {height:100%;margin:0;}");
  371. // Add favicon links
  372. String themeName = context.getThemeName();
  373. if (themeName != null) {
  374. String themeUri = getThemeUri(context, themeName);
  375. head.appendElement("link").attr("rel", "shortcut icon")
  376. .attr("type", "image/vnd.microsoft.icon")
  377. .attr("href", themeUri + "/favicon.ico");
  378. head.appendElement("link").attr("rel", "icon")
  379. .attr("type", "image/vnd.microsoft.icon")
  380. .attr("href", themeUri + "/favicon.ico");
  381. }
  382. Collection<? extends Dependency> deps = Dependency.findDependencies(
  383. Collections.singletonList(uiClass),
  384. context.getSession().getCommunicationManager());
  385. for (Dependency dependency : deps) {
  386. Type type = dependency.getType();
  387. String url = context.getUriResolver()
  388. .resolveVaadinUri(dependency.getUrl());
  389. if (type == Type.HTMLIMPORT) {
  390. head.appendElement("link").attr("rel", "import").attr("href",
  391. url);
  392. } else if (type == Type.JAVASCRIPT) {
  393. head.appendElement("script").attr("type", "text/javascript")
  394. .attr("src", url);
  395. } else if (type == Type.STYLESHEET) {
  396. head.appendElement("link").attr("rel", "stylesheet")
  397. .attr("type", "text/css").attr("href", url);
  398. } else {
  399. getLogger().severe("Ignoring unknown dependency type "
  400. + dependency.getType());
  401. }
  402. }
  403. Element body = document.body();
  404. body.attr("scroll", "auto");
  405. body.addClass(ApplicationConstants.GENERATED_BODY_CLASSNAME);
  406. }
  407. private static Logger getLogger() {
  408. return Logger.getLogger(BootstrapHandler.class.getName());
  409. }
  410. protected String getMainDivStyle(BootstrapContext context) {
  411. return null;
  412. }
  413. public WidgetsetInfo getWidgetsetForUI(BootstrapContext context) {
  414. VaadinRequest request = context.getRequest();
  415. UICreateEvent event = new UICreateEvent(context.getRequest(),
  416. context.getUIClass());
  417. WidgetsetInfo widgetset = context.getBootstrapResponse().getUIProvider()
  418. .getWidgetsetInfo(event);
  419. if (widgetset == null) {
  420. // TODO do we want to move WidgetsetInfoImpl elsewhere?
  421. widgetset = new WidgetsetInfoImpl(
  422. request.getService().getConfiguredWidgetset(request));
  423. }
  424. return widgetset;
  425. }
  426. /**
  427. * Method to write the div element into which that actual Vaadin application
  428. * is rendered.
  429. * <p>
  430. * Override this method if you want to add some custom html around around
  431. * the div element into which the actual Vaadin application will be
  432. * rendered.
  433. *
  434. * @param context
  435. *
  436. * @throws IOException
  437. */
  438. private void setupMainDiv(BootstrapContext context) throws IOException {
  439. String style = getMainDivStyle(context);
  440. /*- Add classnames;
  441. * .v-app
  442. * .v-app-loading
  443. *- Additionally added from javascript:
  444. * <themeName, remove non-alphanum>
  445. */
  446. List<Node> fragmentNodes = context.getBootstrapResponse()
  447. .getFragmentNodes();
  448. Element mainDiv = new Element(Tag.valueOf("div"), "");
  449. mainDiv.attr("id", context.getAppId());
  450. mainDiv.addClass("v-app");
  451. mainDiv.addClass(context.getThemeName());
  452. mainDiv.addClass(context.getUIClass().getSimpleName()
  453. .toLowerCase(Locale.ENGLISH));
  454. if (style != null && style.length() != 0) {
  455. mainDiv.attr("style", style);
  456. }
  457. mainDiv.appendElement("div").addClass("v-app-loading");
  458. mainDiv.appendElement("noscript").append(
  459. "You have to enable javascript in your browser to use an application built with Vaadin.");
  460. fragmentNodes.add(mainDiv);
  461. VaadinRequest request = context.getRequest();
  462. VaadinService vaadinService = request.getService();
  463. String vaadinLocation = vaadinService.getStaticFileLocation(request)
  464. + "/VAADIN/";
  465. // Parameter appended to JS to bypass caches after version upgrade.
  466. String versionQueryParam = "?v=" + Version.getFullVersion();
  467. if (context.getPushMode().isEnabled()) {
  468. // Load client-side dependencies for push support
  469. String pushJS = vaadinLocation;
  470. if (context.getRequest().getService().getDeploymentConfiguration()
  471. .isProductionMode()) {
  472. pushJS += ApplicationConstants.VAADIN_PUSH_JS;
  473. } else {
  474. pushJS += ApplicationConstants.VAADIN_PUSH_DEBUG_JS;
  475. }
  476. pushJS += versionQueryParam;
  477. fragmentNodes.add(new Element(Tag.valueOf("script"), "")
  478. .attr("type", "text/javascript").attr("src", pushJS));
  479. }
  480. String bootstrapLocation = vaadinLocation
  481. + ApplicationConstants.VAADIN_BOOTSTRAP_JS + versionQueryParam;
  482. fragmentNodes.add(new Element(Tag.valueOf("script"), "")
  483. .attr("type", "text/javascript")
  484. .attr("src", bootstrapLocation));
  485. Element mainScriptTag = new Element(Tag.valueOf("script"), "")
  486. .attr("type", "text/javascript");
  487. StringBuilder builder = new StringBuilder();
  488. builder.append("//<![CDATA[\n");
  489. builder.append("if (!window.vaadin) alert(" + JsonUtil.quote(
  490. "Failed to load the bootstrap javascript: " + bootstrapLocation)
  491. + ");\n");
  492. appendMainScriptTagContents(context, builder);
  493. builder.append("//]]>");
  494. mainScriptTag.appendChild(
  495. new DataNode(builder.toString(), mainScriptTag.baseUri()));
  496. fragmentNodes.add(mainScriptTag);
  497. }
  498. protected void appendMainScriptTagContents(BootstrapContext context,
  499. StringBuilder builder) throws IOException {
  500. JsonObject appConfig = context.getApplicationParameters();
  501. boolean isDebug = !context.getSession().getConfiguration()
  502. .isProductionMode();
  503. if (isDebug) {
  504. /*
  505. * Add tracking needed for getting bootstrap metrics to the client
  506. * side Profiler if another implementation hasn't already been
  507. * added.
  508. */
  509. builder.append(
  510. "if (typeof window.__gwtStatsEvent != 'function') {\n");
  511. builder.append("vaadin.gwtStatsEvents = [];\n");
  512. builder.append(
  513. "window.__gwtStatsEvent = function(event) {vaadin.gwtStatsEvents.push(event); return true;};\n");
  514. builder.append("}\n");
  515. }
  516. builder.append("vaadin.initApplication(\"");
  517. builder.append(context.getAppId());
  518. builder.append("\",");
  519. appendJsonObject(builder, appConfig, isDebug);
  520. builder.append(");\n");
  521. }
  522. private static void appendJsonObject(StringBuilder builder,
  523. JsonObject jsonObject, boolean isDebug) {
  524. if (isDebug) {
  525. builder.append(JsonUtil.stringify(jsonObject, 4));
  526. } else {
  527. builder.append(JsonUtil.stringify(jsonObject));
  528. }
  529. }
  530. protected JsonObject getApplicationParameters(BootstrapContext context) {
  531. VaadinRequest request = context.getRequest();
  532. VaadinSession session = context.getSession();
  533. VaadinService vaadinService = request.getService();
  534. JsonObject appConfig = Json.createObject();
  535. String themeName = context.getThemeName();
  536. if (themeName != null) {
  537. appConfig.put("theme", themeName);
  538. }
  539. // Ignore restartApplication that might be passed to UI init
  540. if (request.getParameter(
  541. VaadinService.URL_PARAMETER_RESTART_APPLICATION) != null) {
  542. appConfig.put("extraParams", "&" + IGNORE_RESTART_PARAM + "=1");
  543. }
  544. JsonObject versionInfo = Json.createObject();
  545. versionInfo.put("vaadinVersion", Version.getFullVersion());
  546. String atmosphereVersion = AtmospherePushConnection
  547. .getAtmosphereVersion();
  548. if (atmosphereVersion != null) {
  549. versionInfo.put("atmosphereVersion", atmosphereVersion);
  550. }
  551. appConfig.put("versionInfo", versionInfo);
  552. WidgetsetInfo widgetsetInfo = context.getWidgetsetInfo();
  553. appConfig.put("widgetset", VaadinServlet
  554. .stripSpecialChars(widgetsetInfo.getWidgetsetName()));
  555. // add widgetset url if not null
  556. if (widgetsetInfo.getWidgetsetUrl() != null) {
  557. appConfig.put("widgetsetUrl", widgetsetInfo.getWidgetsetUrl());
  558. }
  559. appConfig.put("widgetsetReady", !widgetsetInfo.isCdn());
  560. // Use locale from session if set, else from the request
  561. Locale locale = ServletPortletHelper.findLocale(null,
  562. context.getSession(), context.getRequest());
  563. // Get system messages
  564. SystemMessages systemMessages = vaadinService.getSystemMessages(locale,
  565. request);
  566. if (systemMessages != null) {
  567. // Write the CommunicationError -message to client
  568. JsonObject comErrMsg = Json.createObject();
  569. putValueOrNull(comErrMsg, "caption",
  570. systemMessages.getCommunicationErrorCaption());
  571. putValueOrNull(comErrMsg, "message",
  572. systemMessages.getCommunicationErrorMessage());
  573. putValueOrNull(comErrMsg, "url",
  574. systemMessages.getCommunicationErrorURL());
  575. appConfig.put("comErrMsg", comErrMsg);
  576. JsonObject authErrMsg = Json.createObject();
  577. putValueOrNull(authErrMsg, "caption",
  578. systemMessages.getAuthenticationErrorCaption());
  579. putValueOrNull(authErrMsg, "message",
  580. systemMessages.getAuthenticationErrorMessage());
  581. putValueOrNull(authErrMsg, "url",
  582. systemMessages.getAuthenticationErrorURL());
  583. appConfig.put("authErrMsg", authErrMsg);
  584. JsonObject sessExpMsg = Json.createObject();
  585. putValueOrNull(sessExpMsg, "caption",
  586. systemMessages.getSessionExpiredCaption());
  587. putValueOrNull(sessExpMsg, "message",
  588. systemMessages.getSessionExpiredMessage());
  589. putValueOrNull(sessExpMsg, "url",
  590. systemMessages.getSessionExpiredURL());
  591. appConfig.put("sessExpMsg", sessExpMsg);
  592. }
  593. appConfig.put(ApplicationConstants.CONTEXT_ROOT_URL,
  594. getContextRootPath(context));
  595. // getStaticFileLocation documented to never end with a slash
  596. // vaadinDir should always end with a slash
  597. String vaadinDir = vaadinService.getStaticFileLocation(request)
  598. + "/VAADIN/";
  599. appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir);
  600. if (!session.getConfiguration().isProductionMode()) {
  601. appConfig.put("debug", true);
  602. }
  603. if (vaadinService.isStandalone(request)) {
  604. appConfig.put("standalone", true);
  605. }
  606. appConfig.put("heartbeatInterval", vaadinService
  607. .getDeploymentConfiguration().getHeartbeatInterval());
  608. String serviceUrl = getServiceUrl(context);
  609. if (serviceUrl != null) {
  610. appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
  611. }
  612. boolean sendUrlsAsParameters = vaadinService
  613. .getDeploymentConfiguration().isSendUrlsAsParameters();
  614. if (!sendUrlsAsParameters) {
  615. appConfig.put("sendUrlsAsParameters", false);
  616. }
  617. return appConfig;
  618. }
  619. /**
  620. * @since 8.0.3
  621. */
  622. protected abstract String getContextRootPath(BootstrapContext context);
  623. protected abstract String getServiceUrl(BootstrapContext context);
  624. /**
  625. * Get the URI for the application theme.
  626. *
  627. * A portal-wide default theme is fetched from the portal shared resource
  628. * directory (if any), other themes from the portlet.
  629. *
  630. * @param context
  631. * @param themeName
  632. *
  633. * @return
  634. */
  635. public String getThemeUri(BootstrapContext context, String themeName) {
  636. VaadinRequest request = context.getRequest();
  637. final String staticFilePath = request.getService()
  638. .getStaticFileLocation(request);
  639. return staticFilePath + "/" + VaadinServlet.THEME_DIR_PATH + '/'
  640. + themeName;
  641. }
  642. /**
  643. * Override if required
  644. *
  645. * @param context
  646. * @return
  647. */
  648. public String getThemeName(BootstrapContext context) {
  649. UICreateEvent event = new UICreateEvent(context.getRequest(),
  650. context.getUIClass());
  651. return context.getBootstrapResponse().getUIProvider().getTheme(event);
  652. }
  653. /**
  654. * Do not override.
  655. *
  656. * @param context
  657. * @return
  658. */
  659. public String findAndEscapeThemeName(BootstrapContext context) {
  660. String themeName = getThemeName(context);
  661. if (themeName == null) {
  662. VaadinRequest request = context.getRequest();
  663. themeName = request.getService().getConfiguredTheme(request);
  664. }
  665. // XSS preventation, theme names shouldn't contain special chars anyway.
  666. // The servlet denies them via url parameter.
  667. themeName = VaadinServlet.stripSpecialChars(themeName);
  668. return themeName;
  669. }
  670. protected void writeError(VaadinResponse response, Throwable e)
  671. throws IOException {
  672. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
  673. e.getLocalizedMessage());
  674. }
  675. private void putValueOrNull(JsonObject object, String key, String value) {
  676. assert object != null;
  677. assert key != null;
  678. if (value == null) {
  679. object.put(key, Json.createNull());
  680. } else {
  681. object.put(key, value);
  682. }
  683. }
  684. }