Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

BootstrapHandler.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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. */
  226. public BootstrapUriResolver getUriResolver() {
  227. if (uriResolver == null) {
  228. uriResolver = new BootstrapUriResolver(this);
  229. }
  230. return uriResolver;
  231. }
  232. }
  233. /**
  234. * The URI resolver used in the bootstrap process.
  235. */
  236. protected static class BootstrapUriResolver extends VaadinUriResolver {
  237. private final BootstrapContext context;
  238. private String frontendUrl;
  239. /**
  240. * Creates a new bootstrap resolver based on the given bootstrap
  241. * context.
  242. *
  243. * @param bootstrapContext
  244. * the bootstrap context
  245. */
  246. public BootstrapUriResolver(BootstrapContext bootstrapContext) {
  247. context = bootstrapContext;
  248. }
  249. @Override
  250. protected String getVaadinDirUrl() {
  251. return context.getApplicationParameters()
  252. .getString(ApplicationConstants.VAADIN_DIR_URL);
  253. }
  254. @Override
  255. protected String getThemeUri() {
  256. return getVaadinDirUrl() + "themes/" + context.getThemeName();
  257. }
  258. @Override
  259. protected String getServiceUrlParameterName() {
  260. return getConfigOrNull(
  261. ApplicationConstants.SERVICE_URL_PARAMETER_NAME);
  262. }
  263. @Override
  264. protected String getServiceUrl() {
  265. String serviceUrl = getConfigOrNull(
  266. ApplicationConstants.SERVICE_URL);
  267. if (serviceUrl == null) {
  268. return "./";
  269. } else if (!serviceUrl.endsWith("/")) {
  270. serviceUrl += "/";
  271. }
  272. return serviceUrl;
  273. }
  274. private String getConfigOrNull(String name) {
  275. JsonObject parameters = context.getApplicationParameters();
  276. if (parameters.hasKey(name)) {
  277. return parameters.getString(name);
  278. } else {
  279. return null;
  280. }
  281. }
  282. @Override
  283. protected String encodeQueryStringParameterValue(String queryString) {
  284. String encodedString = null;
  285. try {
  286. encodedString = URLEncoder.encode(queryString, "UTF-8");
  287. } catch (UnsupportedEncodingException e) {
  288. // should never happen
  289. throw new RuntimeException("Could not find UTF-8", e);
  290. }
  291. return encodedString;
  292. }
  293. @Override
  294. protected String getContextRootUrl() {
  295. String root = context.getApplicationParameters()
  296. .getString(ApplicationConstants.CONTEXT_ROOT_URL);
  297. assert root.endsWith("/");
  298. return root;
  299. }
  300. @Override
  301. protected String getFrontendUrl() {
  302. if (frontendUrl == null) {
  303. frontendUrl = resolveFrontendUrl(context.getSession());
  304. }
  305. return frontendUrl;
  306. }
  307. }
  308. /**
  309. * Resolves the URL to use for the {@literal frontend://} protocol.
  310. *
  311. * @param session
  312. * the session of the user to resolve the protocol for
  313. * @return the URL that frontend:// resolves to, possibly using another
  314. * internal protocol
  315. */
  316. public static String resolveFrontendUrl(VaadinSession session) {
  317. DeploymentConfiguration configuration = session.getConfiguration();
  318. String frontendUrl;
  319. if (session.getBrowser().isEs6Supported()) {
  320. frontendUrl = configuration.getApplicationOrSystemProperty(
  321. ApplicationConstants.FRONTEND_URL_ES6,
  322. ApplicationConstants.FRONTEND_URL_ES6_DEFAULT_VALUE);
  323. } else {
  324. frontendUrl = configuration.getApplicationOrSystemProperty(
  325. ApplicationConstants.FRONTEND_URL_ES5,
  326. ApplicationConstants.FRONTEND_URL_ES5_DEFAULT_VALUE);
  327. }
  328. if (!frontendUrl.endsWith("/")) {
  329. frontendUrl += "/";
  330. }
  331. return frontendUrl;
  332. }
  333. @Override
  334. protected boolean canHandleRequest(VaadinRequest request) {
  335. // We do not want to handle /APP requests here, instead let it fall
  336. // through and produce a 404
  337. return !ServletPortletHelper.isAppRequest(request);
  338. }
  339. @Override
  340. public boolean synchronizedHandleRequest(VaadinSession session,
  341. VaadinRequest request, VaadinResponse response) throws IOException {
  342. try {
  343. List<UIProvider> uiProviders = session.getUIProviders();
  344. UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
  345. request);
  346. // Find UI provider and UI class
  347. Class<? extends UI> uiClass = null;
  348. UIProvider provider = null;
  349. for (UIProvider p : uiProviders) {
  350. uiClass = p.getUIClass(classSelectionEvent);
  351. // If we found something
  352. if (uiClass != null) {
  353. provider = p;
  354. break;
  355. }
  356. }
  357. if (provider == null) {
  358. // Can't generate bootstrap if no UI provider matches
  359. return false;
  360. }
  361. BootstrapFragmentResponse bootstrapResponse = new BootstrapFragmentResponse(
  362. this, request, session, uiClass, new ArrayList<>(),
  363. provider);
  364. BootstrapContext context = new BootstrapContext(response,
  365. bootstrapResponse);
  366. bootstrapResponse.setUriResolver(context.getUriResolver());
  367. setupMainDiv(context);
  368. BootstrapFragmentResponse fragmentResponse = context
  369. .getBootstrapResponse();
  370. session.modifyBootstrapResponse(fragmentResponse);
  371. String html = getBootstrapHtml(context);
  372. writeBootstrapPage(response, html);
  373. } catch (JsonException e) {
  374. writeError(response, e);
  375. }
  376. return true;
  377. }
  378. private String getBootstrapHtml(BootstrapContext context) {
  379. VaadinRequest request = context.getRequest();
  380. VaadinResponse response = context.getResponse();
  381. VaadinService vaadinService = request.getService();
  382. BootstrapFragmentResponse fragmentResponse = context
  383. .getBootstrapResponse();
  384. if (vaadinService.isStandalone(request)) {
  385. Map<String, Object> headers = new LinkedHashMap<>();
  386. Document document = Document.createShell("");
  387. BootstrapPageResponse pageResponse = new BootstrapPageResponse(this,
  388. request, context.getSession(), context.getUIClass(),
  389. document, headers, fragmentResponse.getUIProvider());
  390. pageResponse.setUriResolver(context.getUriResolver());
  391. List<Node> fragmentNodes = fragmentResponse.getFragmentNodes();
  392. Element body = document.body();
  393. for (Node node : fragmentNodes) {
  394. body.appendChild(node);
  395. }
  396. setupStandaloneDocument(context, pageResponse);
  397. context.getSession().modifyBootstrapResponse(pageResponse);
  398. sendBootstrapHeaders(response, headers);
  399. return document.outerHtml();
  400. } else {
  401. StringBuilder sb = new StringBuilder();
  402. for (Node node : fragmentResponse.getFragmentNodes()) {
  403. if (sb.length() != 0) {
  404. sb.append('\n');
  405. }
  406. sb.append(node.outerHtml());
  407. }
  408. return sb.toString();
  409. }
  410. }
  411. private void sendBootstrapHeaders(VaadinResponse response,
  412. Map<String, Object> headers) {
  413. Set<Entry<String, Object>> entrySet = headers.entrySet();
  414. for (Entry<String, Object> header : entrySet) {
  415. Object value = header.getValue();
  416. if (value instanceof String) {
  417. response.setHeader(header.getKey(), (String) value);
  418. } else if (value instanceof Long) {
  419. response.setDateHeader(header.getKey(),
  420. ((Long) value).longValue());
  421. } else {
  422. throw new RuntimeException(
  423. "Unsupported header value: " + value);
  424. }
  425. }
  426. }
  427. private void writeBootstrapPage(VaadinResponse response, String html)
  428. throws IOException {
  429. response.setContentType(
  430. ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  431. try (BufferedWriter writer = new BufferedWriter(
  432. new OutputStreamWriter(response.getOutputStream(), "UTF-8"))) {
  433. writer.append(html);
  434. }
  435. }
  436. private void setupStandaloneDocument(BootstrapContext context,
  437. BootstrapPageResponse response) {
  438. response.setHeader("Cache-Control", "no-cache");
  439. response.setHeader("Pragma", "no-cache");
  440. response.setDateHeader("Expires", 0);
  441. Document document = response.getDocument();
  442. DocumentType doctype = new DocumentType("html", "", "",
  443. document.baseUri());
  444. document.child(0).before(doctype);
  445. Element head = document.head();
  446. head.appendElement("meta").attr("http-equiv", "Content-Type").attr(
  447. "content", ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8);
  448. /*
  449. * Enable Chrome Frame in all versions of IE if installed.
  450. */
  451. head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
  452. .attr("content", "IE=11;chrome=1");
  453. Class<? extends UI> uiClass = context.getUIClass();
  454. String viewportContent = null;
  455. Viewport viewportAnnotation = uiClass.getAnnotation(Viewport.class);
  456. ViewportGeneratorClass viewportGeneratorClassAnnotation = uiClass
  457. .getAnnotation(ViewportGeneratorClass.class);
  458. if (viewportAnnotation != null
  459. && viewportGeneratorClassAnnotation != null) {
  460. throw new IllegalStateException(uiClass.getCanonicalName()
  461. + " cannot be annotated with both @"
  462. + Viewport.class.getSimpleName() + " and @"
  463. + ViewportGeneratorClass.class.getSimpleName());
  464. }
  465. if (viewportAnnotation != null) {
  466. viewportContent = viewportAnnotation.value();
  467. } else if (viewportGeneratorClassAnnotation != null) {
  468. Class<? extends ViewportGenerator> viewportGeneratorClass = viewportGeneratorClassAnnotation
  469. .value();
  470. try {
  471. viewportContent = viewportGeneratorClass.newInstance()
  472. .getViewport(context.getRequest());
  473. } catch (Exception e) {
  474. throw new RuntimeException(
  475. "Error processing viewport generator "
  476. + viewportGeneratorClass.getCanonicalName(),
  477. e);
  478. }
  479. }
  480. if (viewportContent != null) {
  481. head.appendElement("meta").attr("name", "viewport").attr("content",
  482. viewportContent);
  483. }
  484. String title = response.getUIProvider().getPageTitle(
  485. new UICreateEvent(context.getRequest(), context.getUIClass()));
  486. if (title != null) {
  487. head.appendElement("title").appendText(title);
  488. }
  489. head.appendElement("style").attr("type", "text/css")
  490. .appendText("html, body {height:100%;margin:0;}");
  491. // Add favicon links
  492. String themeName = context.getThemeName();
  493. if (themeName != null) {
  494. String themeUri = getThemeUri(context, themeName);
  495. head.appendElement("link").attr("rel", "shortcut icon")
  496. .attr("type", "image/vnd.microsoft.icon")
  497. .attr("href", themeUri + "/favicon.ico");
  498. head.appendElement("link").attr("rel", "icon")
  499. .attr("type", "image/vnd.microsoft.icon")
  500. .attr("href", themeUri + "/favicon.ico");
  501. }
  502. Collection<? extends Dependency> deps = Dependency
  503. .findAndFilterDependencies(Collections.singletonList(uiClass),
  504. context.getSession().getCommunicationManager(),
  505. new FilterContext(context.getSession()));
  506. for (Dependency dependency : deps) {
  507. Type type = dependency.getType();
  508. String url = context.getUriResolver()
  509. .resolveVaadinUri(dependency.getUrl());
  510. if (type == Type.HTMLIMPORT) {
  511. head.appendElement("link").attr("rel", "import").attr("href",
  512. url);
  513. } else if (type == Type.JAVASCRIPT) {
  514. head.appendElement("script").attr("type", "text/javascript")
  515. .attr("src", url);
  516. } else if (type == Type.STYLESHEET) {
  517. head.appendElement("link").attr("rel", "stylesheet")
  518. .attr("type", "text/css").attr("href", url);
  519. } else {
  520. getLogger().severe("Ignoring unknown dependency type "
  521. + dependency.getType());
  522. }
  523. }
  524. Element body = document.body();
  525. body.attr("scroll", "auto");
  526. body.addClass(ApplicationConstants.GENERATED_BODY_CLASSNAME);
  527. }
  528. private static Logger getLogger() {
  529. return Logger.getLogger(BootstrapHandler.class.getName());
  530. }
  531. protected String getMainDivStyle(BootstrapContext context) {
  532. return null;
  533. }
  534. public WidgetsetInfo getWidgetsetForUI(BootstrapContext context) {
  535. VaadinRequest request = context.getRequest();
  536. UICreateEvent event = new UICreateEvent(context.getRequest(),
  537. context.getUIClass());
  538. WidgetsetInfo widgetset = context.getBootstrapResponse().getUIProvider()
  539. .getWidgetsetInfo(event);
  540. if (widgetset == null) {
  541. // TODO do we want to move WidgetsetInfoImpl elsewhere?
  542. widgetset = new WidgetsetInfoImpl(
  543. request.getService().getConfiguredWidgetset(request));
  544. }
  545. return widgetset;
  546. }
  547. /**
  548. * Method to write the div element into which that actual Vaadin application
  549. * is rendered.
  550. * <p>
  551. * Override this method if you want to add some custom html around around
  552. * the div element into which the actual Vaadin application will be
  553. * rendered.
  554. *
  555. * @param context
  556. *
  557. * @throws IOException
  558. */
  559. private void setupMainDiv(BootstrapContext context) throws IOException {
  560. String style = getMainDivStyle(context);
  561. /*- Add classnames;
  562. * .v-app
  563. * .v-app-loading
  564. *- Additionally added from javascript:
  565. * <themeName, remove non-alphanum>
  566. */
  567. List<Node> fragmentNodes = context.getBootstrapResponse()
  568. .getFragmentNodes();
  569. Element mainDiv = new Element(Tag.valueOf("div"), "");
  570. mainDiv.attr("id", context.getAppId());
  571. mainDiv.addClass("v-app");
  572. mainDiv.addClass(context.getThemeName());
  573. mainDiv.addClass(context.getUIClass().getSimpleName()
  574. .toLowerCase(Locale.ENGLISH));
  575. if (style != null && style.length() != 0) {
  576. mainDiv.attr("style", style);
  577. }
  578. mainDiv.appendElement("div").addClass("v-app-loading");
  579. mainDiv.appendElement("noscript").append(
  580. "You have to enable javascript in your browser to use an application built with Vaadin.");
  581. fragmentNodes.add(mainDiv);
  582. VaadinRequest request = context.getRequest();
  583. VaadinService vaadinService = request.getService();
  584. String vaadinLocation = vaadinService.getStaticFileLocation(request)
  585. + "/VAADIN/";
  586. // Parameter appended to JS to bypass caches after version upgrade.
  587. String versionQueryParam = "?v=" + Version.getFullVersion();
  588. if (context.getPushMode().isEnabled()) {
  589. // Load client-side dependencies for push support
  590. String pushJS = vaadinLocation;
  591. if (context.getRequest().getService().getDeploymentConfiguration()
  592. .isProductionMode()) {
  593. pushJS += ApplicationConstants.VAADIN_PUSH_JS;
  594. } else {
  595. pushJS += ApplicationConstants.VAADIN_PUSH_DEBUG_JS;
  596. }
  597. pushJS += versionQueryParam;
  598. fragmentNodes.add(new Element(Tag.valueOf("script"), "")
  599. .attr("type", "text/javascript").attr("src", pushJS));
  600. }
  601. String bootstrapLocation = vaadinLocation
  602. + ApplicationConstants.VAADIN_BOOTSTRAP_JS + versionQueryParam;
  603. fragmentNodes.add(new Element(Tag.valueOf("script"), "")
  604. .attr("type", "text/javascript")
  605. .attr("src", bootstrapLocation));
  606. Element mainScriptTag = new Element(Tag.valueOf("script"), "")
  607. .attr("type", "text/javascript");
  608. StringBuilder builder = new StringBuilder();
  609. builder.append("//<![CDATA[\n");
  610. builder.append("if (!window.vaadin) alert(" + JsonUtil.quote(
  611. "Failed to load the bootstrap javascript: " + bootstrapLocation)
  612. + ");\n");
  613. appendMainScriptTagContents(context, builder);
  614. builder.append("//]]>");
  615. mainScriptTag.appendChild(
  616. new DataNode(builder.toString(), mainScriptTag.baseUri()));
  617. fragmentNodes.add(mainScriptTag);
  618. }
  619. protected void appendMainScriptTagContents(BootstrapContext context,
  620. StringBuilder builder) throws IOException {
  621. JsonObject appConfig = context.getApplicationParameters();
  622. boolean isDebug = !context.getSession().getConfiguration()
  623. .isProductionMode();
  624. if (isDebug) {
  625. /*
  626. * Add tracking needed for getting bootstrap metrics to the client
  627. * side Profiler if another implementation hasn't already been
  628. * added.
  629. */
  630. builder.append(
  631. "if (typeof window.__gwtStatsEvent != 'function') {\n");
  632. builder.append("vaadin.gwtStatsEvents = [];\n");
  633. builder.append(
  634. "window.__gwtStatsEvent = function(event) {vaadin.gwtStatsEvents.push(event); return true;};\n");
  635. builder.append("}\n");
  636. }
  637. builder.append("vaadin.initApplication(\"");
  638. builder.append(context.getAppId());
  639. builder.append("\",");
  640. appendJsonObject(builder, appConfig, isDebug);
  641. builder.append(");\n");
  642. }
  643. private static void appendJsonObject(StringBuilder builder,
  644. JsonObject jsonObject, boolean isDebug) {
  645. if (isDebug) {
  646. builder.append(JsonUtil.stringify(jsonObject, 4));
  647. } else {
  648. builder.append(JsonUtil.stringify(jsonObject));
  649. }
  650. }
  651. protected JsonObject getApplicationParameters(BootstrapContext context) {
  652. VaadinRequest request = context.getRequest();
  653. VaadinSession session = context.getSession();
  654. VaadinService vaadinService = request.getService();
  655. JsonObject appConfig = Json.createObject();
  656. String themeName = context.getThemeName();
  657. if (themeName != null) {
  658. appConfig.put("theme", themeName);
  659. }
  660. // Ignore restartApplication that might be passed to UI init
  661. if (request.getParameter(
  662. VaadinService.URL_PARAMETER_RESTART_APPLICATION) != null) {
  663. appConfig.put("extraParams", "&" + IGNORE_RESTART_PARAM + "=1");
  664. }
  665. JsonObject versionInfo = Json.createObject();
  666. versionInfo.put("vaadinVersion", Version.getFullVersion());
  667. String atmosphereVersion = AtmospherePushConnection
  668. .getAtmosphereVersion();
  669. if (atmosphereVersion != null) {
  670. versionInfo.put("atmosphereVersion", atmosphereVersion);
  671. }
  672. appConfig.put("versionInfo", versionInfo);
  673. WidgetsetInfo widgetsetInfo = context.getWidgetsetInfo();
  674. appConfig.put("widgetset", VaadinServlet
  675. .stripSpecialChars(widgetsetInfo.getWidgetsetName()));
  676. // add widgetset url if not null
  677. if (widgetsetInfo.getWidgetsetUrl() != null) {
  678. appConfig.put("widgetsetUrl", widgetsetInfo.getWidgetsetUrl());
  679. }
  680. appConfig.put("widgetsetReady", !widgetsetInfo.isCdn());
  681. // Use locale from session if set, else from the request
  682. Locale locale = ServletPortletHelper.findLocale(null,
  683. context.getSession(), context.getRequest());
  684. // Get system messages
  685. SystemMessages systemMessages = vaadinService.getSystemMessages(locale,
  686. request);
  687. if (systemMessages != null) {
  688. // Write the CommunicationError -message to client
  689. JsonObject comErrMsg = Json.createObject();
  690. putValueOrNull(comErrMsg, "caption",
  691. systemMessages.getCommunicationErrorCaption());
  692. putValueOrNull(comErrMsg, "message",
  693. systemMessages.getCommunicationErrorMessage());
  694. putValueOrNull(comErrMsg, "url",
  695. systemMessages.getCommunicationErrorURL());
  696. appConfig.put("comErrMsg", comErrMsg);
  697. JsonObject authErrMsg = Json.createObject();
  698. putValueOrNull(authErrMsg, "caption",
  699. systemMessages.getAuthenticationErrorCaption());
  700. putValueOrNull(authErrMsg, "message",
  701. systemMessages.getAuthenticationErrorMessage());
  702. putValueOrNull(authErrMsg, "url",
  703. systemMessages.getAuthenticationErrorURL());
  704. appConfig.put("authErrMsg", authErrMsg);
  705. JsonObject sessExpMsg = Json.createObject();
  706. putValueOrNull(sessExpMsg, "caption",
  707. systemMessages.getSessionExpiredCaption());
  708. putValueOrNull(sessExpMsg, "message",
  709. systemMessages.getSessionExpiredMessage());
  710. putValueOrNull(sessExpMsg, "url",
  711. systemMessages.getSessionExpiredURL());
  712. appConfig.put("sessExpMsg", sessExpMsg);
  713. }
  714. appConfig.put(ApplicationConstants.CONTEXT_ROOT_URL,
  715. getContextRootPath(context));
  716. // getStaticFileLocation documented to never end with a slash
  717. // vaadinDir should always end with a slash
  718. String vaadinDir = vaadinService.getStaticFileLocation(request)
  719. + "/VAADIN/";
  720. appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir);
  721. appConfig.put(ApplicationConstants.FRONTEND_URL,
  722. context.getUriResolver().getFrontendUrl());
  723. if (!session.getConfiguration().isProductionMode()) {
  724. appConfig.put("debug", true);
  725. }
  726. if (vaadinService.isStandalone(request)) {
  727. appConfig.put("standalone", true);
  728. }
  729. appConfig.put("heartbeatInterval", vaadinService
  730. .getDeploymentConfiguration().getHeartbeatInterval());
  731. String serviceUrl = getServiceUrl(context);
  732. if (serviceUrl != null) {
  733. appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
  734. }
  735. boolean sendUrlsAsParameters = vaadinService
  736. .getDeploymentConfiguration().isSendUrlsAsParameters();
  737. if (!sendUrlsAsParameters) {
  738. appConfig.put("sendUrlsAsParameters", false);
  739. }
  740. return appConfig;
  741. }
  742. /**
  743. * @since 8.0.3
  744. */
  745. protected abstract String getContextRootPath(BootstrapContext context);
  746. protected abstract String getServiceUrl(BootstrapContext context);
  747. /**
  748. * Get the URI for the application theme.
  749. *
  750. * A portal-wide default theme is fetched from the portal shared resource
  751. * directory (if any), other themes from the portlet.
  752. *
  753. * @param context
  754. * @param themeName
  755. *
  756. * @return
  757. */
  758. public String getThemeUri(BootstrapContext context, String themeName) {
  759. VaadinRequest request = context.getRequest();
  760. final String staticFilePath = request.getService()
  761. .getStaticFileLocation(request);
  762. return staticFilePath + "/" + VaadinServlet.THEME_DIR_PATH + '/'
  763. + themeName;
  764. }
  765. /**
  766. * Override if required
  767. *
  768. * @param context
  769. * @return
  770. */
  771. public String getThemeName(BootstrapContext context) {
  772. UICreateEvent event = new UICreateEvent(context.getRequest(),
  773. context.getUIClass());
  774. return context.getBootstrapResponse().getUIProvider().getTheme(event);
  775. }
  776. /**
  777. * Do not override.
  778. *
  779. * @param context
  780. * @return
  781. */
  782. public String findAndEscapeThemeName(BootstrapContext context) {
  783. String themeName = getThemeName(context);
  784. if (themeName == null) {
  785. VaadinRequest request = context.getRequest();
  786. themeName = request.getService().getConfiguredTheme(request);
  787. }
  788. // XSS preventation, theme names shouldn't contain special chars anyway.
  789. // The servlet denies them via url parameter.
  790. themeName = VaadinServlet.stripSpecialChars(themeName);
  791. return themeName;
  792. }
  793. protected void writeError(VaadinResponse response, Throwable e)
  794. throws IOException {
  795. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
  796. e.getLocalizedMessage());
  797. }
  798. private void putValueOrNull(JsonObject object, String key, String value) {
  799. assert object != null;
  800. assert key != null;
  801. if (value == null) {
  802. object.put(key, Json.createNull());
  803. } else {
  804. object.put(key, value);
  805. }
  806. }
  807. }