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

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