12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081 |
- /*
- * Copyright 2000-2018 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.ui;
-
- import static java.nio.charset.StandardCharsets.UTF_8;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.lang.reflect.Method;
- import java.net.URI;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.concurrent.Future;
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.vaadin.annotations.PreserveOnRefresh;
- import com.vaadin.event.Action;
- import com.vaadin.event.Action.Handler;
- import com.vaadin.event.ActionManager;
- import com.vaadin.event.ConnectorEventListener;
- import com.vaadin.event.MouseEvents.ClickEvent;
- import com.vaadin.event.MouseEvents.ClickListener;
- import com.vaadin.event.UIEvents.PollEvent;
- import com.vaadin.event.UIEvents.PollListener;
- import com.vaadin.event.UIEvents.PollNotifier;
- import com.vaadin.navigator.Navigator;
- import com.vaadin.navigator.PushStateNavigation;
- import com.vaadin.server.ClientConnector;
- import com.vaadin.server.ComponentSizeValidator;
- import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
- import com.vaadin.server.DefaultErrorHandler;
- import com.vaadin.server.ErrorHandler;
- import com.vaadin.server.ErrorHandlingRunnable;
- import com.vaadin.server.LocaleService;
- import com.vaadin.server.Page;
- import com.vaadin.server.PaintException;
- import com.vaadin.server.PaintTarget;
- import com.vaadin.server.UIProvider;
- import com.vaadin.server.VaadinRequest;
- import com.vaadin.server.VaadinService;
- import com.vaadin.server.VaadinServlet;
- import com.vaadin.server.VaadinSession;
- import com.vaadin.server.VaadinSession.State;
- import com.vaadin.server.communication.PushConnection;
- import com.vaadin.shared.ApplicationConstants;
- import com.vaadin.shared.Connector;
- import com.vaadin.shared.EventId;
- import com.vaadin.shared.MouseEventDetails;
- import com.vaadin.shared.Registration;
- import com.vaadin.shared.communication.PushMode;
- import com.vaadin.shared.ui.WindowOrderRpc;
- import com.vaadin.shared.ui.ui.DebugWindowClientRpc;
- import com.vaadin.shared.ui.ui.DebugWindowServerRpc;
- import com.vaadin.shared.ui.ui.PageClientRpc;
- import com.vaadin.shared.ui.ui.ScrollClientRpc;
- import com.vaadin.shared.ui.ui.UIClientRpc;
- import com.vaadin.shared.ui.ui.UIConstants;
- import com.vaadin.shared.ui.ui.UIServerRpc;
- import com.vaadin.shared.ui.ui.UIState;
- import com.vaadin.ui.Component.Focusable;
- import com.vaadin.ui.Dependency.Type;
- import com.vaadin.ui.Window.WindowOrderChangeListener;
- import com.vaadin.ui.declarative.Design;
- import com.vaadin.ui.dnd.DragSourceExtension;
- import com.vaadin.ui.dnd.DropTargetExtension;
- import com.vaadin.util.ConnectorHelper;
- import com.vaadin.util.CurrentInstance;
- import com.vaadin.util.ReflectTools;
-
- /**
- * The topmost component in any component hierarchy. There is one UI for every
- * Vaadin instance in a browser window. A UI may either represent an entire
- * browser window (or tab) or some part of a html page where a Vaadin
- * application is embedded.
- * <p>
- * The UI is the server side entry point for various client side features that
- * are not represented as components added to a layout, e.g notifications, sub
- * windows, and executing javascript in the browser.
- * </p>
- * <p>
- * When a new UI instance is needed, typically because the user opens a URL in a
- * browser window which points to e.g. {@link VaadinServlet}, all
- * {@link UIProvider}s registered to the current {@link VaadinSession} are
- * queried for the UI class that should be used. The selection is by default
- * based on the <code>UI</code> init parameter from web.xml.
- * </p>
- * <p>
- * After a UI has been created by the application, it is initialized using
- * {@link #init(VaadinRequest)}. This method is intended to be overridden by the
- * developer to add components to the user interface and initialize
- * non-component functionality. The component hierarchy must be initialized by
- * passing a {@link Component} with the main layout or other content of the view
- * to {@link #setContent(Component)} or to the constructor of the UI.
- * </p>
- *
- * @see #init(VaadinRequest)
- * @see UIProvider
- *
- * @since 7.0
- */
- public abstract class UI extends AbstractSingleComponentContainer
- implements Action.Notifier, PollNotifier, LegacyComponent, Focusable {
-
- /**
- * The application to which this UI belongs
- */
- private volatile VaadinSession session;
-
- /**
- * List of windows in this UI.
- */
- private final LinkedHashSet<Window> windows = new LinkedHashSet<>();
-
- /**
- * The component that should be scrolled into view after the next repaint.
- * Null if nothing should be scrolled into view.
- */
- private Component scrollIntoView;
-
- /**
- * The id of this UI, used to find the server side instance of the UI form
- * which a request originates. A negative value indicates that the UI id has
- * not yet been assigned by the Application.
- *
- * @see VaadinSession#getNextUIid()
- */
- private int uiId = -1;
-
- /**
- * Keeps track of the Actions added to this component, and manages the
- * painting and handling as well.
- */
- protected ActionManager actionManager;
-
- private ConnectorTracker connectorTracker = new ConnectorTracker(this);
-
- private Page page = new Page(this, getState(false).pageState);
-
- private LoadingIndicatorConfiguration loadingIndicatorConfiguration = new LoadingIndicatorConfigurationImpl(
- this);
-
- /**
- * Holder for old navigation state, needed in doRefresh in order not to call
- * navigateTo too often
- */
- private String oldNavigationState;
-
- /**
- * Scroll Y position.
- */
- private int scrollTop = 0;
-
- /**
- * Scroll X position
- */
- private int scrollLeft = 0;
-
- private UIServerRpc rpc = new UIServerRpc() {
- @Override
- public void click(MouseEventDetails mouseDetails) {
- fireEvent(new ClickEvent(UI.this, mouseDetails));
- }
-
- @Override
- public void resize(int viewWidth, int viewHeight, int windowWidth,
- int windowHeight) {
- // TODO We're not doing anything with the view dimensions
- getPage().updateBrowserWindowSize(windowWidth, windowHeight, true);
- }
-
- @Override
- public void scroll(int scrollTop, int scrollLeft) {
- UI.this.scrollTop = scrollTop;
- UI.this.scrollLeft = scrollLeft;
- }
-
- @Override
- public void poll() {
- fireEvent(new PollEvent(UI.this));
- }
-
- @Override
- public void popstate(String uri) {
- getPage().updateLocation(uri, true, true);
- }
- };
- private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() {
- @Override
- public void showServerDebugInfo(Connector connector) {
- String info = ConnectorHelper
- .getDebugInformation((ClientConnector) connector);
- getLogger().info(info);
- }
-
- @Override
- public void analyzeLayouts() {
- // TODO Move to client side
- List<InvalidLayout> invalidSizes = ComponentSizeValidator
- .validateLayouts(UI.this);
- StringBuilder json = new StringBuilder();
- json.append("{\"invalidLayouts\":");
- json.append('[');
-
- if (invalidSizes != null) {
- boolean first = true;
- for (InvalidLayout invalidSize : invalidSizes) {
- if (!first) {
- json.append(',');
- } else {
- first = false;
- }
- invalidSize.reportErrors(json, System.err);
- }
- }
- json.append("]}");
- getRpcProxy(DebugWindowClientRpc.class)
- .reportLayoutProblems(json.toString());
- }
-
- @Override
- public void showServerDesign(Connector connector) {
- if (!(connector instanceof Component)) {
- getLogger().severe("Tried to output declarative design for "
- + connector + ", which is not a component");
- return;
- }
- if (connector instanceof UI) {
- // We want to see the content of the UI, so we can add it to
- // another UI or component container
- connector = ((UI) connector).getContent();
- }
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- Design.write((Component) connector, baos);
- getLogger().info("Design for " + connector
- + " requested from debug window:\n"
- + baos.toString(UTF_8.name()));
- } catch (IOException e) {
- getLogger().log(Level.WARNING,
- "Error producing design for " + connector, e);
- }
-
- }
- };
-
- private WindowOrderRpc windowOrderRpc = windowOrders -> {
- Map<Integer, Window> orders = new LinkedHashMap<>();
- for (Entry<Integer, Connector> entry : windowOrders.entrySet()) {
- if (entry.getValue() instanceof Window) {
- orders.put(entry.getKey(), (Window) entry.getValue());
- }
- }
- fireWindowOrder(orders);
- };
-
- /**
- * Timestamp keeping track of the last heartbeat of this UI. Updated to the
- * current time whenever the application receives a heartbeat or UIDL
- * request from the client for this UI.
- */
- private long lastHeartbeatTimestamp = System.currentTimeMillis();
-
- private boolean closing = false;
-
- private TooltipConfiguration tooltipConfiguration = new TooltipConfigurationImpl(
- this);
- private PushConfiguration pushConfiguration = new PushConfigurationImpl(
- this);
- private ReconnectDialogConfiguration reconnectDialogConfiguration = new ReconnectDialogConfigurationImpl(
- this);
-
- private NotificationConfiguration notificationConfiguration = new NotificationConfigurationImpl(
- this);
-
- /**
- * Tracks which message from the client should come next. First message from
- * the client has id 0.
- */
- private int lastProcessedClientToServerId = -1;
-
- /**
- * Stores the extension of the active drag source component
- */
- private DragSourceExtension<? extends AbstractComponent> activeDragSource;
-
- /**
- * Creates a new empty UI without a caption. The content of the UI must be
- * set by calling {@link #setContent(Component)} before using the UI.
- */
- public UI() {
- this(null);
- }
-
- /**
- * Creates a new UI with the given component (often a layout) as its
- * content.
- *
- * @param content
- * the component to use as this UIs content.
- *
- * @see #setContent(Component)
- */
- public UI(Component content) {
- registerRpc(rpc);
- registerRpc(debugRpc);
- registerRpc(windowOrderRpc);
- setSizeFull();
- setContent(content);
- }
-
- @Override
- protected UIState getState() {
- return (UIState) super.getState();
- }
-
- @Override
- protected UIState getState(boolean markAsDirty) {
- return (UIState) super.getState(markAsDirty);
- }
-
- @Override
- public Class<? extends UIState> getStateType() {
- // This is a workaround for a problem with creating the correct state
- // object during build
- return UIState.class;
- }
-
- /**
- * Overridden to return a value instead of referring to the parent.
- *
- * @return this UI
- *
- * @see com.vaadin.ui.AbstractComponent#getUI()
- */
- @Override
- public UI getUI() {
- return this;
- }
-
- /**
- * Gets the application object to which the component is attached.
- *
- * <p>
- * The method will return {@code null} if the component is not currently
- * attached to an application.
- * </p>
- *
- * <p>
- * Getting a null value is often a problem in constructors of regular
- * components and in the initializers of custom composite components. A
- * standard workaround is to use {@link VaadinSession#getCurrent()} to
- * retrieve the application instance that the current request relates to.
- * Another way is to move the problematic initialization to
- * {@link #attach()}, as described in the documentation of the method.
- * </p>
- *
- * @return the parent application of the component or <code>null</code>.
- * @see #attach()
- */
- @Override
- public VaadinSession getSession() {
- return session;
- }
-
- @Override
- public void paintContent(PaintTarget target) throws PaintException {
- page.paintContent(target);
-
- if (scrollIntoView != null) {
- target.addAttribute("scrollTo", scrollIntoView);
- scrollIntoView = null;
- }
-
- if (pendingFocus != null) {
- // ensure focused component is still attached to this main window
- if (equals(pendingFocus.getUI()) || (pendingFocus.getUI() != null
- && equals(pendingFocus.getUI().getParent()))) {
- target.addAttribute("focused", pendingFocus);
- }
- pendingFocus = null;
- }
-
- if (actionManager != null) {
- actionManager.paintActions(null, target);
- }
-
- if (isResizeLazy()) {
- target.addAttribute(UIConstants.RESIZE_LAZY, true);
- }
- }
-
- /**
- * Fire a click event to all click listeners.
- *
- * @param object
- * The raw "value" of the variable change from the client side.
- */
- private void fireClick(Map<String, Object> parameters) {
- MouseEventDetails mouseDetails = MouseEventDetails
- .deSerialize((String) parameters.get("mouseDetails"));
- fireEvent(new ClickEvent(this, mouseDetails));
- }
-
- /**
- * Fire a window order event.
- *
- * @param windows
- * The windows with their orders whose order has been updated.
- */
- private void fireWindowOrder(Map<Integer, Window> windows) {
- for (Entry<Integer, Window> entry : windows.entrySet()) {
- entry.getValue().fireWindowOrderChange(entry.getKey());
- }
- fireEvent(new WindowOrderUpdateEvent(this, windows.values()));
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void changeVariables(Object source, Map<String, Object> variables) {
- if (variables.containsKey(EventId.CLICK_EVENT_IDENTIFIER)) {
- fireClick((Map<String, Object>) variables
- .get(EventId.CLICK_EVENT_IDENTIFIER));
- }
-
- // Actions
- if (actionManager != null) {
- actionManager.handleActions(variables, this);
- }
-
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.HasComponents#iterator()
- */
- @Override
- public Iterator<Component> iterator() {
- // TODO could directly create some kind of combined iterator instead of
- // creating a new ArrayList
- List<Component> components = new ArrayList<>();
-
- if (getContent() != null) {
- components.add(getContent());
- }
-
- components.addAll(windows);
-
- return Collections.unmodifiableCollection(components).iterator();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.ComponentContainer#getComponentCount()
- */
- @Override
- public int getComponentCount() {
- return windows.size() + (getContent() == null ? 0 : 1);
- }
-
- /**
- * Sets the session to which this UI is assigned.
- * <p>
- * This method is for internal use by the framework. To explicitly close a
- * UI, see {@link #close()}.
- * </p>
- *
- * @param session
- * the session to set
- *
- * @throws IllegalStateException
- * if the session has already been set
- *
- * @see #getSession()
- */
- public void setSession(VaadinSession session) {
- if (session == null && this.session == null) {
- throw new IllegalStateException(
- "Session should never be set to null when UI.session is already null");
- } else if (session != null && this.session != null) {
- throw new IllegalStateException(
- "Session has already been set. Old session: "
- + getSessionDetails(this.session)
- + ". New session: " + getSessionDetails(session)
- + ".");
- } else {
- if (session == null) {
- try {
- detach();
- } catch (Exception e) {
- getLogger().log(Level.WARNING,
- "Error while detaching UI from session", e);
- }
- // Disable push when the UI is detached. Otherwise the
- // push connection and possibly VaadinSession will live
- // on.
- getPushConfiguration().setPushMode(PushMode.DISABLED);
-
- new Thread(() -> {
- // This intentionally does disconnect without locking
- // the VaadinSession to avoid deadlocks where the server
- // uses a lock for the websocket connection
-
- // See https://dev.vaadin.com/ticket/18436
- // The underlying problem is
- // https://dev.vaadin.com/ticket/16919
- setPushConnection(null);
- }).start();
- }
- this.session = session;
- }
-
- if (session != null) {
- attach();
- }
- }
-
- private static String getSessionDetails(VaadinSession session) {
- if (session == null) {
- return null;
- } else {
- return session + " for " + session.getService().getServiceName();
- }
- }
-
- /**
- * Gets the id of the UI, used to identify this UI within its application
- * when processing requests. The UI id should be present in every request to
- * the server that originates from this UI.
- * {@link VaadinService#findUI(VaadinRequest)} uses this id to find the
- * route to which the request belongs.
- * <p>
- * This method is not intended to be overridden. If it is overridden, care
- * should be taken since this method might be called in situations where
- * {@link UI#getCurrent()} does not return this UI.
- *
- * @return the id of this UI
- */
- public int getUIId() {
- return uiId;
- }
-
- /**
- * Adds a window as a subwindow inside this UI. To open a new browser window
- * or tab, you should instead use a {@link UIProvider}.
- *
- * @param window
- * @throws IllegalArgumentException
- * if the window is already added to an application
- * @throws NullPointerException
- * if the given <code>Window</code> is <code>null</code>.
- */
- public void addWindow(Window window)
- throws IllegalArgumentException, NullPointerException {
-
- if (window == null) {
- throw new NullPointerException("Argument must not be null");
- }
-
- if (window.isAttached()) {
- throw new IllegalArgumentException(
- "Window is already attached to an application.");
- }
-
- attachWindow(window);
- }
-
- /**
- * Helper method to attach a window.
- *
- * @param w
- * the window to add
- */
- private void attachWindow(Window w) {
- windows.add(w);
- w.setParent(this);
- fireComponentAttachEvent(w);
- markAsDirty();
- }
-
- /**
- * Remove the given subwindow from this UI.
- *
- * Since Vaadin 6.5, {@link Window.CloseListener}s are called also when
- * explicitly removing a window by calling this method.
- *
- * Since Vaadin 6.5, returns a boolean indicating if the window was removed
- * or not.
- *
- * @param window
- * Window to be removed.
- * @return true if the subwindow was removed, false otherwise
- */
- public boolean removeWindow(Window window) {
- if (!windows.remove(window)) {
- // Window window is not a subwindow of this UI.
- return false;
- }
- window.setParent(null);
- markAsDirty();
- window.fireClose();
- fireComponentDetachEvent(window);
- fireWindowOrder(Collections.singletonMap(-1, window));
-
- return true;
- }
-
- /**
- * Gets all the windows added to this UI.
- *
- * @return an unmodifiable collection of windows
- */
- public Collection<Window> getWindows() {
- return Collections.unmodifiableCollection(windows);
- }
-
- @Override
- public void focus() {
- super.focus();
- }
-
- /**
- * Component that should be focused after the next repaint. Null if no focus
- * change should take place.
- */
- private Focusable pendingFocus;
-
- private boolean resizeLazy = false;
-
- private Navigator navigator;
-
- private PushConnection pushConnection = null;
-
- private LocaleService localeService = new LocaleService(this,
- getState(false).localeServiceState);
-
- private String embedId;
-
- private String uiPathInfo;
-
- private String uiRootPath;
-
- private boolean mobileHtml5DndPolyfillLoaded;
-
- /**
- * This method is used by Component.Focusable objects to request focus to
- * themselves. Focus renders must be handled at window level (instead of
- * Component.Focusable) due we want the last focused component to be focused
- * in client too. Not the one that is rendered last (the case we'd get if
- * implemented in Focusable only).
- *
- * To focus component from Vaadin application, use Focusable.focus(). See
- * {@link Focusable}.
- *
- * @param focusable
- * to be focused on next paint
- */
- public void setFocusedComponent(Focusable focusable) {
- pendingFocus = focusable;
- markAsDirty();
- }
-
- /**
- * Scrolls any component between the component and UI to a suitable position
- * so the component is visible to the user. The given component must belong
- * to this UI.
- *
- * @param component
- * the component to be scrolled into view
- * @throws IllegalArgumentException
- * if {@code component} does not belong to this UI
- */
- public void scrollIntoView(Component component)
- throws IllegalArgumentException {
- if (component.getUI() != this) {
- throw new IllegalArgumentException(
- "The component where to scroll must belong to this UI.");
- }
- scrollIntoView = component;
- markAsDirty();
- }
-
- /**
- * Internal initialization method, should not be overridden. This method is
- * not declared as final because that would break compatibility with e.g.
- * CDI.
- *
- * @param request
- * the initialization request
- * @param uiId
- * the id of the new ui
- * @param embedId
- * the embed id of this UI, or <code>null</code> if no id is
- * known
- *
- * @see #getUIId()
- * @see #getEmbedId()
- */
- public void doInit(VaadinRequest request, int uiId, String embedId) {
- if (this.uiId != -1) {
- String message = "This UI instance is already initialized (as UI id "
- + this.uiId
- + ") and can therefore not be initialized again (as UI id "
- + uiId + "). ";
-
- if (getSession() != null
- && !getSession().equals(VaadinSession.getCurrent())) {
- message += "Furthermore, it is already attached to another VaadinSession. ";
- }
- message += "Please make sure you are not accidentally reusing an old UI instance.";
-
- throw new IllegalStateException(message);
- }
- this.uiId = uiId;
- this.embedId = embedId;
-
- // Actual theme - used for finding CustomLayout templates
- setTheme(request.getParameter("theme"));
-
- getPage().init(request);
-
- String uiPathInfo = (String) request
- .getAttribute(ApplicationConstants.UI_ROOT_PATH);
- if (uiPathInfo != null) {
- setUiPathInfo(uiPathInfo);
- }
-
- if (getSession() != null && getSession().getConfiguration() != null
- && getSession().getConfiguration().isSendUrlsAsParameters()
- && getPage().getLocation() != null) {
- // By default the root is the URL from client
- String uiRootPath = getPage().getLocation().getPath();
-
- if (uiPathInfo != null && uiRootPath.contains(uiPathInfo)) {
- // String everything from the URL after uiPathInfo
- // This will remove the navigation state from the URL
- uiRootPath = uiRootPath.substring(0,
- uiRootPath.indexOf(uiPathInfo) + uiPathInfo.length());
- } else if (request.getPathInfo() != null) {
- // uiRootPath does not match the uiPathInfo
- // This can happen for example when embedding a Vaadin UI
- String pathInfo = request.getPathInfo();
- if (uiRootPath.endsWith(pathInfo)) {
- uiRootPath = uiRootPath.substring(0,
- uiRootPath.length() - pathInfo.length());
- }
- }
- // Store the URL as the UI Root Path
- setUiRootPath(uiRootPath);
- }
-
- // Call the init overridden by the application developer
- init(request);
-
- Navigator navigator = getNavigator();
- if (navigator != null) {
- // Kickstart navigation if a navigator was attached in init()
- navigator.navigateTo(navigator.getState());
- }
- }
-
- private void setUiRootPath(String uiRootPath) {
- this.uiRootPath = uiRootPath;
- }
-
- /**
- * Gets the part of path (from browser's URL) that points to this UI.
- * Basically the same as the value from {@link Page#getLocation()}, but
- * without possible view identifiers or path parameters.
- *
- * @return the part of path (from browser's URL) that points to this UI,
- * without possible view identifiers or path parameters
- *
- * @since 8.2
- */
- public String getUiRootPath() {
- return uiRootPath;
- }
-
- private void setUiPathInfo(String uiPathInfo) {
- this.uiPathInfo = uiPathInfo;
- }
-
- /**
- * Gets the path info part of the request that is used to detect the UI.
- * This is defined during UI init by certain {@link UIProvider UIProviders}
- * that map different UIs to different URIs, like Vaadin Spring. This
- * information is used by the {@link Navigator} when the {@link UI} is
- * annotated with {@link PushStateNavigation}.
- * <p>
- * For example if the UI is accessed through
- * {@code http://example.com/MyUI/mainview/parameter=1} the path info would
- * be {@code /MyUI}.
- *
- * @return the path info part of the request; {@code null} if no request
- * from client has been processed
- *
- * @since 8.2
- */
- public String getUiPathInfo() {
- return uiPathInfo;
- }
-
- /**
- * Initializes this UI. This method is intended to be overridden by
- * subclasses to build the view and configure non-component functionality.
- * Performing the initialization in a constructor is not suggested as the
- * state of the UI is not properly set up when the constructor is invoked.
- * <p>
- * The {@link VaadinRequest} can be used to get information about the
- * request that caused this UI to be created.
- * </p>
- *
- * @param request
- * the Vaadin request that caused this UI to be created
- */
- protected abstract void init(VaadinRequest request);
-
- /**
- * Internal reinitialization method, should not be overridden.
- *
- * @since 7.2
- * @param request
- * the request that caused this UI to be reloaded
- */
- public void doRefresh(VaadinRequest request) {
- // This is a horrible hack. We want to have the most recent location and
- // browser window size available in refresh(), but we want to call
- // listeners, if any, only after refresh(). So we momentarily assign the
- // old values back before setting the new values again to ensure the
- // events are properly fired.
-
- Page page = getPage();
-
- URI oldLocation = page.getLocation();
- int oldWidth = page.getBrowserWindowWidth();
- int oldHeight = page.getBrowserWindowHeight();
-
- page.init(request);
-
- // Reset heartbeat timeout to avoid surprise if it's almost expired
- setLastHeartbeatTimestamp(System.currentTimeMillis());
-
- refresh(request);
-
- URI newLocation = page.getLocation();
- int newWidth = page.getBrowserWindowWidth();
- int newHeight = page.getBrowserWindowHeight();
-
- page.updateLocation(oldLocation.toString(), false, false);
- page.updateBrowserWindowSize(oldWidth, oldHeight, false);
-
- page.updateLocation(newLocation.toString(), true, false);
- page.updateBrowserWindowSize(newWidth, newHeight, true);
-
- // Navigate if there is navigator, this is needed in case of
- // PushStateNavigation. Call navigateTo only if state have
- // truly changed
- Navigator navigator = getNavigator();
- if (navigator != null) {
- if (oldNavigationState == null) oldNavigationState = getNavigator().getState();
- if (!navigator.getState().equals(oldNavigationState)) {
- navigator.navigateTo(navigator.getState());
- oldNavigationState = navigator.getState();
- }
- }
- }
-
- /**
- * Reinitializes this UI after a browser refresh if the UI is set to be
- * preserved on refresh, typically using the {@link PreserveOnRefresh}
- * annotation. This method is intended to be overridden by subclasses if
- * needed; the default implementation is empty.
- * <p>
- * The {@link VaadinRequest} can be used to get information about the
- * request that caused this UI to be reloaded.
- *
- * @since 7.2
- * @param request
- * the request that caused this UI to be reloaded
- */
- protected void refresh(VaadinRequest request) {
- }
-
- /**
- * Sets the thread local for the current UI. This method is used by the
- * framework to set the current application whenever a new request is
- * processed and it is cleared when the request has been processed.
- * <p>
- * The application developer can also use this method to define the current
- * UI outside the normal request handling, e.g. when initiating custom
- * background threads.
- * <p>
- * The UI is stored using a weak reference to avoid leaking memory in case
- * it is not explicitly cleared.
- *
- * @param ui
- * the UI to register as the current UI
- *
- * @see #getCurrent()
- * @see ThreadLocal
- */
- public static void setCurrent(UI ui) {
- CurrentInstance.set(UI.class, ui);
- }
-
- /**
- * Gets the currently used UI. The current UI is automatically defined when
- * processing requests to the server. In other cases, (e.g. from background
- * threads), the current UI is not automatically defined.
- * <p>
- * The UI is stored using a weak reference to avoid leaking memory in case
- * it is not explicitly cleared.
- *
- * @return the current UI instance if available, otherwise <code>null</code>
- *
- * @see #setCurrent(UI)
- */
- public static UI getCurrent() {
- return CurrentInstance.get(UI.class);
- }
-
- /**
- * Set top offset to which the UI should scroll to.
- *
- * @param scrollTop
- */
- public void setScrollTop(int scrollTop) {
- if (scrollTop < 0) {
- throw new IllegalArgumentException(
- "Scroll offset must be at least 0");
- }
- if (this.scrollTop != scrollTop) {
- this.scrollTop = scrollTop;
- getRpcProxy(ScrollClientRpc.class).setScrollTop(scrollTop);
- }
- }
-
- public int getScrollTop() {
- return scrollTop;
- }
-
- /**
- * Set left offset to which the UI should scroll to.
- *
- * @param scrollLeft
- */
- public void setScrollLeft(int scrollLeft) {
- if (scrollLeft < 0) {
- throw new IllegalArgumentException(
- "Scroll offset must be at least 0");
- }
- if (this.scrollLeft != scrollLeft) {
- this.scrollLeft = scrollLeft;
- getRpcProxy(ScrollClientRpc.class).setScrollLeft(scrollLeft);
- }
- }
-
- public int getScrollLeft() {
- return scrollLeft;
- }
-
- @Override
- protected ActionManager getActionManager() {
- if (actionManager == null) {
- actionManager = new ActionManager(this);
- }
- return actionManager;
- }
-
- @Override
- public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
- T action) {
- getActionManager().addAction(action);
- }
-
- @Override
- public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
- T action) {
- if (actionManager != null) {
- actionManager.removeAction(action);
- }
- }
-
- @Override
- public void addActionHandler(Handler actionHandler) {
- getActionManager().addActionHandler(actionHandler);
- }
-
- @Override
- public void removeActionHandler(Handler actionHandler) {
- if (actionManager != null) {
- actionManager.removeActionHandler(actionHandler);
- }
- }
-
- /**
- * Should resize operations be lazy, i.e. should there be a delay before
- * layout sizes are recalculated and resize events are sent to the server.
- * Speeds up resize operations in slow UIs with the penalty of slightly
- * decreased usability.
- * <p>
- * Default value: <code>false</code>
- * </p>
- * <p>
- * When there are active window resize listeners, lazy resize mode should be
- * used to avoid a large number of events during resize.
- * </p>
- *
- * @param resizeLazy
- * true to use a delay before recalculating sizes, false to
- * calculate immediately.
- */
- public void setResizeLazy(boolean resizeLazy) {
- this.resizeLazy = resizeLazy;
- markAsDirty();
- }
-
- /**
- * Checks whether lazy resize is enabled.
- *
- * @return <code>true</code> if lazy resize is enabled, <code>false</code>
- * if lazy resize is not enabled
- */
- public boolean isResizeLazy() {
- return resizeLazy;
- }
-
- /**
- * Add a click listener to the UI. The listener is called whenever the user
- * clicks inside the UI. Also when the click targets a component inside the
- * UI, provided the targeted component does not prevent the click event from
- * propagating.
- *
- * @see Registration
- *
- * @param listener
- * The listener to add, not null
- * @return a registration object for removing the listener
- * @since 8.0
- */
- public Registration addClickListener(ClickListener listener) {
- return addListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
- listener, ClickListener.clickMethod);
- }
-
- /**
- * Remove a click listener from the UI. The listener should earlier have
- * been added using {@link #addListener(ClickListener)}.
- *
- * @param listener
- * The listener to remove
- *
- * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
- * registration object returned from
- * {@link #removeClickListener(ClickListener)}.
- */
- @Deprecated
- public void removeClickListener(ClickListener listener) {
- removeListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
- listener);
- }
-
- @Override
- public boolean isConnectorEnabled() {
- // TODO How can a UI be invisible? What does it mean?
- return isVisible() && isEnabled();
- }
-
- public ConnectorTracker getConnectorTracker() {
- return connectorTracker;
- }
-
- public Page getPage() {
- return page;
- }
-
- /**
- * Returns the navigator attached to this UI or null if there is no
- * navigator.
- *
- * @return
- */
- public Navigator getNavigator() {
- return navigator;
- }
-
- /**
- * For internal use only.
- *
- * @param navigator
- */
- public void setNavigator(Navigator navigator) {
- this.navigator = navigator;
- }
-
- /**
- * Setting the caption of a UI is not supported. To set the title of the
- * HTML page, use Page.setTitle
- *
- * @deprecated As of 7.0, use {@link Page#setTitle(String)}
- */
- @Override
- @Deprecated
- public void setCaption(String caption) {
- throw new UnsupportedOperationException(
- "You can not set the title of a UI. To set the title of the HTML page, use Page.setTitle");
- }
-
- /**
- * Shows a notification message on the middle of the UI. The message
- * automatically disappears ("humanized message").
- *
- * Care should be taken to to avoid XSS vulnerabilities as the caption is
- * rendered as html.
- *
- * @see #showNotification(Notification)
- * @see Notification
- *
- * @param caption
- * The message
- *
- * @deprecated As of 7.0, use Notification.show instead but be aware that
- * Notification.show does not allow HTML.
- */
- @Deprecated
- public void showNotification(String caption) {
- Notification notification = new Notification(caption);
- notification.setHtmlContentAllowed(true);// Backwards compatibility
- getPage().showNotification(notification);
- }
-
- /**
- * Shows a notification message the UI. The position and behavior of the
- * message depends on the type, which is one of the basic types defined in
- * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE.
- *
- * Care should be taken to to avoid XSS vulnerabilities as the caption is
- * rendered as html.
- *
- * @see #showNotification(Notification)
- * @see Notification
- *
- * @param caption
- * The message
- * @param type
- * The message type
- *
- * @deprecated As of 7.0, use Notification.show instead but be aware that
- * Notification.show does not allow HTML.
- */
- @Deprecated
- public void showNotification(String caption, Notification.Type type) {
- Notification notification = new Notification(caption, type);
- notification.setHtmlContentAllowed(true);// Backwards compatibility
- getPage().showNotification(notification);
- }
-
- /**
- * Shows a notification consisting of a bigger caption and a smaller
- * description on the middle of the UI. The message automatically disappears
- * ("humanized message").
- *
- * Care should be taken to to avoid XSS vulnerabilities as the caption and
- * description are rendered as html.
- *
- * @see #showNotification(Notification)
- * @see Notification
- *
- * @param caption
- * The caption of the message
- * @param description
- * The message description
- *
- * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
- * be aware that HTML by default not allowed.
- */
- @Deprecated
- public void showNotification(String caption, String description) {
- Notification notification = new Notification(caption, description);
- notification.setHtmlContentAllowed(true);// Backwards compatibility
- getPage().showNotification(notification);
- }
-
- /**
- * Shows a notification consisting of a bigger caption and a smaller
- * description. The position and behavior of the message depends on the
- * type, which is one of the basic types defined in {@link Notification} ,
- * for instance Notification.TYPE_WARNING_MESSAGE.
- *
- * Care should be taken to to avoid XSS vulnerabilities as the caption and
- * description are rendered as html.
- *
- * @see #showNotification(Notification)
- * @see Notification
- *
- * @param caption
- * The caption of the message
- * @param description
- * The message description
- * @param type
- * The message type
- *
- * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
- * be aware that HTML by default not allowed.
- */
- @Deprecated
- public void showNotification(String caption, String description,
- Notification.Type type) {
- Notification notification = new Notification(caption, description,
- type);
- notification.setHtmlContentAllowed(true);// Backwards compatibility
- getPage().showNotification(notification);
- }
-
- /**
- * Shows a notification consisting of a bigger caption and a smaller
- * description. The position and behavior of the message depends on the
- * type, which is one of the basic types defined in {@link Notification} ,
- * for instance Notification.TYPE_WARNING_MESSAGE.
- *
- * Care should be taken to avoid XSS vulnerabilities if html content is
- * allowed.
- *
- * @see #showNotification(Notification)
- * @see Notification
- *
- * @param caption
- * The message caption
- * @param description
- * The message description
- * @param type
- * The type of message
- * @param htmlContentAllowed
- * Whether html in the caption and description should be
- * displayed as html or as plain text
- *
- * @deprecated As of 7.0, use new Notification(...).show(Page).
- */
- @Deprecated
- public void showNotification(String caption, String description,
- Notification.Type type, boolean htmlContentAllowed) {
- getPage().showNotification(new Notification(caption, description, type,
- htmlContentAllowed));
- }
-
- /**
- * Shows a notification message.
- *
- * @see Notification
- * @see #showNotification(String)
- * @see #showNotification(String, int)
- * @see #showNotification(String, String)
- * @see #showNotification(String, String, int)
- *
- * @param notification
- * The notification message to show
- *
- * @deprecated As of 7.0, use Notification.show instead
- */
- @Deprecated
- public void showNotification(Notification notification) {
- getPage().showNotification(notification);
- }
-
- /**
- * Returns the timestamp of the last received heartbeat for this UI.
- * <p>
- * This method is not intended to be overridden. If it is overridden, care
- * should be taken since this method might be called in situations where
- * {@link UI#getCurrent()} does not return this UI.
- *
- * @see VaadinService#closeInactiveUIs(VaadinSession)
- *
- * @return The time the last heartbeat request occurred, in milliseconds
- * since the epoch.
- */
- public long getLastHeartbeatTimestamp() {
- return lastHeartbeatTimestamp;
- }
-
- /**
- * Sets the last heartbeat request timestamp for this UI. Called by the
- * framework whenever the application receives a valid heartbeat request for
- * this UI.
- * <p>
- * This method is not intended to be overridden. If it is overridden, care
- * should be taken since this method might be called in situations where
- * {@link UI#getCurrent()} does not return this UI.
- *
- * @param lastHeartbeat
- * The time the last heartbeat request occurred, in milliseconds
- * since the epoch.
- */
- public void setLastHeartbeatTimestamp(long lastHeartbeat) {
- lastHeartbeatTimestamp = lastHeartbeat;
- }
-
- /**
- * Gets the theme currently in use by this UI.
- *
- * @return the theme name
- */
- public String getTheme() {
- return getState(false).theme;
- }
-
- /**
- * Sets the theme currently in use by this UI
- * <p>
- * Calling this method will remove the old theme (CSS file) from the
- * application and add the new theme.
- * <p>
- * Note that this method is NOT SAFE to call in a portal environment or
- * other environment where there are multiple UIs on the same page. The old
- * CSS file will be removed even if there are other UIs on the page which
- * are still using it.
- *
- * @since 7.3
- * @param theme
- * The new theme name
- */
- public void setTheme(String theme) {
- if (theme == null) {
- getState().theme = null;
- } else {
- getState().theme = VaadinServlet.stripSpecialChars(theme);
- }
- }
-
- /**
- * Marks this UI to be {@link #detach() detached} from the session at the
- * end of the current request, or the next request if there is no current
- * request (if called from a background thread, for instance.)
- * <p>
- * The UI is detached after the response is sent, so in the current request
- * it can still update the client side normally. However, after the response
- * any new requests from the client side to this UI will cause an error, so
- * usually the client should be asked, for instance, to reload the page
- * (serving a fresh UI instance), to close the page, or to navigate
- * somewhere else.
- * <p>
- * Note that this method is strictly for users to explicitly signal the
- * framework that the UI should be detached. Overriding it is not a reliable
- * way to catch UIs that are to be detached. Instead, {@code UI.detach()}
- * should be overridden or a {@link DetachListener} used.
- */
- public void close() {
- closing = true;
-
- boolean sessionExpired = (session == null
- || session.getState() != State.OPEN);
- getRpcProxy(UIClientRpc.class).uiClosed(sessionExpired);
- if (getPushConnection() != null) {
- // Push the Rpc to the client. The connection will be closed when
- // the UI is detached and cleaned up.
-
- // Can't use UI.push() directly since it checks for a valid session
- if (session != null) {
- session.getService().runPendingAccessTasks(session);
- }
- getPushConnection().push();
- }
-
- }
-
- /**
- * Returns whether this UI is marked as closed and is to be detached.
- * <p>
- * This method is not intended to be overridden. If it is overridden, care
- * should be taken since this method might be called in situations where
- * {@link UI#getCurrent()} does not return this UI.
- *
- * @see #close()
- *
- * @return whether this UI is closing.
- */
- public boolean isClosing() {
- return closing;
- }
-
- /**
- * Called after the UI is added to the session. A UI instance is attached
- * exactly once, before its {@link #init(VaadinRequest) init} method is
- * called.
- *
- * @see Component#attach
- */
- @Override
- public void attach() {
- super.attach();
- getLocaleService().addLocale(getLocale());
- }
-
- /**
- * Called before the UI is removed from the session. A UI instance is
- * detached exactly once, either:
- * <ul>
- * <li>after it is explicitly {@link #close() closed}.
- * <li>when its session is closed or expires
- * <li>after three missed heartbeat requests.
- * </ul>
- * <p>
- * Note that when a UI is detached, any changes made in the {@code detach}
- * methods of any children or {@link DetachListener}s that would be
- * communicated to the client are silently ignored.
- */
- @Override
- public void detach() {
- super.detach();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.vaadin.ui.AbstractSingleComponentContainer#setContent(com.vaadin.
- * ui.Component)
- */
- @Override
- public void setContent(Component content) {
- if (content instanceof Window) {
- throw new IllegalArgumentException(
- "A Window cannot be added using setContent. Use addWindow(Window window) instead");
- }
- super.setContent(content);
- }
-
- @Override
- public void setTabIndex(int tabIndex) {
- getState().tabIndex = tabIndex;
- }
-
- @Override
- public int getTabIndex() {
- return getState(false).tabIndex;
- }
-
- /**
- * Locks the session of this UI and runs the provided Runnable right away.
- * <p>
- * It is generally recommended to use {@link #access(Runnable)} instead of
- * this method for accessing a session from a different thread as
- * {@link #access(Runnable)} can be used while holding the lock of another
- * session. To avoid causing deadlocks, this methods throws an exception if
- * it is detected than another session is also locked by the current thread.
- * </p>
- * <p>
- * This method behaves differently than {@link #access(Runnable)} in some
- * situations:
- * <ul>
- * <li>If the current thread is currently holding the lock of the session,
- * {@link #accessSynchronously(Runnable)} runs the task right away whereas
- * {@link #access(Runnable)} defers the task to a later point in time.</li>
- * <li>If some other thread is currently holding the lock for the session,
- * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock
- * to be available whereas {@link #access(Runnable)} defers the task to a
- * later point in time.</li>
- * </ul>
- * </p>
- *
- * @since 7.1
- *
- * @param runnable
- * the runnable which accesses the UI
- * @throws UIDetachedException
- * if the UI is not attached to a session (and locking can
- * therefore not be done)
- * @throws IllegalStateException
- * if the current thread holds the lock for another session
- *
- * @see #access(Runnable)
- * @see VaadinSession#accessSynchronously(Runnable)
- */
- public void accessSynchronously(Runnable runnable)
- throws UIDetachedException {
- Map<Class<?>, CurrentInstance> old = null;
-
- VaadinSession session = getSession();
-
- if (session == null) {
- throw new UIDetachedException();
- }
-
- VaadinService.verifyNoOtherSessionLocked(session);
-
- session.lock();
- try {
- if (getSession() == null) {
- // UI was detached after fetching the session but before we
- // acquired the lock.
- throw new UIDetachedException();
- }
- old = CurrentInstance.setCurrent(this);
- runnable.run();
- } finally {
- session.unlock();
- if (old != null) {
- CurrentInstance.restoreInstances(old);
- }
- }
-
- }
-
- /**
- * Provides exclusive access to this UI from outside a request handling
- * thread.
- * <p>
- * The given runnable is executed while holding the session lock to ensure
- * exclusive access to this UI. If the session is not locked, the lock will
- * be acquired and the runnable is run right away. If the session is
- * currently locked, the runnable will be run before that lock is released.
- * </p>
- * <p>
- * RPC handlers for components inside this UI do not need to use this method
- * as the session is automatically locked by the framework during RPC
- * handling.
- * </p>
- * <p>
- * Please note that the runnable might be invoked on a different thread or
- * later on the current thread, which means that custom thread locals might
- * not have the expected values when the command is executed.
- * {@link UI#getCurrent()}, {@link VaadinSession#getCurrent()} and
- * {@link VaadinService#getCurrent()} are set according to this UI before
- * executing the command. Other standard CurrentInstance values such as
- * {@link VaadinService#getCurrentRequest()} and
- * {@link VaadinService#getCurrentResponse()} will not be defined.
- * </p>
- * <p>
- * The returned future can be used to check for task completion and to
- * cancel the task.
- * </p>
- *
- * @see #getCurrent()
- * @see #accessSynchronously(Runnable)
- * @see VaadinSession#access(Runnable)
- * @see VaadinSession#lock()
- *
- * @since 7.1
- *
- * @param runnable
- * the runnable which accesses the UI
- * @throws UIDetachedException
- * if the UI is not attached to a session (and locking can
- * therefore not be done)
- * @return a future that can be used to check for task completion and to
- * cancel the task
- */
- public Future<Void> access(final Runnable runnable) {
- VaadinSession session = getSession();
-
- if (session == null) {
- throw new UIDetachedException();
- }
-
- return session.access(new ErrorHandlingRunnable() {
- @Override
- public void run() {
- accessSynchronously(runnable);
- }
-
- @Override
- public void handleError(Exception exception) {
- try {
- exception = ErrorHandlingRunnable.processException(runnable,
- exception);
-
- if (exception instanceof UIDetachedException) {
- assert session != null;
- /*
- * UI was detached after access was run, but before
- * accessSynchronously. Furthermore, there wasn't an
- * ErrorHandlingRunnable that handled the exception.
- */
- getLogger().log(Level.WARNING,
- "access() task ignored because UI got detached after the task was enqueued."
- + " To suppress this message, change the task to implement {} and make it handle {}."
- + " Affected task: {}",
- new Object[] {
- ErrorHandlingRunnable.class.getName(),
- UIDetachedException.class.getName(),
- runnable });
- } else if (exception != null) {
- /*
- * If no ErrorHandlingRunnable, or if it threw an
- * exception of its own.
- */
- ConnectorErrorEvent errorEvent = new ConnectorErrorEvent(
- UI.this, exception);
-
- ErrorHandler errorHandler = com.vaadin.server.ErrorEvent
- .findErrorHandler(UI.this);
-
- if (errorHandler == null && getSession() == null) {
- /*
- * Special case where findErrorHandler(UI) cannot
- * find the session handler because the UI has
- * recently been detached.
- */
- errorHandler = com.vaadin.server.ErrorEvent
- .findErrorHandler(session);
- }
-
- if (errorHandler == null) {
- errorHandler = new DefaultErrorHandler();
- }
-
- errorHandler.error(errorEvent);
- }
- } catch (Exception e) {
- getLogger().log(Level.SEVERE, e.getMessage(), e);
- }
- }
- });
- }
-
- /**
- * Retrieves the object used for configuring tooltips.
- *
- * @return The instance used for tooltip configuration
- */
- public TooltipConfiguration getTooltipConfiguration() {
- return tooltipConfiguration;
- }
-
- /**
- * Retrieves the object used for configuring notifications.
- *
- * @return The instance used for notification configuration
- */
- public NotificationConfiguration getNotificationConfiguration() {
- return notificationConfiguration;
- }
-
- /**
- * Retrieves the object used for configuring the loading indicator.
- *
- * @return The instance used for configuring the loading indicator
- */
- public LoadingIndicatorConfiguration getLoadingIndicatorConfiguration() {
- return loadingIndicatorConfiguration;
- }
-
- /**
- * Pushes the pending changes and client RPC invocations of this UI to the
- * client-side.
- * <p>
- * If push is enabled, but the push connection is not currently open, the
- * push will be done when the connection is established.
- * <p>
- * As with all UI methods, the session must be locked when calling this
- * method. It is also recommended that {@link UI#getCurrent()} is set up to
- * return this UI since writing the response may invoke logic in any
- * attached component or extension. The recommended way of fulfilling these
- * conditions is to use {@link #access(Runnable)}.
- *
- * @throws IllegalStateException
- * if push is disabled.
- * @throws UIDetachedException
- * if this UI is not attached to a session.
- *
- * @see #getPushConfiguration()
- *
- * @since 7.1
- */
- public void push() {
- VaadinSession session = getSession();
-
- if (session == null) {
- throw new UIDetachedException("Cannot push a detached UI");
- }
- assert session.hasLock();
-
- if (!getPushConfiguration().getPushMode().isEnabled()) {
- throw new IllegalStateException("Push not enabled");
- }
- assert pushConnection != null;
-
- /*
- * Purge the pending access queue as it might mark a connector as dirty
- * when the push would otherwise be ignored because there are no changes
- * to push.
- */
- session.getService().runPendingAccessTasks(session);
-
- if (!getConnectorTracker().hasDirtyConnectors()) {
- // Do not push if there is nothing to push
- return;
- }
-
- pushConnection.push();
- }
-
- /**
- * Returns the internal push connection object used by this UI. This method
- * should only be called by the framework.
- * <p>
- * This method is not intended to be overridden. If it is overridden, care
- * should be taken since this method might be called in situations where
- * {@link UI#getCurrent()} does not return this UI.
- *
- * @return the push connection used by this UI, or {@code null} if push is
- * not available.
- */
- public PushConnection getPushConnection() {
- assert !(getPushConfiguration().getPushMode().isEnabled()
- && pushConnection == null);
- return pushConnection;
- }
-
- /**
- * Sets the internal push connection object used by this UI. This method
- * should only be called by the framework.
- * <p>
- * The {@code pushConnection} argument must be non-null if and only if
- * {@code getPushConfiguration().getPushMode().isEnabled()}.
- *
- * @param pushConnection
- * the push connection to use for this UI
- */
- public void setPushConnection(PushConnection pushConnection) {
- // If pushMode is disabled then there should never be a pushConnection;
- // if enabled there should always be
- assert (pushConnection == null)
- ^ getPushConfiguration().getPushMode().isEnabled();
-
- if (pushConnection == this.pushConnection) {
- return;
- }
-
- if (this.pushConnection != null && this.pushConnection.isConnected()) {
- this.pushConnection.disconnect();
- }
-
- this.pushConnection = pushConnection;
- }
-
- /**
- * Sets the interval with which the UI should poll the server to see if
- * there are any changes. Polling is disabled by default.
- * <p>
- * Note that it is possible to enable push and polling at the same time but
- * it should not be done to avoid excessive server traffic.
- * </p>
- * <p>
- * Add-on developers should note that this method is only meant for the
- * application developer. An add-on should not set the poll interval
- * directly, rather instruct the user to set it.
- * </p>
- *
- * @param intervalInMillis
- * The interval (in ms) with which the UI should poll the server
- * or -1 to disable polling
- */
- public void setPollInterval(int intervalInMillis) {
- getState().pollInterval = intervalInMillis;
- }
-
- /**
- * Returns the interval with which the UI polls the server.
- *
- * @return The interval (in ms) with which the UI polls the server or -1 if
- * polling is disabled
- */
- public int getPollInterval() {
- return getState(false).pollInterval;
- }
-
- @Override
- public Registration addPollListener(PollListener listener) {
- return addListener(EventId.POLL, PollEvent.class, listener,
- PollListener.POLL_METHOD);
- }
-
- @Override
- @Deprecated
- public void removePollListener(PollListener listener) {
- removeListener(EventId.POLL, PollEvent.class, listener);
- }
-
- /**
- * Retrieves the object used for configuring the push channel.
- *
- * @since 7.1
- * @return The instance used for push configuration
- */
- public PushConfiguration getPushConfiguration() {
- return pushConfiguration;
- }
-
- /**
- * Retrieves the object used for configuring the reconnect dialog.
- *
- * @since 7.6
- * @return The instance used for reconnect dialog configuration
- */
- public ReconnectDialogConfiguration getReconnectDialogConfiguration() {
- return reconnectDialogConfiguration;
- }
-
- /**
- * Get the label that is added to the container element, where tooltip,
- * notification and dialogs are added to.
- *
- * @return the label of the container
- */
- public String getOverlayContainerLabel() {
- return getState(false).overlayContainerLabel;
- }
-
- /**
- * Sets the label that is added to the container element, where tooltip,
- * notifications and dialogs are added to.
- * <p>
- * This is helpful for users of assistive devices, as this element is
- * reachable for them.
- * </p>
- *
- * @param overlayContainerLabel
- * label to use for the container
- */
- public void setOverlayContainerLabel(String overlayContainerLabel) {
- getState().overlayContainerLabel = overlayContainerLabel;
- }
-
- /**
- * Returns the locale service which handles transmission of Locale data to
- * the client.
- *
- * @since 7.1
- * @return The LocaleService for this UI
- */
- public LocaleService getLocaleService() {
- return localeService;
- }
-
- private static Logger getLogger() {
- return Logger.getLogger(UI.class.getName());
- }
-
- /**
- * Gets a string the uniquely distinguishes this UI instance based on where
- * it is embedded. The embed identifier is based on the
- * <code>window.name</code> DOM attribute of the browser window where the UI
- * is displayed and the id of the div element where the UI is embedded.
- *
- * @since 7.2
- * @return the embed id for this UI, or <code>null</code> if no id known
- */
- public String getEmbedId() {
- return embedId;
- }
-
- /**
- * Gets the last processed server message id.
- *
- * Used internally for communication tracking.
- *
- * @return lastProcessedServerMessageId the id of the last processed server
- * message
- * @since 7.6
- */
- public int getLastProcessedClientToServerId() {
- return lastProcessedClientToServerId;
- }
-
- /**
- * Sets the last processed server message id.
- *
- * Used internally for communication tracking.
- *
- * @param lastProcessedClientToServerId
- * the id of the last processed server message
- * @since 7.6
- */
- public void setLastProcessedClientToServerId(
- int lastProcessedClientToServerId) {
- this.lastProcessedClientToServerId = lastProcessedClientToServerId;
- }
-
- /**
- * Adds a WindowOrderUpdateListener to the UI.
- * <p>
- * The WindowOrderUpdateEvent is fired when the order positions of windows
- * are updated. It can happen when some window (this or other) is brought to
- * front or detached.
- * <p>
- * The other way to listen window position for specific window is
- * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)}
- *
- * @see Window#addWindowOrderChangeListener(WindowOrderChangeListener)
- *
- * @param listener
- * the WindowModeChangeListener to add.
- * @since 8.0
- *
- * @return a registration object for removing the listener
- */
- public Registration addWindowOrderUpdateListener(
- WindowOrderUpdateListener listener) {
- addListener(EventId.WINDOW_ORDER, WindowOrderUpdateEvent.class,
- listener, WindowOrderUpdateListener.windowOrderUpdateMethod);
- return () -> removeListener(EventId.WINDOW_ORDER,
- WindowOrderUpdateEvent.class, listener);
- }
-
- /**
- * Sets the drag source of an active HTML5 drag event.
- *
- * @param extension
- * Extension of the drag source component.
- * @see DragSourceExtension
- * @since 8.1
- */
- public void setActiveDragSource(
- DragSourceExtension<? extends AbstractComponent> extension) {
- activeDragSource = extension;
- }
-
- /**
- * Gets the drag source of an active HTML5 drag event.
- *
- * @return Extension of the drag source component if the drag event is
- * active and originated from this UI, {@literal null} otherwise.
- * @see DragSourceExtension
- * @since 8.1
- */
- public DragSourceExtension<? extends AbstractComponent> getActiveDragSource() {
- return activeDragSource;
- }
-
- /**
- * Returns whether HTML5 DnD extensions {@link DragSourceExtension} and
- * {@link DropTargetExtension} and alike should be enabled for mobile
- * devices.
- * <p>
- * By default, it is disabled.
- *
- * @return {@code true} if enabled, {@code false} if not
- * @since 8.1
- * @see #setMobileHtml5DndEnabled(boolean)
- */
- public boolean isMobileHtml5DndEnabled() {
- return getState(false).enableMobileHTML5DnD;
- }
-
- /**
- * Enable or disable HTML5 DnD for mobile devices.
- * <p>
- * Usually you should enable the support in the {@link #init(VaadinRequest)}
- * method. By default, it is disabled. This operation is NOOP when the user
- * is not on a mobile device.
- * <p>
- * Changing this will effect all {@link DragSourceExtension} and
- * {@link DropTargetExtension} (and subclasses) that have not yet been
- * attached to the UI on the client side.
- * <p>
- * <em>NOTE: When disabling this after it has been enabled, it will not
- * affect {@link DragSourceExtension} and {@link DropTargetExtension} (and
- * subclasses) that have been previously added. Those extensions should be
- * explicitly removed to make sure user cannot perform DnD operations
- * anymore.</em>
- *
- * @param enabled
- * {@code true} if enabled, {@code false} if not
- * @since 8.1
- */
- public void setMobileHtml5DndEnabled(boolean enabled) {
- if (getState(false).enableMobileHTML5DnD != enabled) {
- getState().enableMobileHTML5DnD = enabled;
-
- if (isMobileHtml5DndEnabled()) {
- loadMobileHtml5DndPolyfill();
- }
- }
- }
-
- /**
- * Load and initialize the mobile drag-drop-polyfill if needed and not yet
- * done so.
- */
- private void loadMobileHtml5DndPolyfill() {
- if (mobileHtml5DndPolyfillLoaded) {
- return;
- }
- if (!getPage().getWebBrowser().isTouchDevice()) {
- return;
- }
- mobileHtml5DndPolyfillLoaded = true;
-
- String vaadinLocation = getSession().getService().getStaticFileLocation(
- VaadinService.getCurrentRequest()) + "/VAADIN/";
-
- getPage().addDependency(new Dependency(Type.JAVASCRIPT,
- vaadinLocation + ApplicationConstants.MOBILE_DND_POLYFILL_JS));
-
- getRpcProxy(PageClientRpc.class).initializeMobileHtml5DndPolyfill();
- }
-
- /**
- * Event which is fired when the ordering of the windows is updated.
- * <p>
- * The other way to listen window position for specific window is
- * {@link Window#addWindowOrderChangeListener(WindowOrderChangeListener)}
- *
- * @see Window.WindowOrderChangeEvent
- *
- * @author Vaadin Ltd
- * @since 8.0
- *
- */
- public static class WindowOrderUpdateEvent extends Component.Event {
-
- private final Collection<Window> windows;
-
- public WindowOrderUpdateEvent(Component source,
- Collection<Window> windows) {
- super(source);
- this.windows = windows;
- }
-
- /**
- * Gets the windows in the order they appear in the UI: top most window
- * is first, bottom one last.
- *
- * @return the windows collection
- */
- public Collection<Window> getWindows() {
- return windows;
- }
- }
-
- /**
- * An interface used for listening to Windows order update events.
- *
- * @since 8.0
- *
- * @see Window.WindowOrderChangeEvent
- */
- @FunctionalInterface
- public interface WindowOrderUpdateListener extends ConnectorEventListener {
-
- public static final Method windowOrderUpdateMethod = ReflectTools
- .findMethod(WindowOrderUpdateListener.class,
- "windowOrderUpdated", WindowOrderUpdateEvent.class);
-
- /**
- * Called when the windows order positions are changed. Use
- * {@link WindowOrderUpdateEvent#getWindows()} to get a reference to the
- * {@link Window}s whose order positions are updated. Use
- * {@link Window#getOrderPosition()} to get window position for specific
- * window.
- *
- * @param event
- */
- public void windowOrderUpdated(WindowOrderUpdateEvent event);
- }
- }
|