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.

UI.java 62KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866
  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.ui;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.IOException;
  19. import java.lang.reflect.Method;
  20. import java.net.URI;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.LinkedHashMap;
  27. import java.util.LinkedHashSet;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Map.Entry;
  31. import java.util.concurrent.Future;
  32. import java.util.logging.Level;
  33. import java.util.logging.Logger;
  34. import com.vaadin.annotations.PreserveOnRefresh;
  35. import com.vaadin.event.Action;
  36. import com.vaadin.event.Action.Handler;
  37. import com.vaadin.event.ActionManager;
  38. import com.vaadin.event.ConnectorEventListener;
  39. import com.vaadin.event.MouseEvents.ClickEvent;
  40. import com.vaadin.event.MouseEvents.ClickListener;
  41. import com.vaadin.event.UIEvents.PollEvent;
  42. import com.vaadin.event.UIEvents.PollListener;
  43. import com.vaadin.event.UIEvents.PollNotifier;
  44. import com.vaadin.navigator.Navigator;
  45. import com.vaadin.server.ClientConnector;
  46. import com.vaadin.server.ComponentSizeValidator;
  47. import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
  48. import com.vaadin.server.DefaultErrorHandler;
  49. import com.vaadin.server.ErrorHandler;
  50. import com.vaadin.server.ErrorHandlingRunnable;
  51. import com.vaadin.server.LocaleService;
  52. import com.vaadin.server.Page;
  53. import com.vaadin.server.PaintException;
  54. import com.vaadin.server.PaintTarget;
  55. import com.vaadin.server.UIProvider;
  56. import com.vaadin.server.VaadinRequest;
  57. import com.vaadin.server.VaadinService;
  58. import com.vaadin.server.VaadinServlet;
  59. import com.vaadin.server.VaadinSession;
  60. import com.vaadin.server.VaadinSession.State;
  61. import com.vaadin.server.communication.PushConnection;
  62. import com.vaadin.shared.Connector;
  63. import com.vaadin.shared.EventId;
  64. import com.vaadin.shared.MouseEventDetails;
  65. import com.vaadin.shared.Registration;
  66. import com.vaadin.shared.communication.PushMode;
  67. import com.vaadin.shared.ui.WindowOrderRpc;
  68. import com.vaadin.shared.ui.ui.DebugWindowClientRpc;
  69. import com.vaadin.shared.ui.ui.DebugWindowServerRpc;
  70. import com.vaadin.shared.ui.ui.ScrollClientRpc;
  71. import com.vaadin.shared.ui.ui.UIClientRpc;
  72. import com.vaadin.shared.ui.ui.UIConstants;
  73. import com.vaadin.shared.ui.ui.UIServerRpc;
  74. import com.vaadin.shared.ui.ui.UIState;
  75. import com.vaadin.ui.Component.Focusable;
  76. import com.vaadin.ui.Window.WindowOrderChangeListener;
  77. import com.vaadin.ui.declarative.Design;
  78. import com.vaadin.util.ConnectorHelper;
  79. import com.vaadin.util.CurrentInstance;
  80. import com.vaadin.util.ReflectTools;
  81. /**
  82. * The topmost component in any component hierarchy. There is one UI for every
  83. * Vaadin instance in a browser window. A UI may either represent an entire
  84. * browser window (or tab) or some part of a html page where a Vaadin
  85. * application is embedded.
  86. * <p>
  87. * The UI is the server side entry point for various client side features that
  88. * are not represented as components added to a layout, e.g notifications, sub
  89. * windows, and executing javascript in the browser.
  90. * </p>
  91. * <p>
  92. * When a new UI instance is needed, typically because the user opens a URL in a
  93. * browser window which points to e.g. {@link VaadinServlet}, all
  94. * {@link UIProvider}s registered to the current {@link VaadinSession} are
  95. * queried for the UI class that should be used. The selection is by default
  96. * based on the <code>UI</code> init parameter from web.xml.
  97. * </p>
  98. * <p>
  99. * After a UI has been created by the application, it is initialized using
  100. * {@link #init(VaadinRequest)}. This method is intended to be overridden by the
  101. * developer to add components to the user interface and initialize
  102. * non-component functionality. The component hierarchy must be initialized by
  103. * passing a {@link Component} with the main layout or other content of the view
  104. * to {@link #setContent(Component)} or to the constructor of the UI.
  105. * </p>
  106. *
  107. * @see #init(VaadinRequest)
  108. * @see UIProvider
  109. *
  110. * @since 7.0
  111. */
  112. public abstract class UI extends AbstractSingleComponentContainer
  113. implements Action.Container, Action.Notifier, PollNotifier,
  114. LegacyComponent, Focusable {
  115. /**
  116. * The application to which this UI belongs
  117. */
  118. private volatile VaadinSession session;
  119. /**
  120. * List of windows in this UI.
  121. */
  122. private final LinkedHashSet<Window> windows = new LinkedHashSet<>();
  123. /**
  124. * The component that should be scrolled into view after the next repaint.
  125. * Null if nothing should be scrolled into view.
  126. */
  127. private Component scrollIntoView;
  128. /**
  129. * The id of this UI, used to find the server side instance of the UI form
  130. * which a request originates. A negative value indicates that the UI id has
  131. * not yet been assigned by the Application.
  132. *
  133. * @see VaadinSession#getNextUIid()
  134. */
  135. private int uiId = -1;
  136. /**
  137. * Keeps track of the Actions added to this component, and manages the
  138. * painting and handling as well.
  139. */
  140. protected ActionManager actionManager;
  141. private ConnectorTracker connectorTracker = new ConnectorTracker(this);
  142. private Page page = new Page(this, getState(false).pageState);
  143. private LoadingIndicatorConfiguration loadingIndicatorConfiguration = new LoadingIndicatorConfigurationImpl(
  144. this);
  145. /**
  146. * Scroll Y position.
  147. */
  148. private int scrollTop = 0;
  149. /**
  150. * Scroll X position
  151. */
  152. private int scrollLeft = 0;
  153. private UIServerRpc rpc = new UIServerRpc() {
  154. @Override
  155. public void click(MouseEventDetails mouseDetails) {
  156. fireEvent(new ClickEvent(UI.this, mouseDetails));
  157. }
  158. @Override
  159. public void resize(int viewWidth, int viewHeight, int windowWidth,
  160. int windowHeight) {
  161. // TODO We're not doing anything with the view dimensions
  162. getPage().updateBrowserWindowSize(windowWidth, windowHeight, true);
  163. }
  164. @Override
  165. public void scroll(int scrollTop, int scrollLeft) {
  166. UI.this.scrollTop = scrollTop;
  167. UI.this.scrollLeft = scrollLeft;
  168. }
  169. @Override
  170. public void poll() {
  171. fireEvent(new PollEvent(UI.this));
  172. }
  173. @Override
  174. public void acknowledge() {
  175. // Nothing to do, just need the message to be sent and processed
  176. }
  177. };
  178. private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() {
  179. @Override
  180. public void showServerDebugInfo(Connector connector) {
  181. String info = ConnectorHelper
  182. .getDebugInformation((ClientConnector) connector);
  183. getLogger().info(info);
  184. }
  185. @Override
  186. public void analyzeLayouts() {
  187. // TODO Move to client side
  188. List<InvalidLayout> invalidSizes = ComponentSizeValidator
  189. .validateLayouts(UI.this);
  190. StringBuilder json = new StringBuilder();
  191. json.append("{\"invalidLayouts\":");
  192. json.append("[");
  193. if (invalidSizes != null) {
  194. boolean first = true;
  195. for (InvalidLayout invalidSize : invalidSizes) {
  196. if (!first) {
  197. json.append(",");
  198. } else {
  199. first = false;
  200. }
  201. invalidSize.reportErrors(json, System.err);
  202. }
  203. }
  204. json.append("]}");
  205. getRpcProxy(DebugWindowClientRpc.class)
  206. .reportLayoutProblems(json.toString());
  207. }
  208. @Override
  209. public void showServerDesign(Connector connector) {
  210. if (!(connector instanceof Component)) {
  211. getLogger().severe("Tried to output declarative design for "
  212. + connector + ", which is not a component");
  213. return;
  214. }
  215. if (connector instanceof UI) {
  216. // We want to see the content of the UI, so we can add it to
  217. // another UI or component container
  218. connector = ((UI) connector).getContent();
  219. }
  220. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  221. try {
  222. Design.write((Component) connector, baos);
  223. getLogger().info("Design for " + connector
  224. + " requested from debug window:\n"
  225. + baos.toString("UTF-8"));
  226. } catch (IOException e) {
  227. getLogger().log(Level.WARNING,
  228. "Error producing design for " + connector, e);
  229. }
  230. }
  231. };
  232. private WindowOrderRpc windowOrderRpc = new WindowOrderRpc() {
  233. @Override
  234. public void windowOrderChanged(
  235. HashMap<Integer, Connector> windowOrders) {
  236. Map<Integer, Window> orders = new LinkedHashMap<>();
  237. for (Entry<Integer, Connector> entry : windowOrders.entrySet()) {
  238. if (entry.getValue() instanceof Window) {
  239. orders.put(entry.getKey(), (Window) entry.getValue());
  240. }
  241. }
  242. fireWindowOrder(orders);
  243. }
  244. };
  245. /**
  246. * Timestamp keeping track of the last heartbeat of this UI. Updated to the
  247. * current time whenever the application receives a heartbeat or UIDL
  248. * request from the client for this UI.
  249. */
  250. private long lastHeartbeatTimestamp = System.currentTimeMillis();
  251. private boolean closing = false;
  252. private TooltipConfiguration tooltipConfiguration = new TooltipConfigurationImpl(
  253. this);
  254. private PushConfiguration pushConfiguration = new PushConfigurationImpl(
  255. this);
  256. private ReconnectDialogConfiguration reconnectDialogConfiguration = new ReconnectDialogConfigurationImpl(
  257. this);
  258. private NotificationConfiguration notificationConfiguration = new NotificationConfigurationImpl(
  259. this);
  260. /**
  261. * Tracks which message from the client should come next. First message from
  262. * the client has id 0.
  263. */
  264. private int lastProcessedClientToServerId = -1;
  265. /**
  266. * Creates a new empty UI without a caption. The content of the UI must be
  267. * set by calling {@link #setContent(Component)} before using the UI.
  268. */
  269. public UI() {
  270. this(null);
  271. }
  272. /**
  273. * Creates a new UI with the given component (often a layout) as its
  274. * content.
  275. *
  276. * @param content
  277. * the component to use as this UIs content.
  278. *
  279. * @see #setContent(Component)
  280. */
  281. public UI(Component content) {
  282. registerRpc(rpc);
  283. registerRpc(debugRpc);
  284. registerRpc(windowOrderRpc);
  285. setSizeFull();
  286. setContent(content);
  287. }
  288. @Override
  289. protected UIState getState() {
  290. return (UIState) super.getState();
  291. }
  292. @Override
  293. protected UIState getState(boolean markAsDirty) {
  294. return (UIState) super.getState(markAsDirty);
  295. }
  296. @Override
  297. public Class<? extends UIState> getStateType() {
  298. // This is a workaround for a problem with creating the correct state
  299. // object during build
  300. return UIState.class;
  301. }
  302. /**
  303. * Overridden to return a value instead of referring to the parent.
  304. *
  305. * @return this UI
  306. *
  307. * @see com.vaadin.ui.AbstractComponent#getUI()
  308. */
  309. @Override
  310. public UI getUI() {
  311. return this;
  312. }
  313. /**
  314. * Gets the application object to which the component is attached.
  315. *
  316. * <p>
  317. * The method will return {@code null} if the component is not currently
  318. * attached to an application.
  319. * </p>
  320. *
  321. * <p>
  322. * Getting a null value is often a problem in constructors of regular
  323. * components and in the initializers of custom composite components. A
  324. * standard workaround is to use {@link VaadinSession#getCurrent()} to
  325. * retrieve the application instance that the current request relates to.
  326. * Another way is to move the problematic initialization to
  327. * {@link #attach()}, as described in the documentation of the method.
  328. * </p>
  329. *
  330. * @return the parent application of the component or <code>null</code>.
  331. * @see #attach()
  332. */
  333. @Override
  334. public VaadinSession getSession() {
  335. return session;
  336. }
  337. @Override
  338. public void paintContent(PaintTarget target) throws PaintException {
  339. page.paintContent(target);
  340. if (scrollIntoView != null) {
  341. target.addAttribute("scrollTo", scrollIntoView);
  342. scrollIntoView = null;
  343. }
  344. if (pendingFocus != null) {
  345. // ensure focused component is still attached to this main window
  346. if (equals(pendingFocus.getUI()) || (pendingFocus.getUI() != null
  347. && equals(pendingFocus.getUI().getParent()))) {
  348. target.addAttribute("focused", pendingFocus);
  349. }
  350. pendingFocus = null;
  351. }
  352. if (actionManager != null) {
  353. actionManager.paintActions(null, target);
  354. }
  355. if (isResizeLazy()) {
  356. target.addAttribute(UIConstants.RESIZE_LAZY, true);
  357. }
  358. }
  359. /**
  360. * Fire a click event to all click listeners.
  361. *
  362. * @param object
  363. * The raw "value" of the variable change from the client side.
  364. */
  365. private void fireClick(Map<String, Object> parameters) {
  366. MouseEventDetails mouseDetails = MouseEventDetails
  367. .deSerialize((String) parameters.get("mouseDetails"));
  368. fireEvent(new ClickEvent(this, mouseDetails));
  369. }
  370. /**
  371. * Fire a window order event.
  372. *
  373. * @param windows
  374. * The windows with their orders whose order has been updated.
  375. */
  376. private void fireWindowOrder(Map<Integer, Window> windows) {
  377. for (Entry<Integer, Window> entry : windows.entrySet()) {
  378. entry.getValue().fireWindowOrderChange(entry.getKey());
  379. }
  380. fireEvent(new WindowOrderUpdateEvent(this, windows.values()));
  381. }
  382. @Override
  383. @SuppressWarnings("unchecked")
  384. public void changeVariables(Object source, Map<String, Object> variables) {
  385. if (variables.containsKey(EventId.CLICK_EVENT_IDENTIFIER)) {
  386. fireClick((Map<String, Object>) variables
  387. .get(EventId.CLICK_EVENT_IDENTIFIER));
  388. }
  389. // Actions
  390. if (actionManager != null) {
  391. actionManager.handleActions(variables, this);
  392. }
  393. if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) {
  394. String location = (String) variables
  395. .get(UIConstants.LOCATION_VARIABLE);
  396. getPage().updateLocation(location, true);
  397. }
  398. }
  399. /*
  400. * (non-Javadoc)
  401. *
  402. * @see com.vaadin.ui.HasComponents#iterator()
  403. */
  404. @Override
  405. public Iterator<Component> iterator() {
  406. // TODO could directly create some kind of combined iterator instead of
  407. // creating a new ArrayList
  408. ArrayList<Component> components = new ArrayList<>();
  409. if (getContent() != null) {
  410. components.add(getContent());
  411. }
  412. components.addAll(windows);
  413. return components.iterator();
  414. }
  415. /*
  416. * (non-Javadoc)
  417. *
  418. * @see com.vaadin.ui.ComponentContainer#getComponentCount()
  419. */
  420. @Override
  421. public int getComponentCount() {
  422. return windows.size() + (getContent() == null ? 0 : 1);
  423. }
  424. /**
  425. * Sets the session to which this UI is assigned.
  426. * <p>
  427. * This method is for internal use by the framework. To explicitly close a
  428. * UI, see {@link #close()}.
  429. * </p>
  430. *
  431. * @param session
  432. * the session to set
  433. *
  434. * @throws IllegalStateException
  435. * if the session has already been set
  436. *
  437. * @see #getSession()
  438. */
  439. public void setSession(VaadinSession session) {
  440. if (session == null && this.session == null) {
  441. throw new IllegalStateException(
  442. "Session should never be set to null when UI.session is already null");
  443. } else if (session != null && this.session != null) {
  444. throw new IllegalStateException(
  445. "Session has already been set. Old session: "
  446. + getSessionDetails(this.session)
  447. + ". New session: " + getSessionDetails(session)
  448. + ".");
  449. } else {
  450. if (session == null) {
  451. try {
  452. detach();
  453. } catch (Exception e) {
  454. getLogger().log(Level.WARNING,
  455. "Error while detaching UI from session", e);
  456. }
  457. // Disable push when the UI is detached. Otherwise the
  458. // push connection and possibly VaadinSession will live
  459. // on.
  460. getPushConfiguration().setPushMode(PushMode.DISABLED);
  461. new Thread(new Runnable() {
  462. @Override
  463. public void run() {
  464. // This intentionally does disconnect without locking
  465. // the VaadinSession to avoid deadlocks where the server
  466. // uses a lock for the websocket connection
  467. // See https://dev.vaadin.com/ticket/18436
  468. // The underlying problem is
  469. // https://dev.vaadin.com/ticket/16919
  470. setPushConnection(null);
  471. }
  472. }).start();
  473. }
  474. this.session = session;
  475. }
  476. if (session != null) {
  477. attach();
  478. }
  479. }
  480. private static String getSessionDetails(VaadinSession session) {
  481. if (session == null) {
  482. return null;
  483. } else {
  484. return session.toString() + " for "
  485. + session.getService().getServiceName();
  486. }
  487. }
  488. /**
  489. * Gets the id of the UI, used to identify this UI within its application
  490. * when processing requests. The UI id should be present in every request to
  491. * the server that originates from this UI.
  492. * {@link VaadinService#findUI(VaadinRequest)} uses this id to find the
  493. * route to which the request belongs.
  494. * <p>
  495. * This method is not intended to be overridden. If it is overridden, care
  496. * should be taken since this method might be called in situations where
  497. * {@link UI#getCurrent()} does not return this UI.
  498. *
  499. * @return the id of this UI
  500. */
  501. public int getUIId() {
  502. return uiId;
  503. }
  504. /**
  505. * Adds a window as a subwindow inside this UI. To open a new browser window
  506. * or tab, you should instead use a {@link UIProvider}.
  507. *
  508. * @param window
  509. * @throws IllegalArgumentException
  510. * if the window is already added to an application
  511. * @throws NullPointerException
  512. * if the given <code>Window</code> is <code>null</code>.
  513. */
  514. public void addWindow(Window window)
  515. throws IllegalArgumentException, NullPointerException {
  516. if (window == null) {
  517. throw new NullPointerException("Argument must not be null");
  518. }
  519. if (window.isAttached()) {
  520. throw new IllegalArgumentException(
  521. "Window is already attached to an application.");
  522. }
  523. attachWindow(window);
  524. }
  525. /**
  526. * Helper method to attach a window.
  527. *
  528. * @param w
  529. * the window to add
  530. */
  531. private void attachWindow(Window w) {
  532. windows.add(w);
  533. w.setParent(this);
  534. fireComponentAttachEvent(w);
  535. markAsDirty();
  536. }
  537. /**
  538. * Remove the given subwindow from this UI.
  539. *
  540. * Since Vaadin 6.5, {@link Window.CloseListener}s are called also when
  541. * explicitly removing a window by calling this method.
  542. *
  543. * Since Vaadin 6.5, returns a boolean indicating if the window was removed
  544. * or not.
  545. *
  546. * @param window
  547. * Window to be removed.
  548. * @return true if the subwindow was removed, false otherwise
  549. */
  550. public boolean removeWindow(Window window) {
  551. if (!windows.remove(window)) {
  552. // Window window is not a subwindow of this UI.
  553. return false;
  554. }
  555. window.setParent(null);
  556. markAsDirty();
  557. window.fireClose();
  558. fireComponentDetachEvent(window);
  559. fireWindowOrder(Collections.singletonMap(-1, window));
  560. return true;
  561. }
  562. /**
  563. * Gets all the windows added to this UI.
  564. *
  565. * @return an unmodifiable collection of windows
  566. */
  567. public Collection<Window> getWindows() {
  568. return Collections.unmodifiableCollection(windows);
  569. }
  570. @Override
  571. public void focus() {
  572. super.focus();
  573. }
  574. /**
  575. * Component that should be focused after the next repaint. Null if no focus
  576. * change should take place.
  577. */
  578. private Focusable pendingFocus;
  579. private boolean resizeLazy = false;
  580. private Navigator navigator;
  581. private PushConnection pushConnection = null;
  582. private LocaleService localeService = new LocaleService(this,
  583. getState(false).localeServiceState);
  584. private String embedId;
  585. /**
  586. * This method is used by Component.Focusable objects to request focus to
  587. * themselves. Focus renders must be handled at window level (instead of
  588. * Component.Focusable) due we want the last focused component to be focused
  589. * in client too. Not the one that is rendered last (the case we'd get if
  590. * implemented in Focusable only).
  591. *
  592. * To focus component from Vaadin application, use Focusable.focus(). See
  593. * {@link Focusable}.
  594. *
  595. * @param focusable
  596. * to be focused on next paint
  597. */
  598. public void setFocusedComponent(Focusable focusable) {
  599. pendingFocus = focusable;
  600. markAsDirty();
  601. }
  602. /**
  603. * Scrolls any component between the component and UI to a suitable position
  604. * so the component is visible to the user. The given component must belong
  605. * to this UI.
  606. *
  607. * @param component
  608. * the component to be scrolled into view
  609. * @throws IllegalArgumentException
  610. * if {@code component} does not belong to this UI
  611. */
  612. public void scrollIntoView(Component component)
  613. throws IllegalArgumentException {
  614. if (component.getUI() != this) {
  615. throw new IllegalArgumentException(
  616. "The component where to scroll must belong to this UI.");
  617. }
  618. scrollIntoView = component;
  619. markAsDirty();
  620. }
  621. /**
  622. * Internal initialization method, should not be overridden. This method is
  623. * not declared as final because that would break compatibility with e.g.
  624. * CDI.
  625. *
  626. * @param request
  627. * the initialization request
  628. * @param uiId
  629. * the id of the new ui
  630. * @param embedId
  631. * the embed id of this UI, or <code>null</code> if no id is
  632. * known
  633. *
  634. * @see #getUIId()
  635. * @see #getEmbedId()
  636. */
  637. public void doInit(VaadinRequest request, int uiId, String embedId) {
  638. if (this.uiId != -1) {
  639. String message = "This UI instance is already initialized (as UI id "
  640. + this.uiId
  641. + ") and can therefore not be initialized again (as UI id "
  642. + uiId + "). ";
  643. if (getSession() != null
  644. && !getSession().equals(VaadinSession.getCurrent())) {
  645. message += "Furthermore, it is already attached to another VaadinSession. ";
  646. }
  647. message += "Please make sure you are not accidentally reusing an old UI instance.";
  648. throw new IllegalStateException(message);
  649. }
  650. this.uiId = uiId;
  651. this.embedId = embedId;
  652. // Actual theme - used for finding CustomLayout templates
  653. setTheme(request.getParameter("theme"));
  654. getPage().init(request);
  655. // Call the init overridden by the application developer
  656. init(request);
  657. Navigator navigator = getNavigator();
  658. if (navigator != null) {
  659. // Kickstart navigation if a navigator was attached in init()
  660. navigator.navigateTo(navigator.getState());
  661. }
  662. }
  663. /**
  664. * Initializes this UI. This method is intended to be overridden by
  665. * subclasses to build the view and configure non-component functionality.
  666. * Performing the initialization in a constructor is not suggested as the
  667. * state of the UI is not properly set up when the constructor is invoked.
  668. * <p>
  669. * The {@link VaadinRequest} can be used to get information about the
  670. * request that caused this UI to be created.
  671. * </p>
  672. *
  673. * @param request
  674. * the Vaadin request that caused this UI to be created
  675. */
  676. protected abstract void init(VaadinRequest request);
  677. /**
  678. * Internal reinitialization method, should not be overridden.
  679. *
  680. * @since 7.2
  681. * @param request
  682. * the request that caused this UI to be reloaded
  683. */
  684. public void doRefresh(VaadinRequest request) {
  685. // This is a horrible hack. We want to have the most recent location and
  686. // browser window size available in refresh(), but we want to call
  687. // listeners, if any, only after refresh(). So we momentarily assign the
  688. // old values back before setting the new values again to ensure the
  689. // events are properly fired.
  690. Page page = getPage();
  691. URI oldLocation = page.getLocation();
  692. int oldWidth = page.getBrowserWindowWidth();
  693. int oldHeight = page.getBrowserWindowHeight();
  694. page.init(request);
  695. // Reset heartbeat timeout to avoid surprise if it's almost expired
  696. setLastHeartbeatTimestamp(System.currentTimeMillis());
  697. refresh(request);
  698. URI newLocation = page.getLocation();
  699. int newWidth = page.getBrowserWindowWidth();
  700. int newHeight = page.getBrowserWindowHeight();
  701. page.updateLocation(oldLocation.toString(), false);
  702. page.updateBrowserWindowSize(oldWidth, oldHeight, false);
  703. page.updateLocation(newLocation.toString(), true);
  704. page.updateBrowserWindowSize(newWidth, newHeight, true);
  705. }
  706. /**
  707. * Reinitializes this UI after a browser refresh if the UI is set to be
  708. * preserved on refresh, typically using the {@link PreserveOnRefresh}
  709. * annotation. This method is intended to be overridden by subclasses if
  710. * needed; the default implementation is empty.
  711. * <p>
  712. * The {@link VaadinRequest} can be used to get information about the
  713. * request that caused this UI to be reloaded.
  714. *
  715. * @since 7.2
  716. * @param request
  717. * the request that caused this UI to be reloaded
  718. */
  719. protected void refresh(VaadinRequest request) {
  720. }
  721. /**
  722. * Sets the thread local for the current UI. This method is used by the
  723. * framework to set the current application whenever a new request is
  724. * processed and it is cleared when the request has been processed.
  725. * <p>
  726. * The application developer can also use this method to define the current
  727. * UI outside the normal request handling, e.g. when initiating custom
  728. * background threads.
  729. * <p>
  730. * The UI is stored using a weak reference to avoid leaking memory in case
  731. * it is not explicitly cleared.
  732. *
  733. * @param ui
  734. * the UI to register as the current UI
  735. *
  736. * @see #getCurrent()
  737. * @see ThreadLocal
  738. */
  739. public static void setCurrent(UI ui) {
  740. CurrentInstance.set(UI.class, ui);
  741. }
  742. /**
  743. * Gets the currently used UI. The current UI is automatically defined when
  744. * processing requests to the server. In other cases, (e.g. from background
  745. * threads), the current UI is not automatically defined.
  746. * <p>
  747. * The UI is stored using a weak reference to avoid leaking memory in case
  748. * it is not explicitly cleared.
  749. *
  750. * @return the current UI instance if available, otherwise <code>null</code>
  751. *
  752. * @see #setCurrent(UI)
  753. */
  754. public static UI getCurrent() {
  755. return CurrentInstance.get(UI.class);
  756. }
  757. /**
  758. * Set top offset to which the UI should scroll to.
  759. *
  760. * @param scrollTop
  761. */
  762. public void setScrollTop(int scrollTop) {
  763. if (scrollTop < 0) {
  764. throw new IllegalArgumentException(
  765. "Scroll offset must be at least 0");
  766. }
  767. if (this.scrollTop != scrollTop) {
  768. this.scrollTop = scrollTop;
  769. getRpcProxy(ScrollClientRpc.class).setScrollTop(scrollTop);
  770. }
  771. }
  772. public int getScrollTop() {
  773. return scrollTop;
  774. }
  775. /**
  776. * Set left offset to which the UI should scroll to.
  777. *
  778. * @param scrollLeft
  779. */
  780. public void setScrollLeft(int scrollLeft) {
  781. if (scrollLeft < 0) {
  782. throw new IllegalArgumentException(
  783. "Scroll offset must be at least 0");
  784. }
  785. if (this.scrollLeft != scrollLeft) {
  786. this.scrollLeft = scrollLeft;
  787. getRpcProxy(ScrollClientRpc.class).setScrollLeft(scrollLeft);
  788. }
  789. }
  790. public int getScrollLeft() {
  791. return scrollLeft;
  792. }
  793. @Override
  794. protected ActionManager getActionManager() {
  795. if (actionManager == null) {
  796. actionManager = new ActionManager(this);
  797. }
  798. return actionManager;
  799. }
  800. @Override
  801. public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
  802. T action) {
  803. getActionManager().addAction(action);
  804. }
  805. @Override
  806. public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
  807. T action) {
  808. if (actionManager != null) {
  809. actionManager.removeAction(action);
  810. }
  811. }
  812. @Override
  813. public void addActionHandler(Handler actionHandler) {
  814. getActionManager().addActionHandler(actionHandler);
  815. }
  816. @Override
  817. public void removeActionHandler(Handler actionHandler) {
  818. if (actionManager != null) {
  819. actionManager.removeActionHandler(actionHandler);
  820. }
  821. }
  822. /**
  823. * Should resize operations be lazy, i.e. should there be a delay before
  824. * layout sizes are recalculated and resize events are sent to the server.
  825. * Speeds up resize operations in slow UIs with the penalty of slightly
  826. * decreased usability.
  827. * <p>
  828. * Default value: <code>false</code>
  829. * </p>
  830. * <p>
  831. * When there are active window resize listeners, lazy resize mode should be
  832. * used to avoid a large number of events during resize.
  833. * </p>
  834. *
  835. * @param resizeLazy
  836. * true to use a delay before recalculating sizes, false to
  837. * calculate immediately.
  838. */
  839. public void setResizeLazy(boolean resizeLazy) {
  840. this.resizeLazy = resizeLazy;
  841. markAsDirty();
  842. }
  843. /**
  844. * Checks whether lazy resize is enabled.
  845. *
  846. * @return <code>true</code> if lazy resize is enabled, <code>false</code>
  847. * if lazy resize is not enabled
  848. */
  849. public boolean isResizeLazy() {
  850. return resizeLazy;
  851. }
  852. /**
  853. * Add a click listener to the UI. The listener is called whenever the user
  854. * clicks inside the UI. Also when the click targets a component inside the
  855. * UI, provided the targeted component does not prevent the click event from
  856. * propagating.
  857. *
  858. * @see Registration
  859. *
  860. * @param listener
  861. * The listener to add, not null
  862. * @return a registration object for removing the listener
  863. */
  864. public Registration addClickListener(ClickListener listener) {
  865. return addListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
  866. listener, ClickListener.clickMethod);
  867. }
  868. /**
  869. * Remove a click listener from the UI. The listener should earlier have
  870. * been added using {@link #addListener(ClickListener)}.
  871. *
  872. * @param listener
  873. * The listener to remove
  874. *
  875. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  876. * registration object returned from
  877. * {@link #removeClickListener(ClickListener)}.
  878. */
  879. @Deprecated
  880. public void removeClickListener(ClickListener listener) {
  881. removeListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
  882. listener);
  883. }
  884. @Override
  885. public boolean isConnectorEnabled() {
  886. // TODO How can a UI be invisible? What does it mean?
  887. return isVisible() && isEnabled();
  888. }
  889. public ConnectorTracker getConnectorTracker() {
  890. return connectorTracker;
  891. }
  892. public Page getPage() {
  893. return page;
  894. }
  895. /**
  896. * Returns the navigator attached to this UI or null if there is no
  897. * navigator.
  898. *
  899. * @return
  900. */
  901. public Navigator getNavigator() {
  902. return navigator;
  903. }
  904. /**
  905. * For internal use only.
  906. *
  907. * @param navigator
  908. */
  909. public void setNavigator(Navigator navigator) {
  910. this.navigator = navigator;
  911. }
  912. /**
  913. * Setting the caption of a UI is not supported. To set the title of the
  914. * HTML page, use Page.setTitle
  915. *
  916. * @deprecated As of 7.0, use {@link Page#setTitle(String)}
  917. */
  918. @Override
  919. @Deprecated
  920. public void setCaption(String caption) {
  921. throw new UnsupportedOperationException(
  922. "You can not set the title of a UI. To set the title of the HTML page, use Page.setTitle");
  923. }
  924. /**
  925. * Shows a notification message on the middle of the UI. The message
  926. * automatically disappears ("humanized message").
  927. *
  928. * Care should be taken to to avoid XSS vulnerabilities as the caption is
  929. * rendered as html.
  930. *
  931. * @see #showNotification(Notification)
  932. * @see Notification
  933. *
  934. * @param caption
  935. * The message
  936. *
  937. * @deprecated As of 7.0, use Notification.show instead but be aware that
  938. * Notification.show does not allow HTML.
  939. */
  940. @Deprecated
  941. public void showNotification(String caption) {
  942. Notification notification = new Notification(caption);
  943. notification.setHtmlContentAllowed(true);// Backwards compatibility
  944. getPage().showNotification(notification);
  945. }
  946. /**
  947. * Shows a notification message the UI. The position and behavior of the
  948. * message depends on the type, which is one of the basic types defined in
  949. * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE.
  950. *
  951. * Care should be taken to to avoid XSS vulnerabilities as the caption is
  952. * rendered as html.
  953. *
  954. * @see #showNotification(Notification)
  955. * @see Notification
  956. *
  957. * @param caption
  958. * The message
  959. * @param type
  960. * The message type
  961. *
  962. * @deprecated As of 7.0, use Notification.show instead but be aware that
  963. * Notification.show does not allow HTML.
  964. */
  965. @Deprecated
  966. public void showNotification(String caption, Notification.Type type) {
  967. Notification notification = new Notification(caption, type);
  968. notification.setHtmlContentAllowed(true);// Backwards compatibility
  969. getPage().showNotification(notification);
  970. }
  971. /**
  972. * Shows a notification consisting of a bigger caption and a smaller
  973. * description on the middle of the UI. The message automatically disappears
  974. * ("humanized message").
  975. *
  976. * Care should be taken to to avoid XSS vulnerabilities as the caption and
  977. * description are rendered as html.
  978. *
  979. * @see #showNotification(Notification)
  980. * @see Notification
  981. *
  982. * @param caption
  983. * The caption of the message
  984. * @param description
  985. * The message description
  986. *
  987. * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
  988. * be aware that HTML by default not allowed.
  989. */
  990. @Deprecated
  991. public void showNotification(String caption, String description) {
  992. Notification notification = new Notification(caption, description);
  993. notification.setHtmlContentAllowed(true);// Backwards compatibility
  994. getPage().showNotification(notification);
  995. }
  996. /**
  997. * Shows a notification consisting of a bigger caption and a smaller
  998. * description. The position and behavior of the message depends on the
  999. * type, which is one of the basic types defined in {@link Notification} ,
  1000. * for instance Notification.TYPE_WARNING_MESSAGE.
  1001. *
  1002. * Care should be taken to to avoid XSS vulnerabilities as the caption and
  1003. * description are rendered as html.
  1004. *
  1005. * @see #showNotification(Notification)
  1006. * @see Notification
  1007. *
  1008. * @param caption
  1009. * The caption of the message
  1010. * @param description
  1011. * The message description
  1012. * @param type
  1013. * The message type
  1014. *
  1015. * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
  1016. * be aware that HTML by default not allowed.
  1017. */
  1018. @Deprecated
  1019. public void showNotification(String caption, String description,
  1020. Notification.Type type) {
  1021. Notification notification = new Notification(caption, description,
  1022. type);
  1023. notification.setHtmlContentAllowed(true);// Backwards compatibility
  1024. getPage().showNotification(notification);
  1025. }
  1026. /**
  1027. * Shows a notification consisting of a bigger caption and a smaller
  1028. * description. The position and behavior of the message depends on the
  1029. * type, which is one of the basic types defined in {@link Notification} ,
  1030. * for instance Notification.TYPE_WARNING_MESSAGE.
  1031. *
  1032. * Care should be taken to avoid XSS vulnerabilities if html content is
  1033. * allowed.
  1034. *
  1035. * @see #showNotification(Notification)
  1036. * @see Notification
  1037. *
  1038. * @param caption
  1039. * The message caption
  1040. * @param description
  1041. * The message description
  1042. * @param type
  1043. * The type of message
  1044. * @param htmlContentAllowed
  1045. * Whether html in the caption and description should be
  1046. * displayed as html or as plain text
  1047. *
  1048. * @deprecated As of 7.0, use new Notification(...).show(Page).
  1049. */
  1050. @Deprecated
  1051. public void showNotification(String caption, String description,
  1052. Notification.Type type, boolean htmlContentAllowed) {
  1053. getPage().showNotification(new Notification(caption, description, type,
  1054. htmlContentAllowed));
  1055. }
  1056. /**
  1057. * Shows a notification message.
  1058. *
  1059. * @see Notification
  1060. * @see #showNotification(String)
  1061. * @see #showNotification(String, int)
  1062. * @see #showNotification(String, String)
  1063. * @see #showNotification(String, String, int)
  1064. *
  1065. * @param notification
  1066. * The notification message to show
  1067. *
  1068. * @deprecated As of 7.0, use Notification.show instead
  1069. */
  1070. @Deprecated
  1071. public void showNotification(Notification notification) {
  1072. getPage().showNotification(notification);
  1073. }
  1074. /**
  1075. * Returns the timestamp of the last received heartbeat for this UI.
  1076. * <p>
  1077. * This method is not intended to be overridden. If it is overridden, care
  1078. * should be taken since this method might be called in situations where
  1079. * {@link UI#getCurrent()} does not return this UI.
  1080. *
  1081. * @see VaadinService#closeInactiveUIs(VaadinSession)
  1082. *
  1083. * @return The time the last heartbeat request occurred, in milliseconds
  1084. * since the epoch.
  1085. */
  1086. public long getLastHeartbeatTimestamp() {
  1087. return lastHeartbeatTimestamp;
  1088. }
  1089. /**
  1090. * Sets the last heartbeat request timestamp for this UI. Called by the
  1091. * framework whenever the application receives a valid heartbeat request for
  1092. * this UI.
  1093. * <p>
  1094. * This method is not intended to be overridden. If it is overridden, care
  1095. * should be taken since this method might be called in situations where
  1096. * {@link UI#getCurrent()} does not return this UI.
  1097. *
  1098. * @param lastHeartbeat
  1099. * The time the last heartbeat request occurred, in milliseconds
  1100. * since the epoch.
  1101. */
  1102. public void setLastHeartbeatTimestamp(long lastHeartbeat) {
  1103. lastHeartbeatTimestamp = lastHeartbeat;
  1104. }
  1105. /**
  1106. * Gets the theme currently in use by this UI
  1107. *
  1108. * @return the theme name
  1109. */
  1110. public String getTheme() {
  1111. return getState(false).theme;
  1112. }
  1113. /**
  1114. * Sets the theme currently in use by this UI
  1115. * <p>
  1116. * Calling this method will remove the old theme (CSS file) from the
  1117. * application and add the new theme.
  1118. * <p>
  1119. * Note that this method is NOT SAFE to call in a portal environment or
  1120. * other environment where there are multiple UIs on the same page. The old
  1121. * CSS file will be removed even if there are other UIs on the page which
  1122. * are still using it.
  1123. *
  1124. * @since 7.3
  1125. * @param theme
  1126. * The new theme name
  1127. */
  1128. public void setTheme(String theme) {
  1129. if (theme == null) {
  1130. getState().theme = null;
  1131. } else {
  1132. getState().theme = VaadinServlet.stripSpecialChars(theme);
  1133. }
  1134. }
  1135. /**
  1136. * Marks this UI to be {@link #detach() detached} from the session at the
  1137. * end of the current request, or the next request if there is no current
  1138. * request (if called from a background thread, for instance.)
  1139. * <p>
  1140. * The UI is detached after the response is sent, so in the current request
  1141. * it can still update the client side normally. However, after the response
  1142. * any new requests from the client side to this UI will cause an error, so
  1143. * usually the client should be asked, for instance, to reload the page
  1144. * (serving a fresh UI instance), to close the page, or to navigate
  1145. * somewhere else.
  1146. * <p>
  1147. * Note that this method is strictly for users to explicitly signal the
  1148. * framework that the UI should be detached. Overriding it is not a reliable
  1149. * way to catch UIs that are to be detached. Instead, {@code UI.detach()}
  1150. * should be overridden or a {@link DetachListener} used.
  1151. */
  1152. public void close() {
  1153. closing = true;
  1154. boolean sessionExpired = (session == null
  1155. || session.getState() != State.OPEN);
  1156. getRpcProxy(UIClientRpc.class).uiClosed(sessionExpired);
  1157. if (getPushConnection() != null) {
  1158. // Push the Rpc to the client. The connection will be closed when
  1159. // the UI is detached and cleaned up.
  1160. // Can't use UI.push() directly since it checks for a valid session
  1161. if (session != null) {
  1162. session.getService().runPendingAccessTasks(session);
  1163. }
  1164. getPushConnection().push();
  1165. }
  1166. }
  1167. /**
  1168. * Returns whether this UI is marked as closed and is to be detached.
  1169. * <p>
  1170. * This method is not intended to be overridden. If it is overridden, care
  1171. * should be taken since this method might be called in situations where
  1172. * {@link UI#getCurrent()} does not return this UI.
  1173. *
  1174. * @see #close()
  1175. *
  1176. * @return whether this UI is closing.
  1177. */
  1178. public boolean isClosing() {
  1179. return closing;
  1180. }
  1181. /**
  1182. * Called after the UI is added to the session. A UI instance is attached
  1183. * exactly once, before its {@link #init(VaadinRequest) init} method is
  1184. * called.
  1185. *
  1186. * @see Component#attach
  1187. */
  1188. @Override
  1189. public void attach() {
  1190. super.attach();
  1191. getLocaleService().addLocale(getLocale());
  1192. }
  1193. /**
  1194. * Called before the UI is removed from the session. A UI instance is
  1195. * detached exactly once, either:
  1196. * <ul>
  1197. * <li>after it is explicitly {@link #close() closed}.
  1198. * <li>when its session is closed or expires
  1199. * <li>after three missed heartbeat requests.
  1200. * </ul>
  1201. * <p>
  1202. * Note that when a UI is detached, any changes made in the {@code detach}
  1203. * methods of any children or {@link DetachListener}s that would be
  1204. * communicated to the client are silently ignored.
  1205. */
  1206. @Override
  1207. public void detach() {
  1208. super.detach();
  1209. }
  1210. /*
  1211. * (non-Javadoc)
  1212. *
  1213. * @see
  1214. * com.vaadin.ui.AbstractSingleComponentContainer#setContent(com.vaadin.
  1215. * ui.Component)
  1216. */
  1217. @Override
  1218. public void setContent(Component content) {
  1219. if (content instanceof Window) {
  1220. throw new IllegalArgumentException(
  1221. "A Window cannot be added using setContent. Use addWindow(Window window) instead");
  1222. }
  1223. super.setContent(content);
  1224. }
  1225. @Override
  1226. public void setTabIndex(int tabIndex) {
  1227. getState().tabIndex = tabIndex;
  1228. }
  1229. @Override
  1230. public int getTabIndex() {
  1231. return getState(false).tabIndex;
  1232. }
  1233. /**
  1234. * Locks the session of this UI and runs the provided Runnable right away.
  1235. * <p>
  1236. * It is generally recommended to use {@link #access(Runnable)} instead of
  1237. * this method for accessing a session from a different thread as
  1238. * {@link #access(Runnable)} can be used while holding the lock of another
  1239. * session. To avoid causing deadlocks, this methods throws an exception if
  1240. * it is detected than another session is also locked by the current thread.
  1241. * </p>
  1242. * <p>
  1243. * This method behaves differently than {@link #access(Runnable)} in some
  1244. * situations:
  1245. * <ul>
  1246. * <li>If the current thread is currently holding the lock of the session,
  1247. * {@link #accessSynchronously(Runnable)} runs the task right away whereas
  1248. * {@link #access(Runnable)} defers the task to a later point in time.</li>
  1249. * <li>If some other thread is currently holding the lock for the session,
  1250. * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock
  1251. * to be available whereas {@link #access(Runnable)} defers the task to a
  1252. * later point in time.</li>
  1253. * </ul>
  1254. * </p>
  1255. *
  1256. * @since 7.1
  1257. *
  1258. * @param runnable
  1259. * the runnable which accesses the UI
  1260. * @throws UIDetachedException
  1261. * if the UI is not attached to a session (and locking can
  1262. * therefore not be done)
  1263. * @throws IllegalStateException
  1264. * if the current thread holds the lock for another session
  1265. *
  1266. * @see #access(Runnable)
  1267. * @see VaadinSession#accessSynchronously(Runnable)
  1268. */
  1269. public void accessSynchronously(Runnable runnable)
  1270. throws UIDetachedException {
  1271. Map<Class<?>, CurrentInstance> old = null;
  1272. VaadinSession session = getSession();
  1273. if (session == null) {
  1274. throw new UIDetachedException();
  1275. }
  1276. VaadinService.verifyNoOtherSessionLocked(session);
  1277. session.lock();
  1278. try {
  1279. if (getSession() == null) {
  1280. // UI was detached after fetching the session but before we
  1281. // acquired the lock.
  1282. throw new UIDetachedException();
  1283. }
  1284. old = CurrentInstance.setCurrent(this);
  1285. runnable.run();
  1286. } finally {
  1287. session.unlock();
  1288. if (old != null) {
  1289. CurrentInstance.restoreInstances(old);
  1290. }
  1291. }
  1292. }
  1293. /**
  1294. * Provides exclusive access to this UI from outside a request handling
  1295. * thread.
  1296. * <p>
  1297. * The given runnable is executed while holding the session lock to ensure
  1298. * exclusive access to this UI. If the session is not locked, the lock will
  1299. * be acquired and the runnable is run right away. If the session is
  1300. * currently locked, the runnable will be run before that lock is released.
  1301. * </p>
  1302. * <p>
  1303. * RPC handlers for components inside this UI do not need to use this method
  1304. * as the session is automatically locked by the framework during RPC
  1305. * handling.
  1306. * </p>
  1307. * <p>
  1308. * Please note that the runnable might be invoked on a different thread or
  1309. * later on the current thread, which means that custom thread locals might
  1310. * not have the expected values when the command is executed.
  1311. * {@link UI#getCurrent()}, {@link VaadinSession#getCurrent()} and
  1312. * {@link VaadinService#getCurrent()} are set according to this UI before
  1313. * executing the command. Other standard CurrentInstance values such as
  1314. * {@link VaadinService#getCurrentRequest()} and
  1315. * {@link VaadinService#getCurrentResponse()} will not be defined.
  1316. * </p>
  1317. * <p>
  1318. * The returned future can be used to check for task completion and to
  1319. * cancel the task.
  1320. * </p>
  1321. *
  1322. * @see #getCurrent()
  1323. * @see #accessSynchronously(Runnable)
  1324. * @see VaadinSession#access(Runnable)
  1325. * @see VaadinSession#lock()
  1326. *
  1327. * @since 7.1
  1328. *
  1329. * @param runnable
  1330. * the runnable which accesses the UI
  1331. * @throws UIDetachedException
  1332. * if the UI is not attached to a session (and locking can
  1333. * therefore not be done)
  1334. * @return a future that can be used to check for task completion and to
  1335. * cancel the task
  1336. */
  1337. public Future<Void> access(final Runnable runnable) {
  1338. VaadinSession session = getSession();
  1339. if (session == null) {
  1340. throw new UIDetachedException();
  1341. }
  1342. return session.access(new ErrorHandlingRunnable() {
  1343. @Override
  1344. public void run() {
  1345. accessSynchronously(runnable);
  1346. }
  1347. @Override
  1348. public void handleError(Exception exception) {
  1349. try {
  1350. if (runnable instanceof ErrorHandlingRunnable) {
  1351. ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable;
  1352. errorHandlingRunnable.handleError(exception);
  1353. } else {
  1354. ConnectorErrorEvent errorEvent = new ConnectorErrorEvent(
  1355. UI.this, exception);
  1356. ErrorHandler errorHandler = com.vaadin.server.ErrorEvent
  1357. .findErrorHandler(UI.this);
  1358. if (errorHandler == null) {
  1359. errorHandler = new DefaultErrorHandler();
  1360. }
  1361. errorHandler.error(errorEvent);
  1362. }
  1363. } catch (Exception e) {
  1364. getLogger().log(Level.SEVERE, e.getMessage(), e);
  1365. }
  1366. }
  1367. });
  1368. }
  1369. /**
  1370. * Retrieves the object used for configuring tooltips.
  1371. *
  1372. * @return The instance used for tooltip configuration
  1373. */
  1374. public TooltipConfiguration getTooltipConfiguration() {
  1375. return tooltipConfiguration;
  1376. }
  1377. /**
  1378. * Retrieves the object used for configuring notifications.
  1379. *
  1380. * @return The instance used for notification configuration
  1381. */
  1382. public NotificationConfiguration getNotificationConfiguration() {
  1383. return notificationConfiguration;
  1384. }
  1385. /**
  1386. * Retrieves the object used for configuring the loading indicator.
  1387. *
  1388. * @return The instance used for configuring the loading indicator
  1389. */
  1390. public LoadingIndicatorConfiguration getLoadingIndicatorConfiguration() {
  1391. return loadingIndicatorConfiguration;
  1392. }
  1393. /**
  1394. * Pushes the pending changes and client RPC invocations of this UI to the
  1395. * client-side.
  1396. * <p>
  1397. * If push is enabled, but the push connection is not currently open, the
  1398. * push will be done when the connection is established.
  1399. * <p>
  1400. * As with all UI methods, the session must be locked when calling this
  1401. * method. It is also recommended that {@link UI#getCurrent()} is set up to
  1402. * return this UI since writing the response may invoke logic in any
  1403. * attached component or extension. The recommended way of fulfilling these
  1404. * conditions is to use {@link #access(Runnable)}.
  1405. *
  1406. * @throws IllegalStateException
  1407. * if push is disabled.
  1408. * @throws UIDetachedException
  1409. * if this UI is not attached to a session.
  1410. *
  1411. * @see #getPushConfiguration()
  1412. *
  1413. * @since 7.1
  1414. */
  1415. public void push() {
  1416. VaadinSession session = getSession();
  1417. if (session == null) {
  1418. throw new UIDetachedException("Cannot push a detached UI");
  1419. }
  1420. assert session.hasLock();
  1421. if (!getPushConfiguration().getPushMode().isEnabled()) {
  1422. throw new IllegalStateException("Push not enabled");
  1423. }
  1424. assert pushConnection != null;
  1425. /*
  1426. * Purge the pending access queue as it might mark a connector as dirty
  1427. * when the push would otherwise be ignored because there are no changes
  1428. * to push.
  1429. */
  1430. session.getService().runPendingAccessTasks(session);
  1431. if (!getConnectorTracker().hasDirtyConnectors()) {
  1432. // Do not push if there is nothing to push
  1433. return;
  1434. }
  1435. pushConnection.push();
  1436. }
  1437. /**
  1438. * Returns the internal push connection object used by this UI. This method
  1439. * should only be called by the framework.
  1440. * <p>
  1441. * This method is not intended to be overridden. If it is overridden, care
  1442. * should be taken since this method might be called in situations where
  1443. * {@link UI#getCurrent()} does not return this UI.
  1444. *
  1445. * @return the push connection used by this UI, or {@code null} if push is
  1446. * not available.
  1447. */
  1448. public PushConnection getPushConnection() {
  1449. assert !(getPushConfiguration().getPushMode().isEnabled()
  1450. && pushConnection == null);
  1451. return pushConnection;
  1452. }
  1453. /**
  1454. * Sets the internal push connection object used by this UI. This method
  1455. * should only be called by the framework.
  1456. * <p>
  1457. * The {@code pushConnection} argument must be non-null if and only if
  1458. * {@code getPushConfiguration().getPushMode().isEnabled()}.
  1459. *
  1460. * @param pushConnection
  1461. * the push connection to use for this UI
  1462. */
  1463. public void setPushConnection(PushConnection pushConnection) {
  1464. // If pushMode is disabled then there should never be a pushConnection;
  1465. // if enabled there should always be
  1466. assert (pushConnection == null)
  1467. ^ getPushConfiguration().getPushMode().isEnabled();
  1468. if (pushConnection == this.pushConnection) {
  1469. return;
  1470. }
  1471. if (this.pushConnection != null && this.pushConnection.isConnected()) {
  1472. this.pushConnection.disconnect();
  1473. }
  1474. this.pushConnection = pushConnection;
  1475. }
  1476. /**
  1477. * Sets the interval with which the UI should poll the server to see if
  1478. * there are any changes. Polling is disabled by default.
  1479. * <p>
  1480. * Note that it is possible to enable push and polling at the same time but
  1481. * it should not be done to avoid excessive server traffic.
  1482. * </p>
  1483. * <p>
  1484. * Add-on developers should note that this method is only meant for the
  1485. * application developer. An add-on should not set the poll interval
  1486. * directly, rather instruct the user to set it.
  1487. * </p>
  1488. *
  1489. * @param intervalInMillis
  1490. * The interval (in ms) with which the UI should poll the server
  1491. * or -1 to disable polling
  1492. */
  1493. public void setPollInterval(int intervalInMillis) {
  1494. getState().pollInterval = intervalInMillis;
  1495. }
  1496. /**
  1497. * Returns the interval with which the UI polls the server.
  1498. *
  1499. * @return The interval (in ms) with which the UI polls the server or -1 if
  1500. * polling is disabled
  1501. */
  1502. public int getPollInterval() {
  1503. return getState(false).pollInterval;
  1504. }
  1505. @Override
  1506. public Registration addPollListener(PollListener listener) {
  1507. return addListener(EventId.POLL, PollEvent.class, listener,
  1508. PollListener.POLL_METHOD);
  1509. }
  1510. @Override
  1511. @Deprecated
  1512. public void removePollListener(PollListener listener) {
  1513. removeListener(EventId.POLL, PollEvent.class, listener);
  1514. }
  1515. /**
  1516. * Retrieves the object used for configuring the push channel.
  1517. *
  1518. * @since 7.1
  1519. * @return The instance used for push configuration
  1520. */
  1521. public PushConfiguration getPushConfiguration() {
  1522. return pushConfiguration;
  1523. }
  1524. /**
  1525. * Retrieves the object used for configuring the reconnect dialog.
  1526. *
  1527. * @since 7.6
  1528. * @return The instance used for reconnect dialog configuration
  1529. */
  1530. public ReconnectDialogConfiguration getReconnectDialogConfiguration() {
  1531. return reconnectDialogConfiguration;
  1532. }
  1533. /**
  1534. * Get the label that is added to the container element, where tooltip,
  1535. * notification and dialogs are added to.
  1536. *
  1537. * @return the label of the container
  1538. */
  1539. public String getOverlayContainerLabel() {
  1540. return getState(false).overlayContainerLabel;
  1541. }
  1542. /**
  1543. * Sets the label that is added to the container element, where tooltip,
  1544. * notifications and dialogs are added to.
  1545. * <p>
  1546. * This is helpful for users of assistive devices, as this element is
  1547. * reachable for them.
  1548. * </p>
  1549. *
  1550. * @param overlayContainerLabel
  1551. * label to use for the container
  1552. */
  1553. public void setOverlayContainerLabel(String overlayContainerLabel) {
  1554. getState().overlayContainerLabel = overlayContainerLabel;
  1555. }
  1556. /**
  1557. * Returns the locale service which handles transmission of Locale data to
  1558. * the client.
  1559. *
  1560. * @since 7.1
  1561. * @return The LocaleService for this UI
  1562. */
  1563. public LocaleService getLocaleService() {
  1564. return localeService;
  1565. }
  1566. private static Logger getLogger() {
  1567. return Logger.getLogger(UI.class.getName());
  1568. }
  1569. /**
  1570. * Gets a string the uniquely distinguishes this UI instance based on where
  1571. * it is embedded. The embed identifier is based on the
  1572. * <code>window.name</code> DOM attribute of the browser window where the UI
  1573. * is displayed and the id of the div element where the UI is embedded.
  1574. *
  1575. * @since 7.2
  1576. * @return the embed id for this UI, or <code>null</code> if no id known
  1577. */
  1578. public String getEmbedId() {
  1579. return embedId;
  1580. }
  1581. /**
  1582. * Gets the last processed server message id.
  1583. *
  1584. * Used internally for communication tracking.
  1585. *
  1586. * @return lastProcessedServerMessageId the id of the last processed server
  1587. * message
  1588. * @since 7.6
  1589. */
  1590. public int getLastProcessedClientToServerId() {
  1591. return lastProcessedClientToServerId;
  1592. }
  1593. /**
  1594. * Sets the last processed server message id.
  1595. *
  1596. * Used internally for communication tracking.
  1597. *
  1598. * @param lastProcessedServerMessageId
  1599. * the id of the last processed server message
  1600. * @since 7.6
  1601. */
  1602. public void setLastProcessedClientToServerId(
  1603. int lastProcessedClientToServerId) {
  1604. this.lastProcessedClientToServerId = lastProcessedClientToServerId;
  1605. }
  1606. /**
  1607. * Adds a WindowOrderUpdateListener to the UI.
  1608. * <p>
  1609. * The WindowOrderUpdateEvent is fired when the order positions of windows
  1610. * are updated. It can happen when some window (this or other) is brought to
  1611. * front or detached.
  1612. * <p>
  1613. * The other way to listen window position for specific window is
  1614. * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)}
  1615. *
  1616. * @see Window#addWindowOrderChangeListener(WindowOrderChangeListener)
  1617. *
  1618. * @param listener
  1619. * the WindowModeChangeListener to add.
  1620. * @since 8.0.0
  1621. *
  1622. * @return a registration object for removing the listener
  1623. */
  1624. public Registration addWindowOrderUpdateListener(
  1625. WindowOrderUpdateListener listener) {
  1626. addListener(EventId.WINDOW_ORDER, WindowOrderUpdateEvent.class,
  1627. listener, WindowOrderUpdateListener.windowOrderUpdateMethod);
  1628. return () -> removeListener(EventId.WINDOW_ORDER,
  1629. WindowOrderUpdateEvent.class, listener);
  1630. }
  1631. /**
  1632. * Event which is fired when the ordering of the windows is updated.
  1633. * <p>
  1634. * The other way to listen window position for specific window is
  1635. * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)}
  1636. *
  1637. * @see Window.WindowOrderChangeEvent
  1638. *
  1639. * @author Vaadin Ltd
  1640. * @since 8.0.0
  1641. *
  1642. */
  1643. public static class WindowOrderUpdateEvent extends Component.Event {
  1644. private final Collection<Window> windows;
  1645. public WindowOrderUpdateEvent(Component source,
  1646. Collection<Window> windows) {
  1647. super(source);
  1648. this.windows = windows;
  1649. }
  1650. /**
  1651. * Gets the windows in the order they appear in the UI: top most window
  1652. * is first, bottom one last.
  1653. *
  1654. * @return the windows collection
  1655. */
  1656. public Collection<Window> getWindows() {
  1657. return windows;
  1658. }
  1659. }
  1660. /**
  1661. * An interface used for listening to Windows order update events.
  1662. *
  1663. * @since 8.0.0
  1664. *
  1665. * @see Window.WindowOrderChangeEvent
  1666. */
  1667. @FunctionalInterface
  1668. public interface WindowOrderUpdateListener extends ConnectorEventListener {
  1669. public static final Method windowOrderUpdateMethod = ReflectTools
  1670. .findMethod(WindowOrderUpdateListener.class,
  1671. "windowOrderUpdated", WindowOrderUpdateEvent.class);
  1672. /**
  1673. * Called when the windows order positions are changed. Use
  1674. * {@link WindowOrderUpdateEvent#getWindows()} to get a reference to the
  1675. * {@link Window}s whose order positions are updated. Use
  1676. * {@link Window#getOrderPosition()} to get window position for specific
  1677. * window.
  1678. *
  1679. * @param event
  1680. */
  1681. public void windowOrderUpdated(WindowOrderUpdateEvent event);
  1682. }
  1683. }