Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

UI.java 58KB

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