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 81KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248
  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.GWT;
  13. import com.google.gwt.core.client.JavaScriptObject;
  14. import com.google.gwt.core.client.JsArray;
  15. import com.google.gwt.core.client.JsArrayString;
  16. import com.google.gwt.core.client.Scheduler;
  17. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  18. import com.google.gwt.http.client.Request;
  19. import com.google.gwt.http.client.RequestBuilder;
  20. import com.google.gwt.http.client.RequestCallback;
  21. import com.google.gwt.http.client.RequestException;
  22. import com.google.gwt.http.client.Response;
  23. import com.google.gwt.json.client.JSONArray;
  24. import com.google.gwt.json.client.JSONString;
  25. import com.google.gwt.regexp.shared.MatchResult;
  26. import com.google.gwt.regexp.shared.RegExp;
  27. import com.google.gwt.user.client.Command;
  28. import com.google.gwt.user.client.DOM;
  29. import com.google.gwt.user.client.Element;
  30. import com.google.gwt.user.client.Event;
  31. import com.google.gwt.user.client.Timer;
  32. import com.google.gwt.user.client.Window;
  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 VPaintableWidget#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<VPaintableWidget> relativeSizeChanges = new ArrayList<VPaintableWidget>();
  130. private ArrayList<Widget> componentCaptionSizeChanges = new ArrayList<Widget>();
  131. private Date requestStartTime;
  132. private boolean validatingLayouts = false;
  133. private Set<VPaintableWidget> zeroWidthComponents = null;
  134. private Set<VPaintableWidget> zeroHeightComponents = null;
  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(VPaintableWidget paintable) {
  373. String params = getRepaintAllParameters() + "&highlightComponent="
  374. + paintableMap.getPid(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 (!getPaintableMap().hasPaintable(id)
  715. && !getPaintableMap().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.getWidgetForPaintable().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.getWidgetForPaintable().clear();
  856. getPaintableMap().clear();
  857. if (meta.containsKey("invalidLayouts")) {
  858. validatingLayouts = true;
  859. zeroWidthComponents = new HashSet<VPaintableWidget>();
  860. zeroHeightComponents = new HashSet<VPaintableWidget>();
  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<VPaintableWidget> updatedVPaintableWidgets = new ArrayList<VPaintableWidget>();
  886. relativeSizeChanges.clear();
  887. componentCaptionSizeChanges.clear();
  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. VPaintable paintable = paintableMap.getPaintable(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. VPaintable paintable = paintableMap
  913. .getPaintable(paintableId);
  914. if (null != paintable) {
  915. // TODO handle as a ValueMap or similar object and
  916. // native JavaScript processing?
  917. JavaScriptObject value = states
  918. .getJavaScriptObject(paintableId);
  919. // TODO implement with shared state subclasses
  920. SharedState state = GWT.create(SharedState.class);
  921. Map<String, Object> stateMap = (Map<String, Object>) JsonDecoder
  922. .convertValue(new JSONArray(value),
  923. getPaintableMap());
  924. state.setState(stateMap);
  925. paintable.updateState(state);
  926. }
  927. } catch (final Throwable e) {
  928. VConsole.error(e);
  929. }
  930. }
  931. // update paintables
  932. for (int i = 0; i < length; i++) {
  933. try {
  934. final UIDL change = changes.get(i).cast();
  935. final UIDL uidl = change.getChildUIDL(0);
  936. // TODO optimize
  937. final VPaintableWidget paintable = (VPaintableWidget) paintableMap
  938. .getPaintable(uidl.getId());
  939. if (paintable != null) {
  940. paintable.updateFromUIDL(uidl,
  941. ApplicationConnection.this);
  942. updatedVPaintableWidgets.add(paintable);
  943. } else {
  944. if (!uidl.getTag().equals(
  945. configuration.getEncodedWindowTag())) {
  946. VConsole.error("Received update for "
  947. + uidl.getTag()
  948. + ", but there is no such paintable ("
  949. + uidl.getId() + ") rendered.");
  950. } else {
  951. String pid = uidl.getId();
  952. if (!paintableMap.hasPaintable(pid)) {
  953. paintableMap.registerPaintable(pid, view);
  954. }
  955. // VView does not call updateComponent so we
  956. // register any event listeners here
  957. paintableMap.registerEventListenersFromUIDL(
  958. pid, uidl);
  959. // Finally allow VView to update itself
  960. view.updateFromUIDL(uidl,
  961. ApplicationConnection.this);
  962. }
  963. }
  964. } catch (final Throwable e) {
  965. VConsole.error(e);
  966. }
  967. }
  968. if (json.containsKey("dd")) {
  969. // response contains data for drag and drop service
  970. VDragAndDropManager.get().handleServerResponse(
  971. json.getValueMap("dd"));
  972. }
  973. // Check which widgets' size has been updated
  974. Set<Widget> sizeUpdatedWidgets = new HashSet<Widget>();
  975. updatedVPaintableWidgets.addAll(relativeSizeChanges);
  976. sizeUpdatedWidgets.addAll(componentCaptionSizeChanges);
  977. for (VPaintableWidget paintable : updatedVPaintableWidgets) {
  978. Widget widget = paintable.getWidgetForPaintable();
  979. Size oldSize = paintableMap.getOffsetSize(paintable);
  980. Size newSize = new Size(widget.getOffsetWidth(),
  981. widget.getOffsetHeight());
  982. if (oldSize == null || !oldSize.equals(newSize)) {
  983. sizeUpdatedWidgets.add(widget);
  984. paintableMap.setOffsetSize(paintable, newSize);
  985. }
  986. }
  987. Util.componentSizeUpdated(sizeUpdatedWidgets);
  988. if (meta != null) {
  989. if (meta.containsKey("appError")) {
  990. ValueMap error = meta.getValueMap("appError");
  991. String html = "";
  992. if (error.containsKey("caption")
  993. && error.getString("caption") != null) {
  994. html += "<h1>" + error.getAsString("caption")
  995. + "</h1>";
  996. }
  997. if (error.containsKey("message")
  998. && error.getString("message") != null) {
  999. html += "<p>" + error.getAsString("message")
  1000. + "</p>";
  1001. }
  1002. String url = null;
  1003. if (error.containsKey("url")) {
  1004. url = error.getString("url");
  1005. }
  1006. if (html.length() != 0) {
  1007. /* 45 min */
  1008. VNotification n = VNotification
  1009. .createNotification(1000 * 60 * 45);
  1010. n.addEventListener(new NotificationRedirect(url));
  1011. n.show(html, VNotification.CENTERED_TOP,
  1012. VNotification.STYLE_SYSTEM);
  1013. } else {
  1014. redirect(url);
  1015. }
  1016. applicationRunning = false;
  1017. }
  1018. if (validatingLayouts) {
  1019. VConsole.printLayoutProblems(meta,
  1020. ApplicationConnection.this,
  1021. zeroHeightComponents, zeroWidthComponents);
  1022. zeroHeightComponents = null;
  1023. zeroWidthComponents = null;
  1024. validatingLayouts = false;
  1025. }
  1026. }
  1027. if (repaintAll) {
  1028. /*
  1029. * idToPaintableDetail is already cleanded at the start of
  1030. * the changeset handling, bypass cleanup.
  1031. */
  1032. paintableMap.purgeUnregistryBag(false);
  1033. } else {
  1034. paintableMap.purgeUnregistryBag(true);
  1035. }
  1036. // TODO build profiling for widget impl loading time
  1037. final long prosessingTime = (new Date().getTime())
  1038. - start.getTime();
  1039. VConsole.log(" Processing time was "
  1040. + String.valueOf(prosessingTime) + "ms for "
  1041. + jsonText.length() + " characters of JSON");
  1042. VConsole.log("Referenced paintables: " + paintableMap.size());
  1043. endRequest();
  1044. }
  1045. };
  1046. ApplicationConfiguration.runWhenWidgetsLoaded(c);
  1047. }
  1048. // Redirect browser, null reloads current page
  1049. private static native void redirect(String url)
  1050. /*-{
  1051. if (url) {
  1052. $wnd.location = url;
  1053. } else {
  1054. $wnd.location.reload(false);
  1055. }
  1056. }-*/;
  1057. private void addVariableToQueue(String paintableId, String variableName,
  1058. Object value, boolean immediate) {
  1059. // note that type is now deduced from value
  1060. // TODO could eliminate invocations of same shared variable setter
  1061. addMethodInvocationToQueue(new MethodInvocation(paintableId,
  1062. UPDATE_VARIABLE_INTERFACE, UPDATE_VARIABLE_METHOD,
  1063. new Object[] { variableName, value }), immediate);
  1064. }
  1065. /**
  1066. * Adds an explicit RPC method invocation to the send queue.
  1067. *
  1068. * @since 7.0
  1069. *
  1070. * @param invocation
  1071. * RPC method invocation
  1072. * @param immediate
  1073. * true to trigger sending within a short time window (possibly
  1074. * combining subsequent calls to a single request), false to let
  1075. * the framework delay sending of RPC calls and variable changes
  1076. * until the next immediate change
  1077. */
  1078. public void addMethodInvocationToQueue(MethodInvocation invocation,
  1079. boolean immediate) {
  1080. pendingInvocations.add(invocation);
  1081. if (immediate) {
  1082. sendPendingVariableChanges();
  1083. }
  1084. }
  1085. /**
  1086. * This method sends currently queued variable changes to server. It is
  1087. * called when immediate variable update must happen.
  1088. *
  1089. * To ensure correct order for variable changes (due servers multithreading
  1090. * or network), we always wait for active request to be handler before
  1091. * sending a new one. If there is an active request, we will put varible
  1092. * "burst" to queue that will be purged after current request is handled.
  1093. *
  1094. */
  1095. public void sendPendingVariableChanges() {
  1096. if (!deferedSendPending) {
  1097. deferedSendPending = true;
  1098. Scheduler.get().scheduleDeferred(sendPendingCommand);
  1099. }
  1100. }
  1101. private final ScheduledCommand sendPendingCommand = new ScheduledCommand() {
  1102. public void execute() {
  1103. deferedSendPending = false;
  1104. doSendPendingVariableChanges();
  1105. }
  1106. };
  1107. private boolean deferedSendPending = false;
  1108. @SuppressWarnings("unchecked")
  1109. private void doSendPendingVariableChanges() {
  1110. if (applicationRunning) {
  1111. if (hasActiveRequest()) {
  1112. // skip empty queues if there are pending bursts to be sent
  1113. if (pendingInvocations.size() > 0 || pendingBursts.size() == 0) {
  1114. pendingBursts.add(pendingInvocations);
  1115. pendingInvocations = new ArrayList<MethodInvocation>();
  1116. }
  1117. } else {
  1118. buildAndSendVariableBurst(pendingInvocations, false);
  1119. }
  1120. }
  1121. }
  1122. /**
  1123. * Build the variable burst and send it to server.
  1124. *
  1125. * When sync is forced, we also force sending of all pending variable-bursts
  1126. * at the same time. This is ok as we can assume that DOM will never be
  1127. * updated after this.
  1128. *
  1129. * @param pendingInvocations
  1130. * List of RPC method invocations to send
  1131. * @param forceSync
  1132. * Should we use synchronous request?
  1133. */
  1134. private void buildAndSendVariableBurst(
  1135. ArrayList<MethodInvocation> pendingInvocations, boolean forceSync) {
  1136. final StringBuffer req = new StringBuffer();
  1137. while (!pendingInvocations.isEmpty()) {
  1138. if (ApplicationConfiguration.isDebugMode()) {
  1139. Util.logVariableBurst(this, pendingInvocations);
  1140. }
  1141. JSONArray reqJson = new JSONArray();
  1142. for (MethodInvocation invocation : pendingInvocations) {
  1143. JSONArray invocationJson = new JSONArray();
  1144. invocationJson.set(0,
  1145. new JSONString(invocation.getPaintableId()));
  1146. invocationJson.set(1,
  1147. new JSONString(invocation.getInterfaceName()));
  1148. invocationJson.set(2,
  1149. new JSONString(invocation.getMethodName()));
  1150. JSONArray paramJson = new JSONArray();
  1151. for (int i = 0; i < invocation.getParameters().length; ++i) {
  1152. // TODO non-static encoder? type registration?
  1153. paramJson.set(i, JsonEncoder.encode(
  1154. invocation.getParameters()[i], getPaintableMap()));
  1155. }
  1156. invocationJson.set(3, paramJson);
  1157. reqJson.set(reqJson.size(), invocationJson);
  1158. }
  1159. // escape burst separators (if any)
  1160. req.append(escapeBurstContents(reqJson.toString()));
  1161. pendingInvocations.clear();
  1162. // Append all the bursts to this synchronous request
  1163. if (forceSync && !pendingBursts.isEmpty()) {
  1164. pendingInvocations = pendingBursts.get(0);
  1165. pendingBursts.remove(0);
  1166. req.append(VAR_BURST_SEPARATOR);
  1167. }
  1168. }
  1169. // Include the browser detail parameters if they aren't already sent
  1170. String extraParams;
  1171. if (!getConfiguration().isBrowserDetailsSent()) {
  1172. extraParams = getNativeBrowserDetailsParameters(getConfiguration()
  1173. .getRootPanelId());
  1174. getConfiguration().setBrowserDetailsSent();
  1175. } else {
  1176. extraParams = "";
  1177. }
  1178. makeUidlRequest(req.toString(), extraParams, forceSync);
  1179. }
  1180. private void makeUidlRequest(String string) {
  1181. makeUidlRequest(string, "", false);
  1182. }
  1183. /**
  1184. * Sends a new value for the given paintables given variable to the server.
  1185. * <p>
  1186. * The update is actually queued to be sent at a suitable time. If immediate
  1187. * is true, the update is sent as soon as possible. If immediate is false,
  1188. * the update will be sent along with the next immediate update.
  1189. * </p>
  1190. *
  1191. * @param paintableId
  1192. * the id of the paintable that owns the variable
  1193. * @param variableName
  1194. * the name of the variable
  1195. * @param newValue
  1196. * the new value to be sent
  1197. * @param immediate
  1198. * true if the update is to be sent as soon as possible
  1199. */
  1200. public void updateVariable(String paintableId, String variableName,
  1201. VPaintable newValue, boolean immediate) {
  1202. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1203. }
  1204. /**
  1205. * Sends a new value for the given paintables given variable to the server.
  1206. * <p>
  1207. * The update is actually queued to be sent at a suitable time. If immediate
  1208. * is true, the update is sent as soon as possible. If immediate is false,
  1209. * the update will be sent along with the next immediate update.
  1210. * </p>
  1211. *
  1212. * @param paintableId
  1213. * the id of the paintable that owns the variable
  1214. * @param variableName
  1215. * the name of the variable
  1216. * @param newValue
  1217. * the new value to be sent
  1218. * @param immediate
  1219. * true if the update is to be sent as soon as possible
  1220. */
  1221. public void updateVariable(String paintableId, String variableName,
  1222. String newValue, boolean immediate) {
  1223. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1224. }
  1225. /**
  1226. * Sends a new value for the given paintables given variable to the server.
  1227. * <p>
  1228. * The update is actually queued to be sent at a suitable time. If immediate
  1229. * is true, the update is sent as soon as possible. If immediate is false,
  1230. * the update will be sent along with the next immediate update.
  1231. * </p>
  1232. *
  1233. * @param paintableId
  1234. * the id of the paintable that owns the variable
  1235. * @param variableName
  1236. * the name of the variable
  1237. * @param newValue
  1238. * the new value to be sent
  1239. * @param immediate
  1240. * true if the update is to be sent as soon as possible
  1241. */
  1242. public void updateVariable(String paintableId, String variableName,
  1243. int newValue, boolean immediate) {
  1244. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1245. }
  1246. /**
  1247. * Sends a new value for the given paintables given variable to the server.
  1248. * <p>
  1249. * The update is actually queued to be sent at a suitable time. If immediate
  1250. * is true, the update is sent as soon as possible. If immediate is false,
  1251. * the update will be sent along with the next immediate update.
  1252. * </p>
  1253. *
  1254. * @param paintableId
  1255. * the id of the paintable that owns the variable
  1256. * @param variableName
  1257. * the name of the variable
  1258. * @param newValue
  1259. * the new value to be sent
  1260. * @param immediate
  1261. * true if the update is to be sent as soon as possible
  1262. */
  1263. public void updateVariable(String paintableId, String variableName,
  1264. long newValue, boolean immediate) {
  1265. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1266. }
  1267. /**
  1268. * Sends a new value for the given paintables given variable to the server.
  1269. * <p>
  1270. * The update is actually queued to be sent at a suitable time. If immediate
  1271. * is true, the update is sent as soon as possible. If immediate is false,
  1272. * the update will be sent along with the next immediate update.
  1273. * </p>
  1274. *
  1275. * @param paintableId
  1276. * the id of the paintable that owns the variable
  1277. * @param variableName
  1278. * the name of the variable
  1279. * @param newValue
  1280. * the new value to be sent
  1281. * @param immediate
  1282. * true if the update is to be sent as soon as possible
  1283. */
  1284. public void updateVariable(String paintableId, String variableName,
  1285. float newValue, boolean immediate) {
  1286. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1287. }
  1288. /**
  1289. * Sends a new value for the given paintables given variable to the server.
  1290. * <p>
  1291. * The update is actually queued to be sent at a suitable time. If immediate
  1292. * is true, the update is sent as soon as possible. If immediate is false,
  1293. * the update will be sent along with the next immediate update.
  1294. * </p>
  1295. *
  1296. * @param paintableId
  1297. * the id of the paintable that owns the variable
  1298. * @param variableName
  1299. * the name of the variable
  1300. * @param newValue
  1301. * the new value to be sent
  1302. * @param immediate
  1303. * true if the update is to be sent as soon as possible
  1304. */
  1305. public void updateVariable(String paintableId, String variableName,
  1306. double newValue, boolean immediate) {
  1307. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1308. }
  1309. /**
  1310. * Sends a new value for the given paintables given variable to the server.
  1311. * <p>
  1312. * The update is actually queued to be sent at a suitable time. If immediate
  1313. * is true, the update is sent as soon as possible. If immediate is false,
  1314. * the update will be sent along with the next immediate update.
  1315. * </p>
  1316. *
  1317. * @param paintableId
  1318. * the id of the paintable that owns the variable
  1319. * @param variableName
  1320. * the name of the variable
  1321. * @param newValue
  1322. * the new value to be sent
  1323. * @param immediate
  1324. * true if the update is to be sent as soon as possible
  1325. */
  1326. public void updateVariable(String paintableId, String variableName,
  1327. boolean newValue, boolean immediate) {
  1328. addVariableToQueue(paintableId, variableName, newValue, immediate);
  1329. }
  1330. /**
  1331. * Sends a new value for the given paintables given variable to the server.
  1332. * <p>
  1333. * The update is actually queued to be sent at a suitable time. If immediate
  1334. * is true, the update is sent as soon as possible. If immediate is false,
  1335. * the update will be sent along with the next immediate update.
  1336. * </p>
  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 map
  1343. * the new values 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. Map<String, Object> map, boolean immediate) {
  1349. addVariableToQueue(paintableId, variableName, map, 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.
  1357. *
  1358. * A null array is sent as an empty array.
  1359. *
  1360. * @param paintableId
  1361. * the id of the paintable that owns the variable
  1362. * @param variableName
  1363. * the name of the variable
  1364. * @param values
  1365. * the new value to be sent
  1366. * @param immediate
  1367. * true if the update is to be sent as soon as possible
  1368. */
  1369. public void updateVariable(String paintableId, String variableName,
  1370. String[] values, boolean immediate) {
  1371. addVariableToQueue(paintableId, variableName, values, immediate);
  1372. }
  1373. /**
  1374. * Sends a new value for the given paintables given variable to the server.
  1375. *
  1376. * The update is actually queued to be sent at a suitable time. If immediate
  1377. * is true, the update is sent as soon as possible. If immediate is false,
  1378. * the update will be sent along with the next immediate update. </p>
  1379. *
  1380. * A null array is sent as an empty array.
  1381. *
  1382. *
  1383. * @param paintableId
  1384. * the id of the paintable that owns the variable
  1385. * @param variableName
  1386. * the name of the variable
  1387. * @param values
  1388. * the new value to be sent
  1389. * @param immediate
  1390. * true if the update is to be sent as soon as possible
  1391. */
  1392. public void updateVariable(String paintableId, String variableName,
  1393. Object[] values, boolean immediate) {
  1394. addVariableToQueue(paintableId, variableName, values, immediate);
  1395. }
  1396. /**
  1397. * Encode burst separator characters in a String for transport over the
  1398. * network. This protects from separator injection attacks.
  1399. *
  1400. * @param value
  1401. * to encode
  1402. * @return encoded value
  1403. */
  1404. protected String escapeBurstContents(String value) {
  1405. final StringBuilder result = new StringBuilder();
  1406. for (int i = 0; i < value.length(); ++i) {
  1407. char character = value.charAt(i);
  1408. switch (character) {
  1409. case VAR_ESCAPE_CHARACTER:
  1410. // fall-through - escape character is duplicated
  1411. case VAR_BURST_SEPARATOR:
  1412. result.append(VAR_ESCAPE_CHARACTER);
  1413. // encode as letters for easier reading
  1414. result.append(((char) (character + 0x30)));
  1415. break;
  1416. default:
  1417. // the char is not a special one - add it to the result as is
  1418. result.append(character);
  1419. break;
  1420. }
  1421. }
  1422. return result.toString();
  1423. }
  1424. public void updateComponentSize(VPaintableWidget paintable) {
  1425. SharedState state = paintable.getState();
  1426. String w = "";
  1427. String h = "";
  1428. if (null != state) {
  1429. // TODO move logging to VUIDLBrowser and VDebugConsole
  1430. VConsole.log("Paintable state for "
  1431. + getPaintableMap().getPid(paintable) + ": "
  1432. + String.valueOf(state.getState()));
  1433. if (state.getState().containsKey(ComponentState.STATE_WIDTH)) {
  1434. w = String.valueOf(state.getState().get(
  1435. ComponentState.STATE_WIDTH));
  1436. }
  1437. if (state.getState().containsKey(ComponentState.STATE_HEIGHT)) {
  1438. h = String.valueOf(state.getState().get(
  1439. ComponentState.STATE_HEIGHT));
  1440. }
  1441. } else {
  1442. // TODO move logging to VUIDLBrowser and VDebugConsole
  1443. VConsole.log("No state for paintable "
  1444. + getPaintableMap().getPid(paintable)
  1445. + " in ApplicationConnection.updateComponentSize()");
  1446. }
  1447. float relativeWidth = Util.parseRelativeSize(w);
  1448. float relativeHeight = Util.parseRelativeSize(h);
  1449. // First update maps so they are correct in the setHeight/setWidth calls
  1450. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1451. // One or both is relative
  1452. FloatSize relativeSize = new FloatSize(relativeWidth,
  1453. relativeHeight);
  1454. if (paintableMap.getRelativeSize(paintable) == null
  1455. && paintableMap.getOffsetSize(paintable) != null) {
  1456. // The component has changed from absolute size to relative size
  1457. relativeSizeChanges.add(paintable);
  1458. }
  1459. paintableMap.setRelativeSize(paintable, relativeSize);
  1460. } else if (relativeHeight < 0.0 && relativeWidth < 0.0) {
  1461. if (paintableMap.getRelativeSize(paintable) != null) {
  1462. // The component has changed from relative size to absolute size
  1463. relativeSizeChanges.add(paintable);
  1464. }
  1465. paintableMap.setRelativeSize(paintable, null);
  1466. }
  1467. Widget component = paintableMap.getWidget(paintable);
  1468. // Set absolute sizes
  1469. if (relativeHeight < 0.0) {
  1470. component.setHeight(h);
  1471. }
  1472. if (relativeWidth < 0.0) {
  1473. component.setWidth(w);
  1474. }
  1475. // Set relative sizes
  1476. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1477. // One or both is relative
  1478. handleComponentRelativeSize(paintable);
  1479. }
  1480. }
  1481. /**
  1482. * Traverses recursively child widgets until ContainerResizedListener child
  1483. * widget is found. They will delegate it further if needed.
  1484. *
  1485. * @param container
  1486. */
  1487. private boolean runningLayout = false;
  1488. /**
  1489. * Causes a re-calculation/re-layout of all paintables in a container.
  1490. *
  1491. * @param container
  1492. */
  1493. public void runDescendentsLayout(HasWidgets container) {
  1494. if (runningLayout) {
  1495. return;
  1496. }
  1497. runningLayout = true;
  1498. internalRunDescendentsLayout(container);
  1499. runningLayout = false;
  1500. }
  1501. /**
  1502. * This will cause re-layouting of all components. Mainly used for
  1503. * development. Published to JavaScript.
  1504. */
  1505. public void forceLayout() {
  1506. Set<Widget> set = new HashSet<Widget>();
  1507. for (VPaintable paintable : paintableMap.getPaintables()) {
  1508. if (paintable instanceof VPaintableWidget) {
  1509. set.add(((VPaintableWidget) paintable).getWidgetForPaintable());
  1510. }
  1511. }
  1512. Util.componentSizeUpdated(set);
  1513. }
  1514. private void internalRunDescendentsLayout(HasWidgets container) {
  1515. // getConsole().log(
  1516. // "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
  1517. final Iterator<Widget> childWidgets = container.iterator();
  1518. while (childWidgets.hasNext()) {
  1519. final Widget child = childWidgets.next();
  1520. if (getPaintableMap().isPaintable(child)) {
  1521. if (handleComponentRelativeSize(child)) {
  1522. /*
  1523. * Only need to propagate event if "child" has a relative
  1524. * size
  1525. */
  1526. if (child instanceof ContainerResizedListener) {
  1527. ((ContainerResizedListener) child).iLayout();
  1528. }
  1529. if (child instanceof HasWidgets) {
  1530. final HasWidgets childContainer = (HasWidgets) child;
  1531. internalRunDescendentsLayout(childContainer);
  1532. }
  1533. }
  1534. } else if (child instanceof HasWidgets) {
  1535. // propagate over non Paintable HasWidgets
  1536. internalRunDescendentsLayout((HasWidgets) child);
  1537. }
  1538. }
  1539. }
  1540. /**
  1541. * Converts relative sizes into pixel sizes.
  1542. *
  1543. * @param child
  1544. * @return true if the child has a relative size
  1545. */
  1546. private boolean handleComponentRelativeSize(VPaintableWidget paintable) {
  1547. if (paintable == null) {
  1548. return false;
  1549. }
  1550. boolean debugSizes = true;
  1551. FloatSize relativeSize = paintableMap.getRelativeSize(paintable);
  1552. if (relativeSize == null) {
  1553. return false;
  1554. }
  1555. Widget widget = paintableMap.getWidget(paintable);
  1556. boolean horizontalScrollBar = false;
  1557. boolean verticalScrollBar = false;
  1558. VPaintableWidgetContainer parentPaintable = paintable.getParent();
  1559. RenderSpace renderSpace;
  1560. // Parent-less components (like sub-windows) are relative to browser
  1561. // window.
  1562. if (parentPaintable == null) {
  1563. renderSpace = new RenderSpace(Window.getClientWidth(),
  1564. Window.getClientHeight());
  1565. } else {
  1566. renderSpace = ((Container) parentPaintable.getWidgetForPaintable())
  1567. .getAllocatedSpace(widget);
  1568. }
  1569. if (relativeSize.getHeight() >= 0) {
  1570. if (renderSpace != null) {
  1571. if (renderSpace.getScrollbarSize() > 0) {
  1572. if (relativeSize.getWidth() > 100) {
  1573. horizontalScrollBar = true;
  1574. } else if (relativeSize.getWidth() < 0
  1575. && renderSpace.getWidth() > 0) {
  1576. int offsetWidth = widget.getOffsetWidth();
  1577. int width = renderSpace.getWidth();
  1578. if (offsetWidth > width) {
  1579. horizontalScrollBar = true;
  1580. }
  1581. }
  1582. }
  1583. int height = renderSpace.getHeight();
  1584. if (horizontalScrollBar) {
  1585. height -= renderSpace.getScrollbarSize();
  1586. }
  1587. if (validatingLayouts && height <= 0) {
  1588. zeroHeightComponents.add(paintable);
  1589. }
  1590. height = (int) (height * relativeSize.getHeight() / 100.0);
  1591. if (height < 0) {
  1592. height = 0;
  1593. }
  1594. if (debugSizes) {
  1595. VConsole.log("Widget "
  1596. + Util.getSimpleName(widget)
  1597. + "/"
  1598. + paintableMap.getPid(paintable)
  1599. + " relative height "
  1600. + relativeSize.getHeight()
  1601. + "% of "
  1602. + renderSpace.getHeight()
  1603. + "px (reported by "
  1604. + Util.getSimpleName(parentPaintable)
  1605. + "/"
  1606. + (parentPaintable == null ? "?" : parentPaintable
  1607. .hashCode()) + ") : " + height + "px");
  1608. }
  1609. widget.setHeight(height + "px");
  1610. } else {
  1611. widget.setHeight(relativeSize.getHeight() + "%");
  1612. VConsole.error(Util.getLayout(widget).getClass().getName()
  1613. + " did not produce allocatedSpace for "
  1614. + widget.getClass().getName());
  1615. }
  1616. }
  1617. if (relativeSize.getWidth() >= 0) {
  1618. if (renderSpace != null) {
  1619. int width = renderSpace.getWidth();
  1620. if (renderSpace.getScrollbarSize() > 0) {
  1621. if (relativeSize.getHeight() > 100) {
  1622. verticalScrollBar = true;
  1623. } else if (relativeSize.getHeight() < 0
  1624. && renderSpace.getHeight() > 0
  1625. && widget.getOffsetHeight() > renderSpace
  1626. .getHeight()) {
  1627. verticalScrollBar = true;
  1628. }
  1629. }
  1630. if (verticalScrollBar) {
  1631. width -= renderSpace.getScrollbarSize();
  1632. }
  1633. if (validatingLayouts && width <= 0) {
  1634. zeroWidthComponents.add(paintable);
  1635. }
  1636. width = (int) (width * relativeSize.getWidth() / 100.0);
  1637. if (width < 0) {
  1638. width = 0;
  1639. }
  1640. if (debugSizes) {
  1641. VConsole.log("Widget "
  1642. + Util.getSimpleName(widget)
  1643. + "/"
  1644. + paintableMap.getPid(paintable)
  1645. + " relative width "
  1646. + relativeSize.getWidth()
  1647. + "% of "
  1648. + renderSpace.getWidth()
  1649. + "px (reported by "
  1650. + Util.getSimpleName(parentPaintable)
  1651. + "/"
  1652. + (parentPaintable == null ? "?" : paintableMap
  1653. .getPid(parentPaintable)) + ") : " + width
  1654. + "px");
  1655. }
  1656. widget.setWidth(width + "px");
  1657. } else {
  1658. widget.setWidth(relativeSize.getWidth() + "%");
  1659. VConsole.error(Util.getLayout(widget).getClass().getName()
  1660. + " did not produce allocatedSpace for "
  1661. + widget.getClass().getName());
  1662. }
  1663. }
  1664. return true;
  1665. }
  1666. /**
  1667. * Converts relative sizes into pixel sizes.
  1668. *
  1669. * @param child
  1670. * @return true if the child has a relative size
  1671. */
  1672. public boolean handleComponentRelativeSize(Widget widget) {
  1673. return handleComponentRelativeSize(paintableMap.getPaintable(widget));
  1674. }
  1675. /**
  1676. * Gets the specified Paintables relative size (percent).
  1677. *
  1678. * @param widget
  1679. * the paintable whose size is needed
  1680. * @return the the size if the paintable is relatively sized, -1 otherwise
  1681. */
  1682. public FloatSize getRelativeSize(Widget widget) {
  1683. return paintableMap.getRelativeSize(paintableMap.getPaintable(widget));
  1684. }
  1685. /**
  1686. * Get either existing or new Paintable for given UIDL.
  1687. *
  1688. * If corresponding Paintable has been previously painted, return it.
  1689. * Otherwise create and register a new Paintable from UIDL. Caller must
  1690. * update the returned Paintable from UIDL after it has been connected to
  1691. * parent.
  1692. *
  1693. * @param uidl
  1694. * UIDL to create Paintable from.
  1695. * @return Either existing or new Paintable corresponding to UIDL.
  1696. */
  1697. public VPaintableWidget getPaintable(UIDL uidl) {
  1698. final String pid = uidl.getId();
  1699. // the actual content UIDL may be deferred, but it always contains
  1700. // enough information to create a paintable instance
  1701. if (!paintableMap.hasPaintable(pid)) {
  1702. // Create and register a new paintable if no old was found
  1703. VPaintableWidget p = widgetSet.createWidget(uidl.getTag(),
  1704. configuration);
  1705. p.setConnection(this);
  1706. p.setId(pid);
  1707. p.init();
  1708. paintableMap.registerPaintable(pid, p);
  1709. }
  1710. return (VPaintableWidget) paintableMap.getPaintable(pid);
  1711. }
  1712. /**
  1713. * Gets a recource that has been pre-loaded via UIDL, such as custom
  1714. * layouts.
  1715. *
  1716. * @param name
  1717. * identifier of the resource to get
  1718. * @return the resource
  1719. */
  1720. public String getResource(String name) {
  1721. return resourcesMap.get(name);
  1722. }
  1723. /**
  1724. * Singleton method to get instance of app's context menu.
  1725. *
  1726. * @return VContextMenu object
  1727. */
  1728. public VContextMenu getContextMenu() {
  1729. if (contextMenu == null) {
  1730. contextMenu = new VContextMenu();
  1731. DOM.setElementProperty(contextMenu.getElement(), "id",
  1732. "PID_VAADIN_CM");
  1733. }
  1734. return contextMenu;
  1735. }
  1736. /**
  1737. * Translates custom protocols in UIDL URI's to be recognizable by browser.
  1738. * All uri's from UIDL should be routed via this method before giving them
  1739. * to browser due URI's in UIDL may contain custom protocols like theme://.
  1740. *
  1741. * @param uidlUri
  1742. * Vaadin URI from uidl
  1743. * @return translated URI ready for browser
  1744. */
  1745. public String translateVaadinUri(String uidlUri) {
  1746. if (uidlUri == null) {
  1747. return null;
  1748. }
  1749. if (uidlUri.startsWith("theme://")) {
  1750. final String themeUri = configuration.getThemeUri();
  1751. if (themeUri == null) {
  1752. VConsole.error("Theme not set: ThemeResource will not be found. ("
  1753. + uidlUri + ")");
  1754. }
  1755. uidlUri = themeUri + uidlUri.substring(7);
  1756. }
  1757. if (uidlUri.startsWith("app://")) {
  1758. uidlUri = getAppUri() + uidlUri.substring(6);
  1759. }
  1760. return uidlUri;
  1761. }
  1762. /**
  1763. * Gets the URI for the current theme. Can be used to reference theme
  1764. * resources.
  1765. *
  1766. * @return URI to the current theme
  1767. */
  1768. public String getThemeUri() {
  1769. return configuration.getThemeUri();
  1770. }
  1771. /**
  1772. * Listens for Notification hide event, and redirects. Used for system
  1773. * messages, such as session expired.
  1774. *
  1775. */
  1776. private class NotificationRedirect implements VNotification.EventListener {
  1777. String url;
  1778. NotificationRedirect(String url) {
  1779. this.url = url;
  1780. }
  1781. public void notificationHidden(HideEvent event) {
  1782. redirect(url);
  1783. }
  1784. }
  1785. /* Extended title handling */
  1786. /**
  1787. * Data showed in tooltips are stored centrilized as it may be needed in
  1788. * varios place: caption, layouts, and in owner components themselves.
  1789. *
  1790. * Updating TooltipInfo is done in updateComponent method.
  1791. *
  1792. */
  1793. public TooltipInfo getTooltipTitleInfo(VPaintableWidget titleOwner,
  1794. Object key) {
  1795. if (null == titleOwner) {
  1796. return null;
  1797. }
  1798. return paintableMap.getTooltipInfo(titleOwner, key);
  1799. }
  1800. private final VTooltip tooltip = new VTooltip(this);
  1801. /**
  1802. * Component may want to delegate Tooltip handling to client. Layouts add
  1803. * Tooltip (description, errors) to caption, but some components may want
  1804. * them to appear one other elements too.
  1805. *
  1806. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1807. *
  1808. * @param event
  1809. * @param owner
  1810. */
  1811. public void handleTooltipEvent(Event event, VPaintableWidget owner) {
  1812. tooltip.handleTooltipEvent(event, owner, null);
  1813. }
  1814. /**
  1815. * Component may want to delegate Tooltip handling to client. Layouts add
  1816. * Tooltip (description, errors) to caption, but some components may want
  1817. * them to appear one other elements too.
  1818. *
  1819. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1820. *
  1821. * @param event
  1822. * @param owner
  1823. * @param key
  1824. * the key for tooltip if this is "additional" tooltip, null for
  1825. * components "main tooltip"
  1826. */
  1827. public void handleTooltipEvent(Event event, VPaintableWidget owner,
  1828. Object key) {
  1829. tooltip.handleTooltipEvent(event, owner, key);
  1830. }
  1831. /*
  1832. * Helper to run layout functions triggered by child components with a
  1833. * decent interval.
  1834. */
  1835. private final Timer layoutTimer = new Timer() {
  1836. private boolean isPending = false;
  1837. @Override
  1838. public void schedule(int delayMillis) {
  1839. if (!isPending) {
  1840. super.schedule(delayMillis);
  1841. isPending = true;
  1842. }
  1843. }
  1844. @Override
  1845. public void run() {
  1846. VConsole.log("Running re-layout of " + view.getClass().getName());
  1847. runDescendentsLayout(view.getWidgetForPaintable());
  1848. isPending = false;
  1849. }
  1850. };
  1851. private VPaintableMap paintableMap = GWT.create(VPaintableMap.class);
  1852. /**
  1853. * Components can call this function to run all layout functions. This is
  1854. * usually done, when component knows that its size has changed.
  1855. */
  1856. public void requestLayoutPhase() {
  1857. layoutTimer.schedule(500);
  1858. }
  1859. protected String getUidlSecurityKey() {
  1860. return uidlSecurityKey;
  1861. }
  1862. /**
  1863. * Use to notify that the given component's caption has changed; layouts may
  1864. * have to be recalculated.
  1865. *
  1866. * @param component
  1867. * the Paintable whose caption has changed
  1868. */
  1869. public void captionSizeUpdated(Widget widget) {
  1870. componentCaptionSizeChanges.add(widget);
  1871. }
  1872. /**
  1873. * Gets the main view, a.k.a top-level window.
  1874. *
  1875. * @return the main view
  1876. */
  1877. public VViewPaintable getView() {
  1878. return view;
  1879. }
  1880. /**
  1881. * If component has several tooltips in addition to the one provided by
  1882. * {@link com.vaadin.ui.AbstractComponent}, component can register them with
  1883. * this method.
  1884. * <p>
  1885. * Component must also pipe events to
  1886. * {@link #handleTooltipEvent(Event, VPaintableWidget, Object)} method.
  1887. * <p>
  1888. * This method can also be used to deregister tooltips by using null as
  1889. * tooltip
  1890. *
  1891. * @param paintable
  1892. * Paintable "owning" this tooltip
  1893. * @param key
  1894. * key assosiated with given tooltip. Can be any object. For
  1895. * example a related dom element. Same key must be given for
  1896. * {@link #handleTooltipEvent(Event, VPaintableWidget, Object)}
  1897. * method.
  1898. *
  1899. * @param tooltip
  1900. * the TooltipInfo object containing details shown in tooltip,
  1901. * null if deregistering tooltip
  1902. */
  1903. public void registerTooltip(VPaintableWidget paintable, Object key,
  1904. TooltipInfo tooltip) {
  1905. paintableMap.registerTooltip(paintable, key, tooltip);
  1906. }
  1907. /**
  1908. * Gets the {@link ApplicationConfiguration} for the current application.
  1909. *
  1910. * @see ApplicationConfiguration
  1911. * @return the configuration for this application
  1912. */
  1913. public ApplicationConfiguration getConfiguration() {
  1914. return configuration;
  1915. }
  1916. /**
  1917. * Checks if there is a registered server side listener for the event. The
  1918. * list of events which has server side listeners is updated automatically
  1919. * before the component is updated so the value is correct if called from
  1920. * updatedFromUIDL.
  1921. *
  1922. * @param eventIdentifier
  1923. * The identifier for the event
  1924. * @return true if at least one listener has been registered on server side
  1925. * for the event identified by eventIdentifier.
  1926. */
  1927. public boolean hasEventListeners(VPaintableWidget paintable,
  1928. String eventIdentifier) {
  1929. return paintableMap.hasEventListeners(paintable, eventIdentifier);
  1930. }
  1931. /**
  1932. * Adds the get parameters to the uri and returns the new uri that contains
  1933. * the parameters.
  1934. *
  1935. * @param uri
  1936. * The uri to which the parameters should be added.
  1937. * @param extraParams
  1938. * One or more parameters in the format "a=b" or "c=d&e=f". An
  1939. * empty string is allowed but will not modify the url.
  1940. * @return The modified URI with the get parameters in extraParams added.
  1941. */
  1942. public static String addGetParameters(String uri, String extraParams) {
  1943. if (extraParams == null || extraParams.length() == 0) {
  1944. return uri;
  1945. }
  1946. // RFC 3986: The query component is indicated by the first question
  1947. // mark ("?") character and terminated by a number sign ("#") character
  1948. // or by the end of the URI.
  1949. String fragment = null;
  1950. int hashPosition = uri.indexOf('#');
  1951. if (hashPosition != -1) {
  1952. // Fragment including "#"
  1953. fragment = uri.substring(hashPosition);
  1954. // The full uri before the fragment
  1955. uri = uri.substring(0, hashPosition);
  1956. }
  1957. if (uri.contains("?")) {
  1958. uri += "&";
  1959. } else {
  1960. uri += "?";
  1961. }
  1962. uri += extraParams;
  1963. if (fragment != null) {
  1964. uri += fragment;
  1965. }
  1966. return uri;
  1967. }
  1968. VPaintableMap getPaintableMap() {
  1969. return paintableMap;
  1970. }
  1971. @Deprecated
  1972. public void unregisterPaintable(VPaintable p) {
  1973. paintableMap.unregisterPaintable(p);
  1974. }
  1975. public VTooltip getVTooltip() {
  1976. return tooltip;
  1977. }
  1978. @Deprecated
  1979. public void handleWidgetTooltipEvent(Event event, Widget owner, Object key) {
  1980. handleTooltipEvent(event, getPaintableMap().getPaintable(owner), key);
  1981. }
  1982. @Deprecated
  1983. public void handleWidgetTooltipEvent(Event event, Widget owner) {
  1984. handleTooltipEvent(event, getPaintableMap().getPaintable(owner));
  1985. }
  1986. @Deprecated
  1987. public void registerWidgetTooltip(Widget owner, Object key, TooltipInfo info) {
  1988. registerTooltip(getPaintableMap().getPaintable(owner), key, info);
  1989. }
  1990. @Deprecated
  1991. public boolean hasWidgetEventListeners(Widget widget, String eventIdentifier) {
  1992. return hasEventListeners(getPaintableMap().getPaintable(widget),
  1993. eventIdentifier);
  1994. }
  1995. }