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

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