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

ApplicationConnection.java 72KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.Date;
  7. import java.util.HashMap;
  8. import java.util.HashSet;
  9. import java.util.Iterator;
  10. import java.util.Map;
  11. import java.util.Set;
  12. import com.google.gwt.core.client.Duration;
  13. import com.google.gwt.core.client.GWT;
  14. import com.google.gwt.core.client.JavaScriptObject;
  15. import com.google.gwt.core.client.JsArray;
  16. import com.google.gwt.core.client.JsArrayString;
  17. import com.google.gwt.core.client.Scheduler;
  18. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  19. import com.google.gwt.http.client.Request;
  20. import com.google.gwt.http.client.RequestBuilder;
  21. import com.google.gwt.http.client.RequestCallback;
  22. import com.google.gwt.http.client.RequestException;
  23. import com.google.gwt.http.client.Response;
  24. import com.google.gwt.json.client.JSONArray;
  25. import com.google.gwt.json.client.JSONString;
  26. import com.google.gwt.regexp.shared.MatchResult;
  27. import com.google.gwt.regexp.shared.RegExp;
  28. import com.google.gwt.user.client.Command;
  29. import com.google.gwt.user.client.DOM;
  30. import com.google.gwt.user.client.Element;
  31. import com.google.gwt.user.client.Event;
  32. import com.google.gwt.user.client.Timer;
  33. import com.google.gwt.user.client.ui.HasWidgets;
  34. import com.google.gwt.user.client.ui.Widget;
  35. import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage;
  36. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  37. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  38. import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
  39. import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
  40. import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
  41. import com.vaadin.terminal.gwt.client.communication.SharedState;
  42. import com.vaadin.terminal.gwt.client.ui.VContextMenu;
  43. import com.vaadin.terminal.gwt.client.ui.VNotification;
  44. import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent;
  45. import com.vaadin.terminal.gwt.client.ui.VViewPaintable;
  46. import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
  47. import com.vaadin.terminal.gwt.server.AbstractCommunicationManager;
  48. /**
  49. * This is the client side communication "engine", managing client-server
  50. * communication with its server side counterpart
  51. * {@link AbstractCommunicationManager}.
  52. *
  53. * Client-side widgets receive updates from the corresponding server-side
  54. * components as calls to
  55. * {@link ComponentConnector#updateFromUIDL(UIDL, ApplicationConnection)} (not to
  56. * be confused with the server side interface
  57. * {@link com.vaadin.terminal.Paintable} ). Any client-side changes (typically
  58. * resulting from user actions) are sent back to the server as variable changes
  59. * (see {@link #updateVariable()}).
  60. *
  61. * TODO document better
  62. *
  63. * Entry point classes (widgetsets) define <code>onModuleLoad()</code>.
  64. */
  65. public class ApplicationConnection {
  66. // This indicates the whole page is generated by us (not embedded)
  67. public static final String GENERATED_BODY_CLASSNAME = "v-generated-body";
  68. public static final String MODIFIED_CLASSNAME = "v-modified";
  69. public static final String DISABLED_CLASSNAME = "v-disabled";
  70. public static final String REQUIRED_CLASSNAME_EXT = "-required";
  71. public static final String ERROR_CLASSNAME_EXT = "-error";
  72. public static final String UPDATE_VARIABLE_INTERFACE = "v";
  73. public static final String UPDATE_VARIABLE_METHOD = "v";
  74. public static final char VAR_BURST_SEPARATOR = '\u001d';
  75. public static final char VAR_ESCAPE_CHARACTER = '\u001b';
  76. public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
  77. /**
  78. * Name of the parameter used to transmit root ids back and forth
  79. */
  80. public static final String ROOT_ID_PARAMETER = "rootId";
  81. /**
  82. * @deprecated use UIDL_SECURITY_TOKEN_ID instead
  83. */
  84. @Deprecated
  85. public static final String UIDL_SECURITY_HEADER = UIDL_SECURITY_TOKEN_ID;
  86. public static final String PARAM_UNLOADBURST = "onunloadburst";
  87. /**
  88. * A string that, if found in a non-JSON response to a UIDL request, will
  89. * cause the browser to refresh the page. If followed by a colon, optional
  90. * whitespace, and a URI, causes the browser to synchronously load the URI.
  91. *
  92. * <p>
  93. * This allows, for instance, a servlet filter to redirect the application
  94. * to a custom login page when the session expires. For example:
  95. * </p>
  96. *
  97. * <pre>
  98. * if (sessionExpired) {
  99. * response.setHeader(&quot;Content-Type&quot;, &quot;text/html&quot;);
  100. * response.getWriter().write(
  101. * myLoginPageHtml + &quot;&lt;!-- Vaadin-Refresh: &quot;
  102. * + request.getContextPath() + &quot; --&gt;&quot;);
  103. * }
  104. * </pre>
  105. */
  106. public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh";
  107. // will hold the UIDL security key (for XSS protection) once received
  108. private String uidlSecurityKey = "init";
  109. private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
  110. private ArrayList<MethodInvocation> pendingInvocations = new ArrayList<MethodInvocation>();
  111. private WidgetSet widgetSet;
  112. private VContextMenu contextMenu = null;
  113. private Timer loadTimer;
  114. private Timer loadTimer2;
  115. private Timer loadTimer3;
  116. private Element loadElement;
  117. private final VViewPaintable view;
  118. protected boolean applicationRunning = false;
  119. private int activeRequests = 0;
  120. protected boolean cssLoaded = false;
  121. /** Parameters for this application connection loaded from the web-page */
  122. private ApplicationConfiguration configuration;
  123. /** List of pending variable change bursts that must be submitted in order */
  124. private final ArrayList<ArrayList<MethodInvocation>> pendingBursts = new ArrayList<ArrayList<MethodInvocation>>();
  125. /** Timer for automatic refirect to SessionExpiredURL */
  126. private Timer redirectTimer;
  127. /** redirectTimer scheduling interval in seconds */
  128. private int sessionExpirationInterval;
  129. private ArrayList<Widget> componentCaptionSizeChanges = new ArrayList<Widget>();
  130. private Date requestStartTime;
  131. private boolean validatingLayouts = false;
  132. private Set<ComponentConnector> zeroWidthComponents = null;
  133. private Set<ComponentConnector> zeroHeightComponents = null;
  134. private final LayoutManager layoutManager = new LayoutManager(this);
  135. public ApplicationConnection() {
  136. view = GWT.create(VViewPaintable.class);
  137. view.setConnection(this);
  138. }
  139. public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) {
  140. VConsole.log("Starting application " + cnf.getRootPanelId());
  141. VConsole.log("Vaadin application servlet version: "
  142. + cnf.getServletVersion());
  143. VConsole.log("Application version: " + cnf.getApplicationVersion());
  144. if (!cnf.getServletVersion().equals(ApplicationConfiguration.VERSION)) {
  145. VConsole.error("Warning: your widget set seems to be built with a different "
  146. + "version than the one used on server. Unexpected "
  147. + "behavior may occur.");
  148. }
  149. this.widgetSet = widgetSet;
  150. configuration = cnf;
  151. ComponentLocator componentLocator = new ComponentLocator(this);
  152. String appRootPanelName = cnf.getRootPanelId();
  153. // remove the end (window name) of autogenerated rootpanel id
  154. appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", "");
  155. initializeTestbenchHooks(componentLocator, appRootPanelName);
  156. initializeClientHooks();
  157. view.init(cnf.getRootPanelId(), this);
  158. showLoadingIndicator();
  159. }
  160. /**
  161. * Starts this application. Don't call this method directly - it's called by
  162. * {@link ApplicationConfiguration#startNextApplication()}, which should be
  163. * called once this application has started (first response received) or
  164. * failed to start. This ensures that the applications are started in order,
  165. * to avoid session-id problems.
  166. *
  167. */
  168. public void start() {
  169. String jsonText = configuration.getUIDL();
  170. if (jsonText == null) {
  171. // inital UIDL not in DOM, request later
  172. repaintAll();
  173. } else {
  174. // Update counter so TestBench knows something is still going on
  175. incrementActiveRequests();
  176. // initial UIDL provided in DOM, continue as if returned by request
  177. handleJSONText(jsonText);
  178. }
  179. }
  180. private native void initializeTestbenchHooks(
  181. ComponentLocator componentLocator, String TTAppId)
  182. /*-{
  183. var ap = this;
  184. var client = {};
  185. client.isActive = function() {
  186. return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()()
  187. || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isExecutingDeferredCommands()();
  188. }
  189. var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
  190. if (vi) {
  191. client.getVersionInfo = function() {
  192. return vi;
  193. }
  194. }
  195. client.getElementByPath = function(id) {
  196. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
  197. }
  198. client.getPathForElement = function(element) {
  199. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
  200. }
  201. $wnd.vaadin.clients[TTAppId] = client;
  202. }-*/;
  203. /**
  204. * Helper for tt initialization
  205. */
  206. private JavaScriptObject getVersionInfo() {
  207. return configuration.getVersionInfoJSObject();
  208. }
  209. /**
  210. * Publishes a JavaScript API for mash-up applications.
  211. * <ul>
  212. * <li><code>vaadin.forceSync()</code> sends pending variable changes, in
  213. * effect synchronizing the server and client state. This is done for all
  214. * applications on host page.</li>
  215. * <li><code>vaadin.postRequestHooks</code> is a map of functions which gets
  216. * called after each XHR made by vaadin application. Note, that it is
  217. * attaching js functions responsibility to create the variable like this:
  218. *
  219. * <code><pre>
  220. * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
  221. * postRequestHooks.myHook = function(appId) {
  222. * if(appId == "MyAppOfInterest") {
  223. * // do the staff you need on xhr activity
  224. * }
  225. * }
  226. * </pre></code> First parameter passed to these functions is the identifier
  227. * of Vaadin application that made the request.
  228. * </ul>
  229. *
  230. * TODO make this multi-app aware
  231. */
  232. private native void initializeClientHooks()
  233. /*-{
  234. var app = this;
  235. var oldSync;
  236. if ($wnd.vaadin.forceSync) {
  237. oldSync = $wnd.vaadin.forceSync;
  238. }
  239. $wnd.vaadin.forceSync = function() {
  240. if (oldSync) {
  241. oldSync();
  242. }
  243. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
  244. }
  245. var oldForceLayout;
  246. if ($wnd.vaadin.forceLayout) {
  247. oldForceLayout = $wnd.vaadin.forceLayout;
  248. }
  249. $wnd.vaadin.forceLayout = function() {
  250. if (oldForceLayout) {
  251. oldForceLayout();
  252. }
  253. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()();
  254. }
  255. }-*/;
  256. /**
  257. * Runs possibly registered client side post request hooks. This is expected
  258. * to be run after each uidl request made by Vaadin application.
  259. *
  260. * @param appId
  261. */
  262. private static native void runPostRequestHooks(String appId)
  263. /*-{
  264. if ($wnd.vaadin.postRequestHooks) {
  265. for ( var hook in $wnd.vaadin.postRequestHooks) {
  266. if (typeof ($wnd.vaadin.postRequestHooks[hook]) == "function") {
  267. try {
  268. $wnd.vaadin.postRequestHooks[hook](appId);
  269. } catch (e) {
  270. }
  271. }
  272. }
  273. }
  274. }-*/;
  275. /**
  276. * Get the active Console for writing debug messages. May return an actual
  277. * logging console, or the NullConsole if debugging is not turned on.
  278. *
  279. * @deprecated Developers should use {@link VConsole} since 6.4.5
  280. *
  281. * @return the active Console
  282. */
  283. @Deprecated
  284. public static Console getConsole() {
  285. return VConsole.getImplementation();
  286. }
  287. /**
  288. * Checks if client side is in debug mode. Practically this is invoked by
  289. * adding ?debug parameter to URI.
  290. *
  291. * @deprecated use ApplicationConfiguration isDebugMode instead.
  292. *
  293. * @return true if client side is currently been debugged
  294. */
  295. @Deprecated
  296. public static boolean isDebugMode() {
  297. return ApplicationConfiguration.isDebugMode();
  298. }
  299. /**
  300. * Gets the application base URI. Using this other than as the download
  301. * action URI can cause problems in Portlet 2.0 deployments.
  302. *
  303. * @return application base URI
  304. */
  305. public String getAppUri() {
  306. return configuration.getApplicationUri();
  307. };
  308. /**
  309. * Indicates whether or not there are currently active UIDL requests. Used
  310. * internally to squence requests properly, seldom needed in Widgets.
  311. *
  312. * @return true if there are active requests
  313. */
  314. public boolean hasActiveRequest() {
  315. return (activeRequests > 0);
  316. }
  317. public void incrementActiveRequests() {
  318. if (activeRequests < 0) {
  319. activeRequests = 1;
  320. } else {
  321. activeRequests++;
  322. }
  323. }
  324. public void decrementActiveRequests() {
  325. if (activeRequests > 0) {
  326. activeRequests--;
  327. }
  328. }
  329. private String getRepaintAllParameters() {
  330. // collect some client side data that will be sent to server on
  331. // initial uidl request
  332. String nativeBootstrapParameters = getNativeBrowserDetailsParameters(getConfiguration()
  333. .getRootPanelId());
  334. String widgetsetVersion = ApplicationConfiguration.VERSION;
  335. // TODO figure out how client and view size could be used better on
  336. // server. screen size can be accessed via Browser object, but other
  337. // values currently only via transaction listener.
  338. String parameters = "repaintAll=1&" + nativeBootstrapParameters
  339. + "&wsver=" + widgetsetVersion;
  340. return parameters;
  341. }
  342. /**
  343. * Gets the browser detail parameters that are sent by the bootstrap
  344. * javascript for two-request initialization.
  345. *
  346. * @param parentElementId
  347. * @return
  348. */
  349. private static native String getNativeBrowserDetailsParameters(
  350. String parentElementId)
  351. /*-{
  352. return $wnd.vaadin.getBrowserDetailsParameters(parentElementId);
  353. }-*/;
  354. protected void repaintAll() {
  355. String repainAllParameters = getRepaintAllParameters();
  356. makeUidlRequest("", repainAllParameters, false);
  357. }
  358. /**
  359. * Requests an analyze of layouts, to find inconsistencies. Exclusively used
  360. * for debugging during development.
  361. */
  362. public void analyzeLayouts() {
  363. String params = getRepaintAllParameters() + "&analyzeLayouts=1";
  364. makeUidlRequest("", params, false);
  365. }
  366. /**
  367. * Sends a request to the server to print details to console that will help
  368. * developer to locate component in the source code.
  369. *
  370. * @param paintable
  371. */
  372. void highlightComponent(ComponentConnector paintable) {
  373. String params = getRepaintAllParameters() + "&highlightComponent="
  374. + connectorMap.getConnectorId(paintable);
  375. makeUidlRequest("", params, false);
  376. }
  377. /**
  378. * Makes an UIDL request to the server.
  379. *
  380. * @param requestData
  381. * Data that is passed to the server.
  382. * @param extraParams
  383. * Parameters that are added as GET parameters to the url.
  384. * Contains key=value pairs joined by & characters or is empty if
  385. * no parameters should be added. Should not start with any
  386. * special character.
  387. * @param forceSync
  388. * true if the request should be synchronous, false otherwise
  389. */
  390. protected void makeUidlRequest(final String requestData,
  391. final String extraParams, final boolean forceSync) {
  392. startRequest();
  393. // Security: double cookie submission pattern
  394. final String payload = uidlSecurityKey + VAR_BURST_SEPARATOR
  395. + requestData;
  396. VConsole.log("Making UIDL Request with params: " + payload);
  397. String uri;
  398. if (configuration.usePortletURLs()) {
  399. uri = configuration.getPortletUidlURLBase();
  400. } else {
  401. uri = getAppUri() + "UIDL";
  402. }
  403. if (extraParams != null && extraParams.length() > 0) {
  404. uri = addGetParameters(uri, extraParams);
  405. }
  406. uri = addGetParameters(uri,
  407. ROOT_ID_PARAMETER + "=" + configuration.getRootId());
  408. doUidlRequest(uri, payload, forceSync);
  409. }
  410. /**
  411. * Sends an asynchronous or synchronous UIDL request to the server using the
  412. * given URI.
  413. *
  414. * @param uri
  415. * The URI to use for the request. May includes GET parameters
  416. * @param payload
  417. * The contents of the request to send
  418. * @param synchronous
  419. * true if the request should be synchronous, false otherwise
  420. */
  421. protected void doUidlRequest(final String uri, final String payload,
  422. final boolean synchronous) {
  423. if (!synchronous) {
  424. RequestCallback requestCallback = new RequestCallback() {
  425. public void onError(Request request, Throwable exception) {
  426. showCommunicationError(exception.getMessage());
  427. endRequest();
  428. }
  429. public void onResponseReceived(Request request,
  430. Response response) {
  431. VConsole.log("Server visit took "
  432. + String.valueOf((new Date()).getTime()
  433. - requestStartTime.getTime()) + "ms");
  434. int statusCode = response.getStatusCode();
  435. switch (statusCode) {
  436. case 0:
  437. showCommunicationError("Invalid status code 0 (server down?)");
  438. endRequest();
  439. return;
  440. case 401:
  441. /*
  442. * Authorization has failed. Could be that the session
  443. * has timed out and the container is redirecting to a
  444. * login page.
  445. */
  446. showAuthenticationError("");
  447. endRequest();
  448. return;
  449. case 503:
  450. // We'll assume msec instead of the usual seconds
  451. int delay = Integer.parseInt(response
  452. .getHeader("Retry-After"));
  453. VConsole.log("503, retrying in " + delay + "msec");
  454. (new Timer() {
  455. @Override
  456. public void run() {
  457. decrementActiveRequests();
  458. doUidlRequest(uri, payload, synchronous);
  459. }
  460. }).schedule(delay);
  461. return;
  462. }
  463. if ((statusCode / 100) == 4) {
  464. // Handle all 4xx errors the same way as (they are
  465. // all permanent errors)
  466. showCommunicationError("UIDL could not be read from server. Check servlets mappings. Error code: "
  467. + statusCode);
  468. endRequest();
  469. return;
  470. }
  471. String contentType = response.getHeader("Content-Type");
  472. if (contentType == null
  473. || !contentType.startsWith("application/json")) {
  474. /*
  475. * A servlet filter or equivalent may have intercepted
  476. * the request and served non-UIDL content (for
  477. * instance, a login page if the session has expired.)
  478. * If the response contains a magic substring, do a
  479. * synchronous refresh. See #8241.
  480. */
  481. MatchResult refreshToken = RegExp.compile(
  482. UIDL_REFRESH_TOKEN + "(:\\s*(.*?))?(\\s|$)")
  483. .exec(response.getText());
  484. if (refreshToken != null) {
  485. redirect(refreshToken.getGroup(2));
  486. return;
  487. }
  488. }
  489. // for(;;);[realjson]
  490. final String jsonText = response.getText().substring(9,
  491. response.getText().length() - 1);
  492. handleJSONText(jsonText);
  493. }
  494. };
  495. try {
  496. doAsyncUIDLRequest(uri, payload, requestCallback);
  497. } catch (RequestException e) {
  498. VConsole.error(e);
  499. endRequest();
  500. }
  501. } else {
  502. // Synchronized call, discarded response (leaving the page)
  503. SynchronousXHR syncXHR = (SynchronousXHR) SynchronousXHR.create();
  504. syncXHR.synchronousPost(uri + "&" + PARAM_UNLOADBURST + "=1",
  505. payload);
  506. /*
  507. * Although we are in theory leaving the page, the page may still
  508. * stay open. End request properly here too. See #3289
  509. */
  510. endRequest();
  511. }
  512. }
  513. /**
  514. * Handles received UIDL JSON text, parsing it, and passing it on to the
  515. * appropriate handlers, while logging timiing information.
  516. *
  517. * @param jsonText
  518. */
  519. private void handleJSONText(String jsonText) {
  520. final Date start = new Date();
  521. final ValueMap json;
  522. try {
  523. json = parseJSONResponse(jsonText);
  524. } catch (final Exception e) {
  525. endRequest();
  526. showCommunicationError(e.getMessage() + " - Original JSON-text:"
  527. + jsonText);
  528. return;
  529. }
  530. VConsole.log("JSON parsing took "
  531. + (new Date().getTime() - start.getTime()) + "ms");
  532. if (applicationRunning) {
  533. handleReceivedJSONMessage(start, jsonText, json);
  534. } else {
  535. applicationRunning = true;
  536. handleWhenCSSLoaded(jsonText, json);
  537. }
  538. }
  539. /**
  540. * Sends an asynchronous UIDL request to the server using the given URI.
  541. *
  542. * @param uri
  543. * The URI to use for the request. May includes GET parameters
  544. * @param payload
  545. * The contents of the request to send
  546. * @param requestCallback
  547. * The handler for the response
  548. * @throws RequestException
  549. * if the request could not be sent
  550. */
  551. protected void doAsyncUIDLRequest(String uri, String payload,
  552. RequestCallback requestCallback) throws RequestException {
  553. RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
  554. // TODO enable timeout
  555. // rb.setTimeoutMillis(timeoutMillis);
  556. rb.setHeader("Content-Type", "text/plain;charset=utf-8");
  557. rb.setRequestData(payload);
  558. rb.setCallback(requestCallback);
  559. rb.send();
  560. }
  561. int cssWaits = 0;
  562. static final int MAX_CSS_WAITS = 100;
  563. protected void handleWhenCSSLoaded(final String jsonText,
  564. final ValueMap json) {
  565. if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) {
  566. (new Timer() {
  567. @Override
  568. public void run() {
  569. handleWhenCSSLoaded(jsonText, json);
  570. }
  571. }).schedule(50);
  572. VConsole.log("Assuming CSS loading is not complete, "
  573. + "postponing render phase. "
  574. + "(.v-loading-indicator height == 0)");
  575. cssWaits++;
  576. } else {
  577. cssLoaded = true;
  578. handleReceivedJSONMessage(new Date(), jsonText, json);
  579. if (cssWaits >= MAX_CSS_WAITS) {
  580. VConsole.error("CSS files may have not loaded properly.");
  581. }
  582. }
  583. }
  584. /**
  585. * Checks whether or not the CSS is loaded. By default checks the size of
  586. * the loading indicator element.
  587. *
  588. * @return
  589. */
  590. protected boolean isCSSLoaded() {
  591. return cssLoaded
  592. || DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0;
  593. }
  594. /**
  595. * Shows the communication error notification.
  596. *
  597. * @param details
  598. * Optional details for debugging.
  599. */
  600. protected void showCommunicationError(String details) {
  601. VConsole.error("Communication error: " + details);
  602. ErrorMessage communicationError = configuration.getCommunicationError();
  603. showError(details, communicationError.getCaption(),
  604. communicationError.getMessage(), communicationError.getUrl());
  605. }
  606. /**
  607. * Shows the authentication error notification.
  608. *
  609. * @param details
  610. * Optional details for debugging.
  611. */
  612. protected void showAuthenticationError(String details) {
  613. VConsole.error("Authentication error: " + details);
  614. ErrorMessage authorizationError = configuration.getAuthorizationError();
  615. showError(details, authorizationError.getCaption(),
  616. authorizationError.getMessage(), authorizationError.getUrl());
  617. }
  618. /**
  619. * Shows the error notification.
  620. *
  621. * @param details
  622. * Optional details for debugging.
  623. */
  624. private void showError(String details, String caption, String message,
  625. String url) {
  626. StringBuilder html = new StringBuilder();
  627. if (caption != null) {
  628. html.append("<h1>");
  629. html.append(caption);
  630. html.append("</h1>");
  631. }
  632. if (message != null) {
  633. html.append("<p>");
  634. html.append(message);
  635. html.append("</p>");
  636. }
  637. if (html.length() > 0) {
  638. // Add error description
  639. html.append("<br/><p><I style=\"font-size:0.7em\">");
  640. html.append(details);
  641. html.append("</I></p>");
  642. VNotification n = VNotification.createNotification(1000 * 60 * 45);
  643. n.addEventListener(new NotificationRedirect(url));
  644. n.show(html.toString(), VNotification.CENTERED_TOP,
  645. VNotification.STYLE_SYSTEM);
  646. } else {
  647. redirect(url);
  648. }
  649. }
  650. protected void startRequest() {
  651. incrementActiveRequests();
  652. requestStartTime = new Date();
  653. // show initial throbber
  654. if (loadTimer == null) {
  655. loadTimer = new Timer() {
  656. @Override
  657. public void run() {
  658. /*
  659. * IE7 does not properly cancel the event with
  660. * loadTimer.cancel() so we have to check that we really
  661. * should make it visible
  662. */
  663. if (loadTimer != null) {
  664. showLoadingIndicator();
  665. }
  666. }
  667. };
  668. // First one kicks in at 300ms
  669. }
  670. loadTimer.schedule(300);
  671. }
  672. protected void endRequest() {
  673. if (applicationRunning) {
  674. checkForPendingVariableBursts();
  675. runPostRequestHooks(configuration.getRootPanelId());
  676. }
  677. decrementActiveRequests();
  678. // deferring to avoid flickering
  679. Scheduler.get().scheduleDeferred(new Command() {
  680. public void execute() {
  681. if (!hasActiveRequest()) {
  682. hideLoadingIndicator();
  683. }
  684. }
  685. });
  686. }
  687. /**
  688. * This method is called after applying uidl change set to application.
  689. *
  690. * It will clean current and queued variable change sets. And send next
  691. * change set if it exists.
  692. */
  693. private void checkForPendingVariableBursts() {
  694. cleanVariableBurst(pendingInvocations);
  695. if (pendingBursts.size() > 0) {
  696. for (Iterator<ArrayList<MethodInvocation>> iterator = pendingBursts
  697. .iterator(); iterator.hasNext();) {
  698. cleanVariableBurst(iterator.next());
  699. }
  700. ArrayList<MethodInvocation> nextBurst = pendingBursts.get(0);
  701. pendingBursts.remove(0);
  702. buildAndSendVariableBurst(nextBurst, false);
  703. }
  704. }
  705. /**
  706. * Cleans given queue of variable changes of such changes that came from
  707. * components that do not exist anymore.
  708. *
  709. * @param variableBurst
  710. */
  711. private void cleanVariableBurst(ArrayList<MethodInvocation> variableBurst) {
  712. for (int i = 1; i < variableBurst.size(); i++) {
  713. String id = variableBurst.get(i).getPaintableId();
  714. if (!getConnectorMap().hasConnector(id)
  715. && !getConnectorMap().isDragAndDropPaintable(id)) {
  716. // variable owner does not exist anymore
  717. variableBurst.remove(i);
  718. VConsole.log("Removed variable from removed component: " + id);
  719. }
  720. }
  721. }
  722. private void showLoadingIndicator() {
  723. // show initial throbber
  724. if (loadElement == null) {
  725. loadElement = DOM.createDiv();
  726. DOM.setStyleAttribute(loadElement, "position", "absolute");
  727. DOM.appendChild(view.getWidget().getElement(),
  728. loadElement);
  729. VConsole.log("inserting load indicator");
  730. }
  731. DOM.setElementProperty(loadElement, "className", "v-loading-indicator");
  732. DOM.setStyleAttribute(loadElement, "display", "block");
  733. // Initialize other timers
  734. loadTimer2 = new Timer() {
  735. @Override
  736. public void run() {
  737. DOM.setElementProperty(loadElement, "className",
  738. "v-loading-indicator-delay");
  739. }
  740. };
  741. // Second one kicks in at 1500ms from request start
  742. loadTimer2.schedule(1200);
  743. loadTimer3 = new Timer() {
  744. @Override
  745. public void run() {
  746. DOM.setElementProperty(loadElement, "className",
  747. "v-loading-indicator-wait");
  748. }
  749. };
  750. // Third one kicks in at 5000ms from request start
  751. loadTimer3.schedule(4700);
  752. }
  753. private void hideLoadingIndicator() {
  754. if (loadTimer != null) {
  755. loadTimer.cancel();
  756. loadTimer = null;
  757. }
  758. if (loadTimer2 != null) {
  759. loadTimer2.cancel();
  760. loadTimer3.cancel();
  761. loadTimer2 = null;
  762. loadTimer3 = null;
  763. }
  764. if (loadElement != null) {
  765. DOM.setStyleAttribute(loadElement, "display", "none");
  766. }
  767. }
  768. /**
  769. * Checks if deferred commands are (potentially) still being executed as a
  770. * result of an update from the server. Returns true if a deferred command
  771. * might still be executing, false otherwise. This will not work correctly
  772. * if a deferred command is added in another deferred command.
  773. * <p>
  774. * Used by the native "client.isActive" function.
  775. * </p>
  776. *
  777. * @return true if deferred commands are (potentially) being executed, false
  778. * otherwise
  779. */
  780. private boolean isExecutingDeferredCommands() {
  781. Scheduler s = Scheduler.get();
  782. if (s instanceof VSchedulerImpl) {
  783. return ((VSchedulerImpl) s).hasWorkQueued();
  784. } else {
  785. return false;
  786. }
  787. }
  788. /**
  789. * Determines whether or not the loading indicator is showing.
  790. *
  791. * @return true if the loading indicator is visible
  792. */
  793. public boolean isLoadingIndicatorVisible() {
  794. if (loadElement == null) {
  795. return false;
  796. }
  797. if (loadElement.getStyle().getProperty("display").equals("none")) {
  798. return false;
  799. }
  800. return true;
  801. }
  802. private static native ValueMap parseJSONResponse(String jsonText)
  803. /*-{
  804. try {
  805. return JSON.parse(jsonText);
  806. } catch (ignored) {
  807. return eval('(' + jsonText + ')');
  808. }
  809. }-*/;
  810. private void handleReceivedJSONMessage(Date start, String jsonText,
  811. ValueMap json) {
  812. handleUIDLMessage(start, jsonText, json);
  813. }
  814. protected void handleUIDLMessage(final Date start, final String jsonText,
  815. final ValueMap json) {
  816. // Handle redirect
  817. if (json.containsKey("redirect")) {
  818. String url = json.getValueMap("redirect").getString("url");
  819. VConsole.log("redirecting to " + url);
  820. redirect(url);
  821. return;
  822. }
  823. // Get security key
  824. if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) {
  825. uidlSecurityKey = json.getString(UIDL_SECURITY_TOKEN_ID);
  826. }
  827. if (json.containsKey("resources")) {
  828. ValueMap resources = json.getValueMap("resources");
  829. JsArrayString keyArray = resources.getKeyArray();
  830. int l = keyArray.length();
  831. for (int i = 0; i < l; i++) {
  832. String key = keyArray.get(i);
  833. resourcesMap.put(key, resources.getAsString(key));
  834. }
  835. }
  836. if (json.containsKey("typeMappings")) {
  837. configuration.addComponentMappings(
  838. json.getValueMap("typeMappings"), widgetSet);
  839. }
  840. Command c = new Command() {
  841. public void execute() {
  842. VConsole.dirUIDL(json, configuration);
  843. if (json.containsKey("locales")) {
  844. // Store locale data
  845. JsArray<ValueMap> valueMapArray = json
  846. .getJSValueMapArray("locales");
  847. LocaleService.addLocales(valueMapArray);
  848. }
  849. boolean repaintAll = false;
  850. ValueMap meta = null;
  851. if (json.containsKey("meta")) {
  852. meta = json.getValueMap("meta");
  853. if (meta.containsKey("repaintAll")) {
  854. repaintAll = true;
  855. view.getWidget().clear();
  856. getConnectorMap().clear();
  857. if (meta.containsKey("invalidLayouts")) {
  858. validatingLayouts = true;
  859. zeroWidthComponents = new HashSet<ComponentConnector>();
  860. zeroHeightComponents = new HashSet<ComponentConnector>();
  861. }
  862. }
  863. if (meta.containsKey("timedRedirect")) {
  864. final ValueMap timedRedirect = meta
  865. .getValueMap("timedRedirect");
  866. redirectTimer = new Timer() {
  867. @Override
  868. public void run() {
  869. redirect(timedRedirect.getString("url"));
  870. }
  871. };
  872. sessionExpirationInterval = timedRedirect
  873. .getInt("interval");
  874. }
  875. }
  876. if (redirectTimer != null) {
  877. redirectTimer.schedule(1000 * sessionExpirationInterval);
  878. }
  879. // three phases/loops:
  880. // - changes: create paintables (if necessary)
  881. // - state: set shared states
  882. // - changes: call updateFromUIDL() for each paintable
  883. // Process changes
  884. JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
  885. ArrayList<ComponentConnector> updatedVPaintableWidgets = new ArrayList<ComponentConnector>();
  886. componentCaptionSizeChanges.clear();
  887. Duration updateDuration = new Duration();
  888. int length = changes.length();
  889. // create paintables if necessary
  890. for (int i = 0; i < length; i++) {
  891. try {
  892. final UIDL change = changes.get(i).cast();
  893. final UIDL uidl = change.getChildUIDL(0);
  894. Connector paintable = connectorMap.getConnector(uidl
  895. .getId());
  896. if (null == paintable
  897. && !uidl.getTag().equals(
  898. configuration.getEncodedWindowTag())) {
  899. // create, initialize and register the paintable
  900. getPaintable(uidl);
  901. }
  902. } catch (final Throwable e) {
  903. VConsole.error(e);
  904. }
  905. }
  906. // set states for all paintables mentioned in "state"
  907. ValueMap states = json.getValueMap("state");
  908. JsArrayString keyArray = states.getKeyArray();
  909. for (int i = 0; i < keyArray.length(); i++) {
  910. try {
  911. String paintableId = keyArray.get(i);
  912. Connector paintable = connectorMap
  913. .getConnector(paintableId);
  914. if (null != paintable) {
  915. JSONArray stateDataAndType = new JSONArray(
  916. states.getJavaScriptObject(paintableId));
  917. Object state = JsonDecoder.convertValue(
  918. stateDataAndType, connectorMap);
  919. paintable.setState((SharedState) state);
  920. }
  921. } catch (final Throwable e) {
  922. VConsole.error(e);
  923. }
  924. }
  925. // update paintables
  926. for (int i = 0; i < length; i++) {
  927. try {
  928. final UIDL change = changes.get(i).cast();
  929. final UIDL uidl = change.getChildUIDL(0);
  930. // TODO optimize
  931. final ComponentConnector paintable = (ComponentConnector) connectorMap
  932. .getConnector(uidl.getId());
  933. if (paintable != null) {
  934. paintable.updateFromUIDL(uidl,
  935. ApplicationConnection.this);
  936. updatedVPaintableWidgets.add(paintable);
  937. } else {
  938. if (!uidl.getTag().equals(
  939. configuration.getEncodedWindowTag())) {
  940. VConsole.error("Received update for "
  941. + uidl.getTag()
  942. + ", but there is no such paintable ("
  943. + uidl.getId() + ") rendered.");
  944. } else {
  945. String pid = uidl.getId();
  946. if (!connectorMap.hasConnector(pid)) {
  947. connectorMap.registerConnector(pid, view);
  948. }
  949. // VView does not call updateComponent so we
  950. // register any event listeners here
  951. connectorMap.registerEventListenersFromUIDL(
  952. pid, uidl);
  953. // Finally allow VView to update itself
  954. view.updateFromUIDL(uidl,
  955. ApplicationConnection.this);
  956. }
  957. }
  958. } catch (final Throwable e) {
  959. VConsole.error(e);
  960. }
  961. }
  962. if (json.containsKey("dd")) {
  963. // response contains data for drag and drop service
  964. VDragAndDropManager.get().handleServerResponse(
  965. json.getValueMap("dd"));
  966. }
  967. VConsole.log("updateFromUidl: "
  968. + updateDuration.elapsedMillis() + " ms");
  969. doLayout(false);
  970. if (meta != null) {
  971. if (meta.containsKey("appError")) {
  972. ValueMap error = meta.getValueMap("appError");
  973. String html = "";
  974. if (error.containsKey("caption")
  975. && error.getString("caption") != null) {
  976. html += "<h1>" + error.getAsString("caption")
  977. + "</h1>";
  978. }
  979. if (error.containsKey("message")
  980. && error.getString("message") != null) {
  981. html += "<p>" + error.getAsString("message")
  982. + "</p>";
  983. }
  984. String url = null;
  985. if (error.containsKey("url")) {
  986. url = error.getString("url");
  987. }
  988. if (html.length() != 0) {
  989. /* 45 min */
  990. VNotification n = VNotification
  991. .createNotification(1000 * 60 * 45);
  992. n.addEventListener(new NotificationRedirect(url));
  993. n.show(html, VNotification.CENTERED_TOP,
  994. VNotification.STYLE_SYSTEM);
  995. } else {
  996. redirect(url);
  997. }
  998. applicationRunning = false;
  999. }
  1000. if (validatingLayouts) {
  1001. VConsole.printLayoutProblems(meta,
  1002. ApplicationConnection.this,
  1003. zeroHeightComponents, zeroWidthComponents);
  1004. zeroHeightComponents = null;
  1005. zeroWidthComponents = null;
  1006. validatingLayouts = false;
  1007. }
  1008. }
  1009. if (repaintAll) {
  1010. /*
  1011. * idToPaintableDetail is already cleanded at the start of
  1012. * the changeset handling, bypass cleanup.
  1013. */
  1014. connectorMap.purgeUnregistryBag(false);
  1015. } else {
  1016. connectorMap.purgeUnregistryBag(true);
  1017. }
  1018. // TODO build profiling for widget impl loading time
  1019. final long prosessingTime = (new Date().getTime())
  1020. - start.getTime();
  1021. VConsole.log(" Processing time was "
  1022. + String.valueOf(prosessingTime) + "ms for "
  1023. + jsonText.length() + " characters of JSON");
  1024. VConsole.log("Referenced paintables: " + connectorMap.size());
  1025. endRequest();
  1026. }
  1027. };
  1028. ApplicationConfiguration.runWhenWidgetsLoaded(c);
  1029. }
  1030. // Redirect browser, null reloads current page
  1031. private static native void redirect(String url)
  1032. /*-{
  1033. if (url) {
  1034. $wnd.location = url;
  1035. } else {
  1036. $wnd.location.reload(false);
  1037. }
  1038. }-*/;
  1039. private void addVariableToQueue(String paintableId, String variableName,
  1040. Object value, boolean immediate) {
  1041. // note that type is now deduced from value
  1042. // TODO could eliminate invocations of same shared variable setter
  1043. addMethodInvocationToQueue(new MethodInvocation(paintableId,
  1044. UPDATE_VARIABLE_INTERFACE, UPDATE_VARIABLE_METHOD,
  1045. new Object[] { variableName, value }), immediate);
  1046. }
  1047. /**
  1048. * Adds an explicit RPC method invocation to the send queue.
  1049. *
  1050. * @since 7.0
  1051. *
  1052. * @param invocation
  1053. * RPC method invocation
  1054. * @param immediate
  1055. * true to trigger sending within a short time window (possibly
  1056. * combining subsequent calls to a single request), false to let
  1057. * the framework delay sending of RPC calls and variable changes
  1058. * until the next immediate change
  1059. */
  1060. public void addMethodInvocationToQueue(MethodInvocation invocation,
  1061. boolean immediate) {
  1062. pendingInvocations.add(invocation);
  1063. if (immediate) {
  1064. sendPendingVariableChanges();
  1065. }
  1066. }
  1067. /**
  1068. * This method sends currently queued variable changes to server. It is
  1069. * called when immediate variable update must happen.
  1070. *
  1071. * To ensure correct order for variable changes (due servers multithreading
  1072. * or network), we always wait for active request to be handler before
  1073. * sending a new one. If there is an active request, we will put varible
  1074. * "burst" to queue that will be purged after current request is handled.
  1075. *
  1076. */
  1077. public void sendPendingVariableChanges() {
  1078. if (!deferedSendPending) {
  1079. deferedSendPending = true;
  1080. Scheduler.get().scheduleDeferred(sendPendingCommand);
  1081. }
  1082. }
  1083. private final ScheduledCommand sendPendingCommand = new ScheduledCommand() {
  1084. public void execute() {
  1085. deferedSendPending = false;
  1086. doSendPendingVariableChanges();
  1087. }
  1088. };
  1089. private boolean deferedSendPending = false;
  1090. @SuppressWarnings("unchecked")
  1091. private void doSendPendingVariableChanges() {
  1092. if (applicationRunning) {
  1093. if (hasActiveRequest()) {
  1094. // skip empty queues if there are pending bursts to be sent
  1095. if (pendingInvocations.size() > 0 || pendingBursts.size() == 0) {
  1096. pendingBursts.add(pendingInvocations);
  1097. pendingInvocations = new ArrayList<MethodInvocation>();
  1098. }
  1099. } else {
  1100. buildAndSendVariableBurst(pendingInvocations, false);
  1101. }
  1102. }
  1103. }
  1104. /**
  1105. * Build the variable burst and send it to server.
  1106. *
  1107. * When sync is forced, we also force sending of all pending variable-bursts
  1108. * at the same time. This is ok as we can assume that DOM will never be
  1109. * updated after this.
  1110. *
  1111. * @param pendingInvocations
  1112. * List of RPC method invocations to send
  1113. * @param forceSync
  1114. * Should we use synchronous request?
  1115. */
  1116. private void buildAndSendVariableBurst(
  1117. ArrayList<MethodInvocation> pendingInvocations, boolean forceSync) {
  1118. final StringBuffer req = new StringBuffer();
  1119. while (!pendingInvocations.isEmpty()) {
  1120. if (ApplicationConfiguration.isDebugMode()) {
  1121. Util.logVariableBurst(this, pendingInvocations);
  1122. }
  1123. JSONArray reqJson = new JSONArray();
  1124. for (MethodInvocation invocation : pendingInvocations) {
  1125. JSONArray invocationJson = new JSONArray();
  1126. invocationJson.set(0,
  1127. new JSONString(invocation.getPaintableId()));
  1128. invocationJson.set(1,
  1129. new JSONString(invocation.getInterfaceName()));
  1130. invocationJson.set(2,
  1131. new JSONString(invocation.getMethodName()));
  1132. JSONArray paramJson = new JSONArray();
  1133. for (int i = 0; i < invocation.getParameters().length; ++i) {
  1134. // TODO non-static encoder? type registration?
  1135. paramJson.set(i, JsonEncoder.encode(
  1136. invocation.getParameters()[i], getConnectorMap()));
  1137. }
  1138. invocationJson.set(3, paramJson);
  1139. reqJson.set(reqJson.size(), invocationJson);
  1140. }
  1141. // escape burst separators (if any)
  1142. req.append(escapeBurstContents(reqJson.toString()));
  1143. pendingInvocations.clear();
  1144. // Append all the bursts to this synchronous request
  1145. if (forceSync && !pendingBursts.isEmpty()) {
  1146. pendingInvocations = pendingBursts.get(0);
  1147. pendingBursts.remove(0);
  1148. req.append(VAR_BURST_SEPARATOR);
  1149. }
  1150. }
  1151. // Include the browser detail parameters if they aren't already sent
  1152. String extraParams;
  1153. if (!getConfiguration().isBrowserDetailsSent()) {
  1154. extraParams = getNativeBrowserDetailsParameters(getConfiguration()
  1155. .getRootPanelId());
  1156. getConfiguration().setBrowserDetailsSent();
  1157. } else {
  1158. extraParams = "";
  1159. }
  1160. makeUidlRequest(req.toString(), extraParams, forceSync);
  1161. }
  1162. private void makeUidlRequest(String string) {
  1163. makeUidlRequest(string, "", false);
  1164. }
  1165. /**
  1166. * Sends a new value for the given paintables given variable to the server.
  1167. * <p>
  1168. * The update is actually queued to be sent at a suitable time. If immediate
  1169. * is true, the update is sent as soon as possible. If immediate is false,
  1170. * the update will be sent along with the next immediate update.
  1171. * </p>
  1172. *
  1173. * @param paintableId
  1174. * the id of the paintable that owns the variable
  1175. * @param variableName
  1176. * the name of the variable
  1177. * @param newValue
  1178. * the new value to be sent
  1179. * @param immediate
  1180. * true if the update is to be sent as soon as possible
  1181. */
  1182. public void updateVariable(String paintableId, String variableName,
  1183. Connector newValue, boolean immediate) {
  1184. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1185. }
  1186. /**
  1187. * Sends a new value for the given paintables given variable to the server.
  1188. * <p>
  1189. * The update is actually queued to be sent at a suitable time. If immediate
  1190. * is true, the update is sent as soon as possible. If immediate is false,
  1191. * the update will be sent along with the next immediate update.
  1192. * </p>
  1193. *
  1194. * @param paintableId
  1195. * the id of the paintable that owns the variable
  1196. * @param variableName
  1197. * the name of the variable
  1198. * @param newValue
  1199. * the new value to be sent
  1200. * @param immediate
  1201. * true if the update is to be sent as soon as possible
  1202. */
  1203. public void updateVariable(String paintableId, String variableName,
  1204. String newValue, boolean immediate) {
  1205. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1206. }
  1207. /**
  1208. * Sends a new value for the given paintables given variable to the server.
  1209. * <p>
  1210. * The update is actually queued to be sent at a suitable time. If immediate
  1211. * is true, the update is sent as soon as possible. If immediate is false,
  1212. * the update will be sent along with the next immediate update.
  1213. * </p>
  1214. *
  1215. * @param paintableId
  1216. * the id of the paintable that owns the variable
  1217. * @param variableName
  1218. * the name of the variable
  1219. * @param newValue
  1220. * the new value to be sent
  1221. * @param immediate
  1222. * true if the update is to be sent as soon as possible
  1223. */
  1224. public void updateVariable(String paintableId, String variableName,
  1225. int newValue, boolean immediate) {
  1226. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1227. }
  1228. /**
  1229. * Sends a new value for the given paintables given variable to the server.
  1230. * <p>
  1231. * The update is actually queued to be sent at a suitable time. If immediate
  1232. * is true, the update is sent as soon as possible. If immediate is false,
  1233. * the update will be sent along with the next immediate update.
  1234. * </p>
  1235. *
  1236. * @param paintableId
  1237. * the id of the paintable that owns the variable
  1238. * @param variableName
  1239. * the name of the variable
  1240. * @param newValue
  1241. * the new value to be sent
  1242. * @param immediate
  1243. * true if the update is to be sent as soon as possible
  1244. */
  1245. public void updateVariable(String paintableId, String variableName,
  1246. long newValue, boolean immediate) {
  1247. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1248. }
  1249. /**
  1250. * Sends a new value for the given paintables given variable to the server.
  1251. * <p>
  1252. * The update is actually queued to be sent at a suitable time. If immediate
  1253. * is true, the update is sent as soon as possible. If immediate is false,
  1254. * the update will be sent along with the next immediate update.
  1255. * </p>
  1256. *
  1257. * @param paintableId
  1258. * the id of the paintable that owns the variable
  1259. * @param variableName
  1260. * the name of the variable
  1261. * @param newValue
  1262. * the new value to be sent
  1263. * @param immediate
  1264. * true if the update is to be sent as soon as possible
  1265. */
  1266. public void updateVariable(String paintableId, String variableName,
  1267. float newValue, boolean immediate) {
  1268. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1269. }
  1270. /**
  1271. * Sends a new value for the given paintables given variable to the server.
  1272. * <p>
  1273. * The update is actually queued to be sent at a suitable time. If immediate
  1274. * is true, the update is sent as soon as possible. If immediate is false,
  1275. * the update will be sent along with the next immediate update.
  1276. * </p>
  1277. *
  1278. * @param paintableId
  1279. * the id of the paintable that owns the variable
  1280. * @param variableName
  1281. * the name of the variable
  1282. * @param newValue
  1283. * the new value to be sent
  1284. * @param immediate
  1285. * true if the update is to be sent as soon as possible
  1286. */
  1287. public void updateVariable(String paintableId, String variableName,
  1288. double newValue, boolean immediate) {
  1289. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1290. }
  1291. /**
  1292. * Sends a new value for the given paintables given variable to the server.
  1293. * <p>
  1294. * The update is actually queued to be sent at a suitable time. If immediate
  1295. * is true, the update is sent as soon as possible. If immediate is false,
  1296. * the update will be sent along with the next immediate update.
  1297. * </p>
  1298. *
  1299. * @param paintableId
  1300. * the id of the paintable that owns the variable
  1301. * @param variableName
  1302. * the name of the variable
  1303. * @param newValue
  1304. * the new value to be sent
  1305. * @param immediate
  1306. * true if the update is to be sent as soon as possible
  1307. */
  1308. public void updateVariable(String paintableId, String variableName,
  1309. boolean newValue, boolean immediate) {
  1310. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1311. }
  1312. /**
  1313. * Sends a new value for the given paintables given variable to the server.
  1314. * <p>
  1315. * The update is actually queued to be sent at a suitable time. If immediate
  1316. * is true, the update is sent as soon as possible. If immediate is false,
  1317. * the update will be sent along with the next immediate update.
  1318. * </p>
  1319. *
  1320. * @param paintableId
  1321. * the id of the paintable that owns the variable
  1322. * @param variableName
  1323. * the name of the variable
  1324. * @param map
  1325. * the new values to be sent
  1326. * @param immediate
  1327. * true if the update is to be sent as soon as possible
  1328. */
  1329. public void updateVariable(String paintableId, String variableName,
  1330. Map<String, Object> map, boolean immediate) {
  1331. addVariableToQueue(paintableId, variableName, map, immediate);
  1332. }
  1333. /**
  1334. * Sends a new value for the given paintables given variable to the server.
  1335. *
  1336. * The update is actually queued to be sent at a suitable time. If immediate
  1337. * is true, the update is sent as soon as possible. If immediate is false,
  1338. * the update will be sent along with the next immediate update.
  1339. *
  1340. * A null array is sent as an empty array.
  1341. *
  1342. * @param paintableId
  1343. * the id of the paintable that owns the variable
  1344. * @param variableName
  1345. * the name of the variable
  1346. * @param values
  1347. * the new value to be sent
  1348. * @param immediate
  1349. * true if the update is to be sent as soon as possible
  1350. */
  1351. public void updateVariable(String paintableId, String variableName,
  1352. String[] values, boolean immediate) {
  1353. addVariableToQueue(paintableId, variableName, values, immediate);
  1354. }
  1355. /**
  1356. * Sends a new value for the given paintables given variable to the server.
  1357. *
  1358. * The update is actually queued to be sent at a suitable time. If immediate
  1359. * is true, the update is sent as soon as possible. If immediate is false,
  1360. * the update will be sent along with the next immediate update. </p>
  1361. *
  1362. * A null array is sent as an empty array.
  1363. *
  1364. *
  1365. * @param paintableId
  1366. * the id of the paintable that owns the variable
  1367. * @param variableName
  1368. * the name of the variable
  1369. * @param values
  1370. * the new value to be sent
  1371. * @param immediate
  1372. * true if the update is to be sent as soon as possible
  1373. */
  1374. public void updateVariable(String paintableId, String variableName,
  1375. Object[] values, boolean immediate) {
  1376. addVariableToQueue(paintableId, variableName, values, immediate);
  1377. }
  1378. /**
  1379. * Encode burst separator characters in a String for transport over the
  1380. * network. This protects from separator injection attacks.
  1381. *
  1382. * @param value
  1383. * to encode
  1384. * @return encoded value
  1385. */
  1386. protected String escapeBurstContents(String value) {
  1387. final StringBuilder result = new StringBuilder();
  1388. for (int i = 0; i < value.length(); ++i) {
  1389. char character = value.charAt(i);
  1390. switch (character) {
  1391. case VAR_ESCAPE_CHARACTER:
  1392. // fall-through - escape character is duplicated
  1393. case VAR_BURST_SEPARATOR:
  1394. result.append(VAR_ESCAPE_CHARACTER);
  1395. // encode as letters for easier reading
  1396. result.append(((char) (character + 0x30)));
  1397. break;
  1398. default:
  1399. // the char is not a special one - add it to the result as is
  1400. result.append(character);
  1401. break;
  1402. }
  1403. }
  1404. return result.toString();
  1405. }
  1406. private boolean runningLayout = false;
  1407. /**
  1408. * Causes a re-calculation/re-layout of all paintables in a container.
  1409. *
  1410. * @param container
  1411. */
  1412. public void runDescendentsLayout(HasWidgets container) {
  1413. if (runningLayout) {
  1414. return;
  1415. }
  1416. runningLayout = true;
  1417. internalRunDescendentsLayout(container);
  1418. runningLayout = false;
  1419. }
  1420. /**
  1421. * This will cause re-layouting of all components. Mainly used for
  1422. * development. Published to JavaScript.
  1423. */
  1424. public void forceLayout() {
  1425. Duration duration = new Duration();
  1426. layoutManager.foceLayout();
  1427. VConsole.log("forceLayout in " + duration.elapsedMillis() + " ms");
  1428. }
  1429. private void internalRunDescendentsLayout(HasWidgets container) {
  1430. // getConsole().log(
  1431. // "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
  1432. final Iterator<Widget> childWidgets = container.iterator();
  1433. while (childWidgets.hasNext()) {
  1434. final Widget child = childWidgets.next();
  1435. if (getConnectorMap().isConnector(child)) {
  1436. if (handleComponentRelativeSize(child)) {
  1437. /*
  1438. * Only need to propagate event if "child" has a relative
  1439. * size
  1440. */
  1441. if (child instanceof ContainerResizedListener) {
  1442. ((ContainerResizedListener) child).iLayout();
  1443. }
  1444. if (child instanceof HasWidgets) {
  1445. final HasWidgets childContainer = (HasWidgets) child;
  1446. internalRunDescendentsLayout(childContainer);
  1447. }
  1448. }
  1449. } else if (child instanceof HasWidgets) {
  1450. // propagate over non Paintable HasWidgets
  1451. internalRunDescendentsLayout((HasWidgets) child);
  1452. }
  1453. }
  1454. }
  1455. /**
  1456. * Converts relative sizes into pixel sizes.
  1457. *
  1458. * @param child
  1459. * @return true if the child has a relative size
  1460. */
  1461. private boolean handleComponentRelativeSize(ComponentConnector paintable) {
  1462. return false;
  1463. }
  1464. /**
  1465. * Converts relative sizes into pixel sizes.
  1466. *
  1467. * @param child
  1468. * @return true if the child has a relative size
  1469. */
  1470. public boolean handleComponentRelativeSize(Widget widget) {
  1471. return handleComponentRelativeSize(connectorMap.getConnector(widget));
  1472. }
  1473. /**
  1474. * Get either existing or new Paintable for given UIDL.
  1475. *
  1476. * If corresponding Paintable has been previously painted, return it.
  1477. * Otherwise create and register a new Paintable from UIDL. Caller must
  1478. * update the returned Paintable from UIDL after it has been connected to
  1479. * parent.
  1480. *
  1481. * @param uidl
  1482. * UIDL to create Paintable from.
  1483. * @return Either existing or new Paintable corresponding to UIDL.
  1484. */
  1485. public ComponentConnector getPaintable(UIDL uidl) {
  1486. final String pid = uidl.getId();
  1487. // the actual content UIDL may be deferred, but it always contains
  1488. // enough information to create a paintable instance
  1489. if (!connectorMap.hasConnector(pid)) {
  1490. // Create and register a new paintable if no old was found
  1491. ComponentConnector p = widgetSet.createWidget(uidl.getTag(),
  1492. configuration);
  1493. p.setConnection(this);
  1494. p.setId(pid);
  1495. p.init();
  1496. connectorMap.registerConnector(pid, p);
  1497. }
  1498. return (ComponentConnector) connectorMap.getConnector(pid);
  1499. }
  1500. /**
  1501. * Gets a recource that has been pre-loaded via UIDL, such as custom
  1502. * layouts.
  1503. *
  1504. * @param name
  1505. * identifier of the resource to get
  1506. * @return the resource
  1507. */
  1508. public String getResource(String name) {
  1509. return resourcesMap.get(name);
  1510. }
  1511. /**
  1512. * Singleton method to get instance of app's context menu.
  1513. *
  1514. * @return VContextMenu object
  1515. */
  1516. public VContextMenu getContextMenu() {
  1517. if (contextMenu == null) {
  1518. contextMenu = new VContextMenu();
  1519. DOM.setElementProperty(contextMenu.getElement(), "id",
  1520. "PID_VAADIN_CM");
  1521. }
  1522. return contextMenu;
  1523. }
  1524. /**
  1525. * Translates custom protocols in UIDL URI's to be recognizable by browser.
  1526. * All uri's from UIDL should be routed via this method before giving them
  1527. * to browser due URI's in UIDL may contain custom protocols like theme://.
  1528. *
  1529. * @param uidlUri
  1530. * Vaadin URI from uidl
  1531. * @return translated URI ready for browser
  1532. */
  1533. public String translateVaadinUri(String uidlUri) {
  1534. if (uidlUri == null) {
  1535. return null;
  1536. }
  1537. if (uidlUri.startsWith("theme://")) {
  1538. final String themeUri = configuration.getThemeUri();
  1539. if (themeUri == null) {
  1540. VConsole.error("Theme not set: ThemeResource will not be found. ("
  1541. + uidlUri + ")");
  1542. }
  1543. uidlUri = themeUri + uidlUri.substring(7);
  1544. }
  1545. if (uidlUri.startsWith("app://")) {
  1546. uidlUri = getAppUri() + uidlUri.substring(6);
  1547. }
  1548. return uidlUri;
  1549. }
  1550. /**
  1551. * Gets the URI for the current theme. Can be used to reference theme
  1552. * resources.
  1553. *
  1554. * @return URI to the current theme
  1555. */
  1556. public String getThemeUri() {
  1557. return configuration.getThemeUri();
  1558. }
  1559. /**
  1560. * Listens for Notification hide event, and redirects. Used for system
  1561. * messages, such as session expired.
  1562. *
  1563. */
  1564. private class NotificationRedirect implements VNotification.EventListener {
  1565. String url;
  1566. NotificationRedirect(String url) {
  1567. this.url = url;
  1568. }
  1569. public void notificationHidden(HideEvent event) {
  1570. redirect(url);
  1571. }
  1572. }
  1573. /* Extended title handling */
  1574. /**
  1575. * Data showed in tooltips are stored centrilized as it may be needed in
  1576. * varios place: caption, layouts, and in owner components themselves.
  1577. *
  1578. * Updating TooltipInfo is done in updateComponent method.
  1579. *
  1580. */
  1581. public TooltipInfo getTooltipTitleInfo(ComponentConnector titleOwner,
  1582. Object key) {
  1583. if (null == titleOwner) {
  1584. return null;
  1585. }
  1586. return connectorMap.getTooltipInfo(titleOwner, key);
  1587. }
  1588. private final VTooltip tooltip = new VTooltip(this);
  1589. /**
  1590. * Component may want to delegate Tooltip handling to client. Layouts add
  1591. * Tooltip (description, errors) to caption, but some components may want
  1592. * them to appear one other elements too.
  1593. *
  1594. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1595. *
  1596. * @param event
  1597. * @param owner
  1598. */
  1599. public void handleTooltipEvent(Event event, ComponentConnector owner) {
  1600. tooltip.handleTooltipEvent(event, owner, null);
  1601. }
  1602. /**
  1603. * Component may want to delegate Tooltip handling to client. Layouts add
  1604. * Tooltip (description, errors) to caption, but some components may want
  1605. * them to appear one other elements too.
  1606. *
  1607. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1608. *
  1609. * @param event
  1610. * @param owner
  1611. * @param key
  1612. * the key for tooltip if this is "additional" tooltip, null for
  1613. * components "main tooltip"
  1614. */
  1615. public void handleTooltipEvent(Event event, ComponentConnector owner,
  1616. Object key) {
  1617. tooltip.handleTooltipEvent(event, owner, key);
  1618. }
  1619. /*
  1620. * Helper to run layout functions triggered by child components with a
  1621. * decent interval.
  1622. */
  1623. private final Timer layoutTimer = new Timer() {
  1624. private boolean isPending = false;
  1625. @Override
  1626. public void schedule(int delayMillis) {
  1627. if (!isPending) {
  1628. super.schedule(delayMillis);
  1629. isPending = true;
  1630. }
  1631. }
  1632. @Override
  1633. public void run() {
  1634. VConsole.log("Running re-layout of " + view.getClass().getName());
  1635. runDescendentsLayout(view.getWidget());
  1636. isPending = false;
  1637. }
  1638. };
  1639. private ConnectorMap connectorMap = GWT.create(ConnectorMap.class);
  1640. /**
  1641. * Components can call this function to run all layout functions. This is
  1642. * usually done, when component knows that its size has changed.
  1643. */
  1644. public void requestLayoutPhase() {
  1645. layoutTimer.schedule(500);
  1646. }
  1647. protected String getUidlSecurityKey() {
  1648. return uidlSecurityKey;
  1649. }
  1650. /**
  1651. * Use to notify that the given component's caption has changed; layouts may
  1652. * have to be recalculated.
  1653. *
  1654. * @param component
  1655. * the Paintable whose caption has changed
  1656. */
  1657. public void captionSizeUpdated(Widget widget) {
  1658. componentCaptionSizeChanges.add(widget);
  1659. }
  1660. /**
  1661. * Gets the main view, a.k.a top-level window.
  1662. *
  1663. * @return the main view
  1664. */
  1665. public VViewPaintable getView() {
  1666. return view;
  1667. }
  1668. /**
  1669. * If component has several tooltips in addition to the one provided by
  1670. * {@link com.vaadin.ui.AbstractComponent}, component can register them with
  1671. * this method.
  1672. * <p>
  1673. * Component must also pipe events to
  1674. * {@link #handleTooltipEvent(Event, ComponentConnector, Object)} method.
  1675. * <p>
  1676. * This method can also be used to deregister tooltips by using null as
  1677. * tooltip
  1678. *
  1679. * @param paintable
  1680. * Paintable "owning" this tooltip
  1681. * @param key
  1682. * key assosiated with given tooltip. Can be any object. For
  1683. * example a related dom element. Same key must be given for
  1684. * {@link #handleTooltipEvent(Event, ComponentConnector, Object)}
  1685. * method.
  1686. *
  1687. * @param tooltip
  1688. * the TooltipInfo object containing details shown in tooltip,
  1689. * null if deregistering tooltip
  1690. */
  1691. public void registerTooltip(ComponentConnector paintable, Object key,
  1692. TooltipInfo tooltip) {
  1693. connectorMap.registerTooltip(paintable, key, tooltip);
  1694. }
  1695. /**
  1696. * Gets the {@link ApplicationConfiguration} for the current application.
  1697. *
  1698. * @see ApplicationConfiguration
  1699. * @return the configuration for this application
  1700. */
  1701. public ApplicationConfiguration getConfiguration() {
  1702. return configuration;
  1703. }
  1704. /**
  1705. * Checks if there is a registered server side listener for the event. The
  1706. * list of events which has server side listeners is updated automatically
  1707. * before the component is updated so the value is correct if called from
  1708. * updatedFromUIDL.
  1709. *
  1710. * @param eventIdentifier
  1711. * The identifier for the event
  1712. * @return true if at least one listener has been registered on server side
  1713. * for the event identified by eventIdentifier.
  1714. */
  1715. public boolean hasEventListeners(ComponentConnector paintable,
  1716. String eventIdentifier) {
  1717. return connectorMap.hasEventListeners(paintable, eventIdentifier);
  1718. }
  1719. /**
  1720. * Adds the get parameters to the uri and returns the new uri that contains
  1721. * the parameters.
  1722. *
  1723. * @param uri
  1724. * The uri to which the parameters should be added.
  1725. * @param extraParams
  1726. * One or more parameters in the format "a=b" or "c=d&e=f". An
  1727. * empty string is allowed but will not modify the url.
  1728. * @return The modified URI with the get parameters in extraParams added.
  1729. */
  1730. public static String addGetParameters(String uri, String extraParams) {
  1731. if (extraParams == null || extraParams.length() == 0) {
  1732. return uri;
  1733. }
  1734. // RFC 3986: The query component is indicated by the first question
  1735. // mark ("?") character and terminated by a number sign ("#") character
  1736. // or by the end of the URI.
  1737. String fragment = null;
  1738. int hashPosition = uri.indexOf('#');
  1739. if (hashPosition != -1) {
  1740. // Fragment including "#"
  1741. fragment = uri.substring(hashPosition);
  1742. // The full uri before the fragment
  1743. uri = uri.substring(0, hashPosition);
  1744. }
  1745. if (uri.contains("?")) {
  1746. uri += "&";
  1747. } else {
  1748. uri += "?";
  1749. }
  1750. uri += extraParams;
  1751. if (fragment != null) {
  1752. uri += fragment;
  1753. }
  1754. return uri;
  1755. }
  1756. ConnectorMap getConnectorMap() {
  1757. return connectorMap;
  1758. }
  1759. @Deprecated
  1760. public void unregisterPaintable(Connector p) {
  1761. connectorMap.unregisterConnector(p);
  1762. }
  1763. public VTooltip getVTooltip() {
  1764. return tooltip;
  1765. }
  1766. @Deprecated
  1767. public void handleTooltipEvent(Event event, Widget owner, Object key) {
  1768. handleTooltipEvent(event, getConnectorMap().getConnector(owner), key);
  1769. }
  1770. @Deprecated
  1771. public void handleTooltipEvent(Event event, Widget owner) {
  1772. handleTooltipEvent(event, getConnectorMap().getConnector(owner));
  1773. }
  1774. @Deprecated
  1775. public void registerTooltip(Widget owner, Object key, TooltipInfo info) {
  1776. registerTooltip(getConnectorMap().getConnector(owner), key, info);
  1777. }
  1778. @Deprecated
  1779. public boolean hasEventListeners(Widget widget, String eventIdentifier) {
  1780. return hasEventListeners(getConnectorMap().getConnector(widget),
  1781. eventIdentifier);
  1782. }
  1783. private boolean layoutScheduled = false;
  1784. private ScheduledCommand layoutCommand = new ScheduledCommand() {
  1785. public void execute() {
  1786. layoutScheduled = false;
  1787. layoutManager.doLayout();
  1788. }
  1789. };
  1790. public void doLayout(boolean lazy) {
  1791. if (!lazy) {
  1792. layoutCommand.execute();
  1793. } else if (!layoutScheduled) {
  1794. layoutScheduled = true;
  1795. Scheduler.get().scheduleDeferred(layoutCommand);
  1796. }
  1797. }
  1798. LayoutManager getLayoutManager() {
  1799. return layoutManager;
  1800. }
  1801. }