You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ApplicationConnection.java 73KB

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