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-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.DependencyFilter.FilterContext;
  43. import com.vaadin.server.communication.AtmospherePushConnection;
  44. import com.vaadin.shared.ApplicationConstants;
  45. import com.vaadin.shared.VaadinUriResolver;
  46. import com.vaadin.shared.Version;
  47. import com.vaadin.shared.communication.PushMode;
  48. import com.vaadin.ui.Dependency;
  49. import com.vaadin.ui.Dependency.Type;
  50. import com.vaadin.ui.UI;
  51. import elemental.json.Json;
  52. import elemental.json.JsonException;
  53. import elemental.json.JsonObject;
  54. import elemental.json.impl.JsonUtil;
  55. /**
  56. * Handles the initial request to start the application.
  57. *
  58. * @author Vaadin Ltd
  59. * @since 7.0.0
  60. *
  61. * @deprecated As of 7.0. Will likely change or be removed in a future version
  62. */
  63. @Deprecated
  64. public abstract class BootstrapHandler extends SynchronizedRequestHandler {
  65. /**
  66. * Parameter that is added to the UI init request if the session has already
  67. * been restarted when generating the bootstrap HTML and ?restartApplication
  68. * should thus be ignored when handling the UI init request.
  69. */
  70. public static final String IGNORE_RESTART_PARAM = "ignoreRestart";
  71. /**
  72. * Provides context information for the bootstrap process.
  73. */
  74. protected class BootstrapContext implements Serializable {
  75. private final VaadinResponse response;
  76. private final BootstrapFragmentResponse bootstrapResponse;
  77. private String themeName;
  78. private String appId;
  79. private PushMode pushMode;
  80. private JsonObject applicationParameters;
  81. private BootstrapUriResolver uriResolver;
  82. private WidgetsetInfo widgetsetInfo;
  83. /**
  84. * Creates a new context instance using the given Vaadin/HTTP response
  85. * and bootstrap response.
  86. *
  87. * @param response
  88. * the response object
  89. * @param bootstrapResponse
  90. * the bootstrap response object
  91. */
  92. public BootstrapContext(VaadinResponse response,
  93. BootstrapFragmentResponse bootstrapResponse) {
  94. this.response = response;
  95. this.bootstrapResponse = bootstrapResponse;
  96. }
  97. /**
  98. * Gets the Vaadin/HTTP response.
  99. *
  100. * @return the Vaadin/HTTP response
  101. */
  102. public VaadinResponse getResponse() {
  103. return response;
  104. }
  105. /**
  106. * Gets the Vaadin/HTTP request.
  107. *
  108. * @return the Vaadin/HTTP request
  109. */
  110. public VaadinRequest getRequest() {
  111. return bootstrapResponse.getRequest();
  112. }
  113. /**
  114. * Gets the Vaadin session.
  115. *
  116. * @return the Vaadin session
  117. */
  118. public VaadinSession getSession() {
  119. return bootstrapResponse.getSession();
  120. }
  121. /**
  122. * Gets the UI class which will be used.
  123. *
  124. * @return the UI class
  125. */
  126. public Class<? extends UI> getUIClass() {
  127. return bootstrapResponse.getUiClass();
  128. }
  129. /**
  130. * Gets information about the widgetset to use.
  131. *
  132. * @return the widgetset which will be loaded
  133. */
  134. public WidgetsetInfo getWidgetsetInfo() {
  135. if (widgetsetInfo == null) {
  136. widgetsetInfo = getWidgetsetForUI(this);
  137. }
  138. return widgetsetInfo;
  139. }
  140. /**
  141. * @return returns the name of the widgetset to use
  142. * @deprecated use {@link #getWidgetsetInfo()} instead
  143. */
  144. @Deprecated
  145. public String getWidgetsetName() {
  146. return getWidgetsetInfo().getWidgetsetName();
  147. }
  148. /**
  149. * Gets the name of the theme to use.
  150. *
  151. * @return the name of the theme, with special characters escaped or
  152. * removed
  153. */
  154. public String getThemeName() {
  155. if (themeName == null) {
  156. themeName = findAndEscapeThemeName(this);
  157. }
  158. return themeName;
  159. }
  160. /**
  161. * Gets the push mode to use.
  162. *
  163. * @return the desired push mode
  164. */
  165. public PushMode getPushMode() {
  166. if (pushMode == null) {
  167. UICreateEvent event = new UICreateEvent(getRequest(),
  168. getUIClass());
  169. pushMode = getBootstrapResponse().getUIProvider()
  170. .getPushMode(event);
  171. if (pushMode == null) {
  172. pushMode = getRequest().getService()
  173. .getDeploymentConfiguration().getPushMode();
  174. }
  175. if (pushMode.isEnabled()
  176. && !getRequest().getService().ensurePushAvailable()) {
  177. /*
  178. * Fall back if not supported (ensurePushAvailable will log
  179. * information to the developer the first time this happens)
  180. */
  181. pushMode = PushMode.DISABLED;
  182. }
  183. }
  184. return pushMode;
  185. }
  186. /**
  187. * Gets the application id.
  188. *
  189. * The application id is defined by
  190. * {@link VaadinService#getMainDivId(VaadinSession, VaadinRequest, Class)}
  191. *
  192. * @return the application id
  193. */
  194. public String getAppId() {
  195. if (appId == null) {
  196. appId = getRequest().getService().getMainDivId(getSession(),
  197. getRequest(), getUIClass());
  198. }
  199. return appId;
  200. }
  201. /**
  202. * Gets the bootstrap response object.
  203. *
  204. * @return the bootstrap response object
  205. */
  206. public BootstrapFragmentResponse getBootstrapResponse() {
  207. return bootstrapResponse;
  208. }
  209. /**
  210. * Gets the application parameters specified by the BootstrapHandler.
  211. *
  212. * @return the application parameters which will be written on the page
  213. */
  214. public JsonObject getApplicationParameters() {
  215. if (applicationParameters == null) {
  216. applicationParameters = BootstrapHandler.this
  217. .getApplicationParameters(this);
  218. }
  219. return applicationParameters;
  220. }
  221. /**
  222. * Gets the URI resolver to use for bootstrap resources.
  223. *
  224. * @return the URI resolver
  225. * @since 8.1
  226. */
  227. public BootstrapUriResolver getUriResolver() {
  228. if (uriResolver == null) {
  229. uriResolver = new BootstrapUriResolver(this);
  230. }
  231. return uriResolver;
  232. }
  233. }
  234. /**
  235. * The URI resolver used in the bootstrap process.
  236. *
  237. * @since 8.1
  238. */
  239. protected static class BootstrapUriResolver extends VaadinUriResolver {
  240. private final BootstrapContext context;
  241. private String frontendUrl;
  242. /**
  243. * Creates a new bootstrap resolver based on the given bootstrap
  244. * context.
  245. *
  246. * @param bootstrapContext
  247. * the bootstrap context
  248. */
  249. public BootstrapUriResolver(BootstrapContext bootstrapContext) {
  250. context = bootstrapContext;
  251. }
  252. @Override
  253. protected String getVaadinDirUrl() {
  254. return context.getApplicationParameters()
  255. .getString(ApplicationConstants.VAADIN_DIR_URL);
  256. }
  257. @Override
  258. protected String getThemeUri() {
  259. return getVaadinDirUrl() + "themes/" + context.getThemeName();
  260. }
  261. @Override
  262. protected String getServiceUrlParameterName() {
  263. return getConfigOrNull(
  264. ApplicationConstants.SERVICE_URL_PARAMETER_NAME);
  265. }
  266. @Override
  267. protected String getServiceUrl() {
  268. String serviceUrl = getConfigOrNull(
  269. ApplicationConstants.SERVICE_URL);
  270. if (serviceUrl == null) {
  271. return "./";
  272. } else if (!serviceUrl.endsWith("/")) {
  273. serviceUrl += "/";
  274. }
  275. return serviceUrl;
  276. }
  277. private String getConfigOrNull(String name) {
  278. JsonObject parameters = context.getApplicationParameters();
  279. if (parameters.hasKey(name)) {
  280. return parameters.getString(name);
  281. } else {
  282. return null;
  283. }
  284. }
  285. @Override
  286. protected String encodeQueryStringParameterValue(String queryString) {
  287. String encodedString = null;
  288. try {
  289. encodedString = URLEncoder.encode(queryString, "UTF-8");
  290. } catch (UnsupportedEncodingException e) {
  291. // should never happen
  292. throw new RuntimeException("Could not find UTF-8", e);
  293. }
  294. return encodedString;
  295. }
  296. @Override
  297. protected String getContextRootUrl() {
  298. String root = context.getApplicationParameters()
  299. .getString(ApplicationConstants.CONTEXT_ROOT_URL);
  300. assert root.endsWith("/");
  301. return root;
  302. }
  303. @Override
  304. protected String getFrontendUrl() {
  305. if (frontendUrl == null) {
  306. frontendUrl = resolveFrontendUrl(context.getSession());
  307. }
  308. return frontendUrl;
  309. }
  310. }
  311. /**
  312. * Resolves the URL to use for the {@literal frontend://} protocol.
  313. *
  314. * @param session
  315. * the session of the user to resolve the protocol for
  316. * @return the URL that frontend:// resolves to, possibly using another
  317. * internal protocol
  318. * @since 8.1
  319. */
  320. public static String resolveFrontendUrl(VaadinSession session) {
  321. DeploymentConfiguration configuration = session.getConfiguration();
  322. String frontendUrl;
  323. if (session.getBrowser().isEs6Supported()) {
  324. frontendUrl = configuration.getApplicationOrSystemProperty(
  325. ApplicationConstants.FRONTEND_URL_ES6,
  326. ApplicationConstants.FRONTEND_URL_ES6_DEFAULT_VALUE);
  327. } else {
  328. frontendUrl = configuration.getApplicationOrSystemProperty(
  329. ApplicationConstants.FRONTEND_URL_ES5,
  330. ApplicationConstants.FRONTEND_URL_ES5_DEFAULT_VALUE);
  331. }
  332. if (!frontendUrl.endsWith("/")) {
  333. frontendUrl += "/";
  334. }
  335. return frontendUrl;
  336. }
  337. @Override
  338. protected boolean canHandleRequest(VaadinRequest request) {
  339. // We do not want to handle /APP requests here, instead let it fall
  340. // through and produce a 404
  341. return !ServletPortletHelper.isAppRequest(request);
  342. }
  343. @Override
  344. public boolean synchronizedHandleRequest(VaadinSession session,
  345. VaadinRequest request, VaadinResponse response) throws IOException {
  346. try {
  347. List<UIProvider> uiProviders = session.getUIProviders();
  348. UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
  349. request);
  350. // Find UI provider and UI class
  351. Class<? extends UI> uiClass = null;
  352. UIProvider provider = null;
  353. for (UIProvider p : uiProviders) {
  354. uiClass = p.getUIClass(classSelectionEvent);
  355. // If we found something
  356. if (uiClass != null) {
  357. provider = p;
  358. break;
  359. }
  360. }
  361. if (provider == null) {
  362. // Can't generate bootstrap if no UI provider matches
  363. return false;
  364. }
  365. BootstrapFragmentResponse bootstrapResponse = new BootstrapFragmentResponse(
  366. this, request, session, uiClass, new ArrayList<>(),
  367. provider);
  368. BootstrapContext context = new BootstrapContext(response,
  369. bootstrapResponse);
  370. bootstrapResponse.setUriResolver(context.getUriResolver());
  371. setupMainDiv(context);
  372. BootstrapFragmentResponse fragmentResponse = context
  373. .getBootstrapResponse();
  374. session.modifyBootstrapResponse(fragmentResponse);
  375. String html = getBootstrapHtml(context);
  376. writeBootstrapPage(response, html);
  377. } catch (JsonException e) {
  378. writeError(response, e);
  379. }
  380. return true;
  381. }
  382. private String getBootstrapHtml(BootstrapContext context) {
  383. VaadinRequest request = context.getRequest();
  384. VaadinResponse response = context.getResponse();
  385. VaadinService vaadinService = request.getService();
  386. BootstrapFragmentResponse fragmentResponse = context
  387. .getBootstrapResponse();
  388. if (vaadinService.isStandalone(request)) {
  389. Map<String, Object> headers = new LinkedHashMap<>();
  390. Document document = Document.createShell("");
  391. BootstrapPageResponse pageResponse = new BootstrapPageResponse(this,
  392. request, context.getSession(), context.getUIClass(),
  393. document, headers, fragmentResponse.getUIProvider());
  394. pageResponse.setUriResolver(context.getUriResolver());
  395. List<Node> fragmentNodes = fragmentResponse.getFragmentNodes();
  396. Element body = document.body();
  397. for (Node node : fragmentNodes) {
  398. body.appendChild(node);
  399. }
  400. setupStandaloneDocument(context, pageResponse);
  401. context.getSession().modifyBootstrapResponse(pageResponse);
  402. sendBootstrapHeaders(response, headers);
  403. return document.outerHtml();
  404. } else {
  405. StringBuilder sb = new StringBuilder();
  406. for (Node node : fragmentResponse.getFragmentNodes()) {
  407. if (sb.length() != 0) {
  408. sb.append('\n');
  409. }
  410. sb.append(node.outerHtml());
  411. }
  412. return sb.toString();
  413. }
  414. }
  415. private void sendBootstrapHeaders(VaadinResponse response,
  416. Map<String, Object> headers) {
  417. Set<Entry<String, Object>> entrySet = headers.entrySet();
  418. for (Entry<String, Object> header : entrySet) {
  419. Object value = header.getValue();
  420. if (value instanceof String) {
  421. response.setHeader(header.getKey(), (String) value);
  422. } else if (value instanceof Long) {
  423. response.setDateHeader(header.getKey(),
  424. ((Long) value).longValue());
  425. } else {
  426. throw new RuntimeException(
  427. "Unsupported header value: " + value);
  428. }
  429. }
  430. }
  431. private void writeBootstrapPage(VaadinResponse response, String html)
  432. throws IOException {
  433. response.setContentType(
  434. ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  435. try (BufferedWriter writer = new BufferedWriter(
  436. new OutputStreamWriter(response.getOutputStream(), "UTF-8"))) {
  437. writer.append(html);
  438. }
  439. }
  440. private void setupStandaloneDocument(BootstrapContext context,
  441. BootstrapPageResponse response) {
  442. response.setHeader("Cache-Control", "no-cache");
  443. response.setHeader("Pragma", "no-cache");
  444. response.setDateHeader("Expires", 0);
  445. Document document = response.getDocument();
  446. DocumentType doctype = new DocumentType("html", "", "",
  447. document.baseUri());
  448. document.child(0).before(doctype);
  449. Element head = document.head();
  450. head.appendElement("meta").attr("http-equiv", "Content-Type").attr(
  451. "content", ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  452. /*
  453. * Enable Chrome Frame in all versions of IE if installed.
  454. */
  455. head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
  456. .attr("content", "IE=11;chrome=1");
  457. Class<? extends UI> uiClass = context.getUIClass();
  458. String viewportContent = null;
  459. Viewport viewportAnnotation = uiClass.getAnnotation(Viewport.class);
  460. ViewportGeneratorClass viewportGeneratorClassAnnotation = uiClass
  461. .getAnnotation(ViewportGeneratorClass.class);
  462. if (viewportAnnotation != null
  463. && viewportGeneratorClassAnnotation != null) {
  464. throw new IllegalStateException(uiClass.getCanonicalName()
  465. + " cannot be annotated with both @"
  466. + Viewport.class.getSimpleName() + " and @"
  467. + ViewportGeneratorClass.class.getSimpleName());
  468. }
  469. if (viewportAnnotation != null) {
  470. viewportContent = viewportAnnotation.value();
  471. } else if (viewportGeneratorClassAnnotation != null) {
  472. Class<? extends ViewportGenerator> viewportGeneratorClass = viewportGeneratorClassAnnotation
  473. .value();
  474. try {
  475. viewportContent = viewportGeneratorClass.newInstance()
  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
  507. .findAndFilterDependencies(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(context.getUIClass().getSimpleName()
  578. .toLowerCase(Locale.ENGLISH));
  579. if (style != null && style.length() != 0) {
  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(
  620. new DataNode(builder.toString(), mainScriptTag.baseUri()));
  621. fragmentNodes.add(mainScriptTag);
  622. }
  623. protected void appendMainScriptTagContents(BootstrapContext context,
  624. StringBuilder builder) throws IOException {
  625. JsonObject appConfig = context.getApplicationParameters();
  626. boolean isDebug = !context.getSession().getConfiguration()
  627. .isProductionMode();
  628. if (isDebug) {
  629. /*
  630. * Add tracking needed for getting bootstrap metrics to the client
  631. * side Profiler if another implementation hasn't already been
  632. * added.
  633. */
  634. builder.append(
  635. "if (typeof window.__gwtStatsEvent != 'function') {\n");
  636. builder.append("vaadin.gwtStatsEvents = [];\n");
  637. builder.append(
  638. "window.__gwtStatsEvent = function(event) {vaadin.gwtStatsEvents.push(event); return true;};\n");
  639. builder.append("}\n");
  640. }
  641. builder.append("vaadin.initApplication(\"");
  642. builder.append(context.getAppId());
  643. builder.append("\",");
  644. appendJsonObject(builder, appConfig, isDebug);
  645. builder.append(");\n");
  646. }
  647. private static void appendJsonObject(StringBuilder builder,
  648. JsonObject jsonObject, boolean isDebug) {
  649. if (isDebug) {
  650. builder.append(JsonUtil.stringify(jsonObject, 4));
  651. } else {
  652. builder.append(JsonUtil.stringify(jsonObject));
  653. }
  654. }
  655. protected JsonObject getApplicationParameters(BootstrapContext context) {
  656. VaadinRequest request = context.getRequest();
  657. VaadinSession session = context.getSession();
  658. VaadinService vaadinService = request.getService();
  659. JsonObject appConfig = Json.createObject();
  660. String themeName = context.getThemeName();
  661. if (themeName != null) {
  662. appConfig.put("theme", themeName);
  663. }
  664. // Ignore restartApplication that might be passed to UI init
  665. if (request.getParameter(
  666. VaadinService.URL_PARAMETER_RESTART_APPLICATION) != null) {
  667. appConfig.put("extraParams", "&" + IGNORE_RESTART_PARAM + "=1");
  668. }
  669. JsonObject versionInfo = Json.createObject();
  670. versionInfo.put("vaadinVersion", Version.getFullVersion());
  671. String atmosphereVersion = AtmospherePushConnection
  672. .getAtmosphereVersion();
  673. if (atmosphereVersion != null) {
  674. versionInfo.put("atmosphereVersion", atmosphereVersion);
  675. }
  676. appConfig.put("versionInfo", versionInfo);
  677. WidgetsetInfo widgetsetInfo = context.getWidgetsetInfo();
  678. appConfig.put("widgetset", VaadinServlet
  679. .stripSpecialChars(widgetsetInfo.getWidgetsetName()));
  680. // add widgetset url if not null
  681. if (widgetsetInfo.getWidgetsetUrl() != null) {
  682. appConfig.put("widgetsetUrl", widgetsetInfo.getWidgetsetUrl());
  683. }
  684. appConfig.put("widgetsetReady", !widgetsetInfo.isCdn());
  685. // Use locale from session if set, else from the request
  686. Locale locale = ServletPortletHelper.findLocale(null,
  687. context.getSession(), context.getRequest());
  688. // Get system messages
  689. SystemMessages systemMessages = vaadinService.getSystemMessages(locale,
  690. request);
  691. if (systemMessages != null) {
  692. // Write the CommunicationError -message to client
  693. JsonObject comErrMsg = Json.createObject();
  694. putValueOrNull(comErrMsg, "caption",
  695. systemMessages.getCommunicationErrorCaption());
  696. putValueOrNull(comErrMsg, "message",
  697. systemMessages.getCommunicationErrorMessage());
  698. putValueOrNull(comErrMsg, "url",
  699. systemMessages.getCommunicationErrorURL());
  700. appConfig.put("comErrMsg", comErrMsg);
  701. JsonObject authErrMsg = Json.createObject();
  702. putValueOrNull(authErrMsg, "caption",
  703. systemMessages.getAuthenticationErrorCaption());
  704. putValueOrNull(authErrMsg, "message",
  705. systemMessages.getAuthenticationErrorMessage());
  706. putValueOrNull(authErrMsg, "url",
  707. systemMessages.getAuthenticationErrorURL());
  708. appConfig.put("authErrMsg", authErrMsg);
  709. JsonObject sessExpMsg = Json.createObject();
  710. putValueOrNull(sessExpMsg, "caption",
  711. systemMessages.getSessionExpiredCaption());
  712. putValueOrNull(sessExpMsg, "message",
  713. systemMessages.getSessionExpiredMessage());
  714. putValueOrNull(sessExpMsg, "url",
  715. systemMessages.getSessionExpiredURL());
  716. appConfig.put("sessExpMsg", sessExpMsg);
  717. }
  718. appConfig.put(ApplicationConstants.CONTEXT_ROOT_URL,
  719. getContextRootPath(context));
  720. // getStaticFileLocation documented to never end with a slash
  721. // vaadinDir should always end with a slash
  722. String vaadinDir = vaadinService.getStaticFileLocation(request)
  723. + "/VAADIN/";
  724. appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir);
  725. appConfig.put(ApplicationConstants.FRONTEND_URL,
  726. context.getUriResolver().getFrontendUrl());
  727. if (!session.getConfiguration().isProductionMode()) {
  728. appConfig.put("debug", true);
  729. }
  730. if (vaadinService.isStandalone(request)) {
  731. appConfig.put("standalone", true);
  732. }
  733. appConfig.put("heartbeatInterval", vaadinService
  734. .getDeploymentConfiguration().getHeartbeatInterval());
  735. String serviceUrl = getServiceUrl(context);
  736. if (serviceUrl != null) {
  737. appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
  738. }
  739. boolean sendUrlsAsParameters = vaadinService
  740. .getDeploymentConfiguration().isSendUrlsAsParameters();
  741. if (!sendUrlsAsParameters) {
  742. appConfig.put("sendUrlsAsParameters", false);
  743. }
  744. return appConfig;
  745. }
  746. /**
  747. * @since 8.0.3
  748. */
  749. protected abstract String getContextRootPath(BootstrapContext context);
  750. protected abstract String getServiceUrl(BootstrapContext context);
  751. /**
  752. * Get the URI for the application theme.
  753. *
  754. * A portal-wide default theme is fetched from the portal shared resource
  755. * directory (if any), other themes from the portlet.
  756. *
  757. * @param context
  758. * @param themeName
  759. *
  760. * @return
  761. */
  762. public String getThemeUri(BootstrapContext context, String themeName) {
  763. VaadinRequest request = context.getRequest();
  764. final String staticFilePath = request.getService()
  765. .getStaticFileLocation(request);
  766. return staticFilePath + "/" + VaadinServlet.THEME_DIR_PATH + '/'
  767. + themeName;
  768. }
  769. /**
  770. * Override if required
  771. *
  772. * @param context
  773. * @return
  774. */
  775. public String getThemeName(BootstrapContext context) {
  776. UICreateEvent event = new UICreateEvent(context.getRequest(),
  777. context.getUIClass());
  778. return context.getBootstrapResponse().getUIProvider().getTheme(event);
  779. }
  780. /**
  781. * Do not override.
  782. *
  783. * @param context
  784. * @return
  785. */
  786. public String findAndEscapeThemeName(BootstrapContext context) {
  787. String themeName = getThemeName(context);
  788. if (themeName == null) {
  789. VaadinRequest request = context.getRequest();
  790. themeName = request.getService().getConfiguredTheme(request);
  791. }
  792. // XSS preventation, theme names shouldn't contain special chars anyway.
  793. // The servlet denies them via url parameter.
  794. themeName = VaadinServlet.stripSpecialChars(themeName);
  795. return themeName;
  796. }
  797. protected void writeError(VaadinResponse response, Throwable e)
  798. throws IOException {
  799. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
  800. e.getLocalizedMessage());
  801. }
  802. private void putValueOrNull(JsonObject object, String key, String value) {
  803. assert object != null;
  804. assert key != null;
  805. if (value == null) {
  806. object.put(key, Json.createNull());
  807. } else {
  808. object.put(key, value);
  809. }
  810. }
  811. }