Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

AbstractCommunicationManager.java 73KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.server;
  5. import java.io.BufferedWriter;
  6. import java.io.CharArrayWriter;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import java.io.InputStreamReader;
  10. import java.io.OutputStream;
  11. import java.io.OutputStreamWriter;
  12. import java.io.PrintWriter;
  13. import java.io.Serializable;
  14. import java.lang.reflect.InvocationTargetException;
  15. import java.lang.reflect.Method;
  16. import java.net.URL;
  17. import java.security.GeneralSecurityException;
  18. import java.text.DateFormat;
  19. import java.text.DateFormatSymbols;
  20. import java.text.SimpleDateFormat;
  21. import java.util.ArrayList;
  22. import java.util.Calendar;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.Comparator;
  26. import java.util.GregorianCalendar;
  27. import java.util.HashMap;
  28. import java.util.HashSet;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import java.util.Locale;
  32. import java.util.Map;
  33. import java.util.Set;
  34. import java.util.StringTokenizer;
  35. import java.util.UUID;
  36. import javax.portlet.PortletRequest;
  37. import javax.portlet.PortletResponse;
  38. import javax.servlet.ServletRequest;
  39. import javax.servlet.ServletResponse;
  40. import com.vaadin.Application;
  41. import com.vaadin.Application.SystemMessages;
  42. import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
  43. import com.vaadin.external.org.apache.commons.fileupload.FileItemStream;
  44. import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
  45. import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
  46. import com.vaadin.external.org.apache.commons.fileupload.ProgressListener;
  47. import com.vaadin.terminal.ApplicationResource;
  48. import com.vaadin.terminal.DownloadStream;
  49. import com.vaadin.terminal.PaintException;
  50. import com.vaadin.terminal.PaintTarget;
  51. import com.vaadin.terminal.Paintable;
  52. import com.vaadin.terminal.Paintable.RepaintRequestEvent;
  53. import com.vaadin.terminal.Terminal.ErrorEvent;
  54. import com.vaadin.terminal.Terminal.ErrorListener;
  55. import com.vaadin.terminal.URIHandler;
  56. import com.vaadin.terminal.UploadStream;
  57. import com.vaadin.terminal.VariableOwner;
  58. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  59. import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
  60. import com.vaadin.ui.AbstractField;
  61. import com.vaadin.ui.Component;
  62. import com.vaadin.ui.DragAndDropWrapper;
  63. import com.vaadin.ui.Upload;
  64. import com.vaadin.ui.Upload.UploadException;
  65. import com.vaadin.ui.Window;
  66. /**
  67. * This is a common base class for the server-side implementations of the
  68. * communication system between the client code (compiled with GWT into
  69. * JavaScript) and the server side components. Its client side counterpart is
  70. * {@link ApplicationConnection}.
  71. *
  72. * A server side component sends its state to the client in a paint request (see
  73. * {@link Paintable} and {@link PaintTarget} on the server side). The client
  74. * widget receives these paint requests as calls to
  75. * {@link com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL()}. The client
  76. * component communicates back to the server by sending a list of variable
  77. * changes (see {@link ApplicationConnection#updateVariable()} and
  78. * {@link VariableOwner#changeVariables(Object, Map)}).
  79. *
  80. * TODO Document better!
  81. */
  82. @SuppressWarnings("serial")
  83. public abstract class AbstractCommunicationManager implements
  84. Paintable.RepaintRequestListener, Serializable {
  85. /**
  86. * Generic interface of a (HTTP or Portlet) request to the application.
  87. *
  88. * This is a wrapper interface that allows
  89. * {@link AbstractCommunicationManager} to use a unified API.
  90. *
  91. * @see javax.servlet.ServletRequest
  92. * @see javax.portlet.PortletRequest
  93. *
  94. * @author peholmst
  95. */
  96. protected interface Request {
  97. /**
  98. * Gets a {@link Session} wrapper implementation representing the
  99. * session for which this request was sent.
  100. *
  101. * Multiple Vaadin applications can be associated with a single session.
  102. *
  103. * @return Session
  104. */
  105. public Session getSession();
  106. /**
  107. * Are the applications in this session running in a portlet or directly
  108. * as servlets.
  109. *
  110. * @return true if in a portlet
  111. */
  112. public boolean isRunningInPortlet();
  113. /**
  114. * Get the named HTTP or portlet request parameter.
  115. *
  116. * @see javax.servlet.ServletRequest#getParameter(String)
  117. * @see javax.portlet.PortletRequest#getParameter(String)
  118. *
  119. * @param name
  120. * @return
  121. */
  122. public String getParameter(String name);
  123. /**
  124. * Returns the length of the request content that can be read from the
  125. * input stream returned by {@link #getInputStream()}.
  126. *
  127. * @return content length in bytes
  128. */
  129. public int getContentLength();
  130. /**
  131. * Returns an input stream from which the request content can be read.
  132. * The request content length can be obtained with
  133. * {@link #getContentLength()} without reading the full stream contents.
  134. *
  135. * @return
  136. * @throws IOException
  137. */
  138. public InputStream getInputStream() throws IOException;
  139. /**
  140. * Returns the request identifier that identifies the target Vaadin
  141. * window for the request.
  142. *
  143. * @return String identifier for the request target window
  144. */
  145. public String getRequestID();
  146. /**
  147. * @see javax.servlet.ServletRequest#getAttribute(String)
  148. * @see javax.portlet.PortletRequest#getAttribute(String)
  149. */
  150. public Object getAttribute(String name);
  151. /**
  152. * @see javax.servlet.ServletRequest#setAttribute(String, Object)
  153. * @see javax.portlet.PortletRequest#setAttribute(String, Object)
  154. */
  155. public void setAttribute(String name, Object value);
  156. /**
  157. * Gets the underlying request object. The request is typically either a
  158. * {@link ServletRequest} or a {@link PortletRequest}.
  159. *
  160. * @return wrapped request object
  161. */
  162. public Object getWrappedRequest();
  163. }
  164. /**
  165. * Generic interface of a (HTTP or Portlet) response from the application.
  166. *
  167. * This is a wrapper interface that allows
  168. * {@link AbstractCommunicationManager} to use a unified API.
  169. *
  170. * @see javax.servlet.ServletResponse
  171. * @see javax.portlet.PortletResponse
  172. *
  173. * @author peholmst
  174. */
  175. protected interface Response {
  176. /**
  177. * Gets the output stream to which the response can be written.
  178. *
  179. * @return
  180. * @throws IOException
  181. */
  182. public OutputStream getOutputStream() throws IOException;
  183. /**
  184. * Sets the MIME content type for the response to be communicated to the
  185. * browser.
  186. *
  187. * @param type
  188. */
  189. public void setContentType(String type);
  190. /**
  191. * Gets the wrapped response object, usually a class implementing either
  192. * {@link ServletResponse} or {@link PortletResponse}.
  193. *
  194. * @return wrapped request object
  195. */
  196. public Object getWrappedResponse();
  197. }
  198. /**
  199. * Generic wrapper interface for a (HTTP or Portlet) session.
  200. *
  201. * Several applications can be associated with a single session.
  202. *
  203. * TODO Document me!
  204. *
  205. * @see javax.servlet.http.HttpSession
  206. * @see javax.portlet.PortletSession
  207. *
  208. * @author peholmst
  209. */
  210. protected interface Session {
  211. public boolean isNew();
  212. public Object getAttribute(String name);
  213. public void setAttribute(String name, Object o);
  214. public int getMaxInactiveInterval();
  215. public Object getWrappedSession();
  216. }
  217. /**
  218. * TODO Document me!
  219. *
  220. * @author peholmst
  221. */
  222. protected interface Callback {
  223. public void criticalNotification(Request request, Response response,
  224. String cap, String msg, String details, String outOfSyncURL)
  225. throws IOException;
  226. public String getRequestPathInfo(Request request);
  227. public InputStream getThemeResourceAsStream(String themeName,
  228. String resource) throws IOException;
  229. }
  230. private static String GET_PARAM_REPAINT_ALL = "repaintAll";
  231. // flag used in the request to indicate that the security token should be
  232. // written to the response
  233. private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
  234. /* Variable records indexes */
  235. private static final int VAR_PID = 1;
  236. private static final int VAR_NAME = 2;
  237. private static final int VAR_TYPE = 3;
  238. private static final int VAR_VALUE = 0;
  239. private static final char VTYPE_PAINTABLE = 'p';
  240. private static final char VTYPE_BOOLEAN = 'b';
  241. private static final char VTYPE_DOUBLE = 'd';
  242. private static final char VTYPE_FLOAT = 'f';
  243. private static final char VTYPE_LONG = 'l';
  244. private static final char VTYPE_INTEGER = 'i';
  245. private static final char VTYPE_STRING = 's';
  246. private static final char VTYPE_ARRAY = 'a';
  247. private static final char VTYPE_STRINGARRAY = 'c';
  248. private static final char VTYPE_MAP = 'm';
  249. private static final String VAR_RECORD_SEPARATOR = "\u001e";
  250. private static final String VAR_FIELD_SEPARATOR = "\u001f";
  251. public static final String VAR_BURST_SEPARATOR = "\u001d";
  252. public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
  253. private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>();
  254. private static final int MAX_BUFFER_SIZE = 64 * 1024;
  255. private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts";
  256. private final ArrayList<Paintable> dirtyPaintables = new ArrayList<Paintable>();
  257. private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>();
  258. private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>();
  259. private int idSequence = 0;
  260. private final Application application;
  261. // Note that this is only accessed from synchronized block and
  262. // thus should be thread-safe.
  263. private String closingWindowName = null;
  264. private List<String> locales;
  265. private int pendingLocalesIndex;
  266. private int timeoutInterval = -1;
  267. private DragAndDropService dragAndDropService;
  268. private static int nextUnusedWindowSuffix = 1;
  269. /**
  270. * TODO New constructor - document me!
  271. *
  272. * @param application
  273. */
  274. public AbstractCommunicationManager(Application application) {
  275. this.application = application;
  276. requireLocale(application.getLocale().toString());
  277. }
  278. /**
  279. * Create an upload handler that is appropriate to the context in which the
  280. * application is being run (servlet or portlet).
  281. *
  282. * @return new {@link FileUpload} instance
  283. */
  284. protected abstract FileUpload createFileUpload();
  285. /**
  286. * TODO New method - document me!
  287. *
  288. * @param upload
  289. * @param request
  290. * @return
  291. * @throws IOException
  292. * @throws FileUploadException
  293. */
  294. protected abstract FileItemIterator getUploadItemIterator(
  295. FileUpload upload, Request request) throws IOException,
  296. FileUploadException;
  297. /**
  298. * TODO New method - document me!
  299. *
  300. * @param request
  301. * @param response
  302. * @throws IOException
  303. * @throws FileUploadException
  304. */
  305. protected void doHandleFileUpload(Request request, Response response)
  306. throws IOException, FileUploadException {
  307. // Create a new file upload handler
  308. final FileUpload upload = createFileUpload();
  309. final UploadProgressListener pl = new UploadProgressListener();
  310. upload.setProgressListener(pl);
  311. // Parse the request
  312. FileItemIterator iter;
  313. try {
  314. iter = getUploadItemIterator(upload, request);
  315. /*
  316. * ATM this loop is run only once as we are uploading one file per
  317. * request.
  318. */
  319. while (iter.hasNext()) {
  320. final FileItemStream item = iter.next();
  321. final String name = item.getFieldName();
  322. // Should report only the filename even if the browser sends the
  323. // path
  324. final String filename = removePath(item.getName());
  325. final String mimeType = item.getContentType();
  326. final InputStream stream = item.openStream();
  327. if (item.isFormField()) {
  328. // ignored, upload requests contains only files
  329. } else {
  330. final UploadStream upstream = new UploadStream() {
  331. public String getContentName() {
  332. return filename;
  333. }
  334. public String getContentType() {
  335. return mimeType;
  336. }
  337. public InputStream getStream() {
  338. return stream;
  339. }
  340. public String getStreamName() {
  341. return "stream";
  342. }
  343. };
  344. if (name.startsWith("XHRFILE")) {
  345. String[] split = item.getFieldName().substring(7)
  346. .split("\\.");
  347. DragAndDropWrapper ddw = (DragAndDropWrapper) idPaintableMap
  348. .get(split[0]);
  349. try {
  350. ddw.receiveFile(upstream, split[1]);
  351. } catch (UploadException e) {
  352. synchronized (application) {
  353. handleChangeVariablesError(application, ddw, e,
  354. new HashMap<String, Object>());
  355. }
  356. }
  357. } else {
  358. int separatorPos = name.lastIndexOf("_");
  359. final String pid = name.substring(0, separatorPos);
  360. final Upload uploadComponent = (Upload) idPaintableMap
  361. .get(pid);
  362. if (uploadComponent == null) {
  363. throw new FileUploadException(
  364. "Upload component not found");
  365. }
  366. if (uploadComponent.isReadOnly()) {
  367. throw new FileUploadException(
  368. "Warning: ignored file upload because upload component is set as read-only");
  369. }
  370. synchronized (application) {
  371. // put upload component into receiving state
  372. uploadComponent.startUpload();
  373. }
  374. // tell UploadProgressListener which component is
  375. // receiving
  376. // file
  377. pl.setUpload(uploadComponent);
  378. try {
  379. uploadComponent.receiveUpload(upstream);
  380. } catch (UploadException e) {
  381. // error happened while receiving file. Handle the
  382. // error in the same manner as it would have
  383. // happened in
  384. // variable change.
  385. synchronized (application) {
  386. handleChangeVariablesError(application,
  387. uploadComponent, e,
  388. new HashMap<String, Object>());
  389. }
  390. }
  391. }
  392. }
  393. }
  394. } catch (final FileUploadException e) {
  395. throw e;
  396. }
  397. sendUploadResponse(request, response);
  398. }
  399. /**
  400. * Removes any possible path information from the filename and returns the
  401. * filename. Separators / and \\ are used.
  402. *
  403. * @param name
  404. * @return
  405. */
  406. private static String removePath(String filename) {
  407. if (filename != null) {
  408. filename = filename.replaceAll("^.*[/\\\\]", "");
  409. }
  410. return filename;
  411. }
  412. /**
  413. * TODO document
  414. *
  415. * @param request
  416. * @param response
  417. * @throws IOException
  418. */
  419. protected void sendUploadResponse(Request request, Response response)
  420. throws IOException {
  421. response.setContentType("text/html");
  422. final OutputStream out = response.getOutputStream();
  423. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  424. new OutputStreamWriter(out, "UTF-8")));
  425. outWriter.print("<html><body>download handled</body></html>");
  426. outWriter.flush();
  427. out.close();
  428. }
  429. /**
  430. * Internally process a UIDL request from the client.
  431. *
  432. * This method calls
  433. * {@link #handleVariables(Request, Response, Callback, Application, Window)}
  434. * to process any changes to variables by the client and then repaints
  435. * affected components using {@link #paintAfterVariableChanges()}.
  436. *
  437. * Also, some cleanup is done when a request arrives for an application that
  438. * has already been closed.
  439. *
  440. * The method handleUidlRequest(...) in subclasses should call this method.
  441. *
  442. * TODO better documentation
  443. *
  444. * @param request
  445. * @param response
  446. * @param callback
  447. * @param window
  448. * target window for the UIDL request, can be null if target not
  449. * found
  450. * @throws IOException
  451. * @throws InvalidUIDLSecurityKeyException
  452. */
  453. protected void doHandleUidlRequest(Request request, Response response,
  454. Callback callback, Window window) throws IOException,
  455. InvalidUIDLSecurityKeyException {
  456. // repaint requested or session has timed out and new one is created
  457. boolean repaintAll;
  458. final OutputStream out;
  459. repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null);
  460. // || (request.getSession().isNew()); FIXME What the h*ll is this??
  461. out = response.getOutputStream();
  462. boolean analyzeLayouts = false;
  463. if (repaintAll) {
  464. // analyzing can be done only with repaintAll
  465. analyzeLayouts = (request.getParameter(GET_PARAM_ANALYZE_LAYOUTS) != null);
  466. }
  467. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  468. new OutputStreamWriter(out, "UTF-8")));
  469. // The rest of the process is synchronized with the application
  470. // in order to guarantee that no parallel variable handling is
  471. // made
  472. synchronized (application) {
  473. // Finds the window within the application
  474. if (application.isRunning()) {
  475. // Returns if no window found
  476. if (window == null) {
  477. // This should not happen, no windows exists but
  478. // application is still open.
  479. System.err
  480. .println("Warning, could not get window for application with request ID "
  481. + request.getRequestID());
  482. return;
  483. }
  484. } else {
  485. // application has been closed
  486. endApplication(request, response, application);
  487. return;
  488. }
  489. // Change all variables based on request parameters
  490. if (!handleVariables(request, response, callback, application,
  491. window)) {
  492. // var inconsistency; the client is probably out-of-sync
  493. SystemMessages ci = null;
  494. try {
  495. Method m = application.getClass().getMethod(
  496. "getSystemMessages", (Class[]) null);
  497. ci = (Application.SystemMessages) m.invoke(null,
  498. (Object[]) null);
  499. } catch (Exception e2) {
  500. // FIXME: Handle exception
  501. // Not critical, but something is still wrong; print
  502. // stacktrace
  503. e2.printStackTrace();
  504. }
  505. if (ci != null) {
  506. String msg = ci.getOutOfSyncMessage();
  507. String cap = ci.getOutOfSyncCaption();
  508. if (msg != null || cap != null) {
  509. callback.criticalNotification(request, response, cap,
  510. msg, null, ci.getOutOfSyncURL());
  511. // will reload page after this
  512. return;
  513. }
  514. }
  515. // No message to show, let's just repaint all.
  516. repaintAll = true;
  517. }
  518. paintAfterVariableChanges(request, response, callback, repaintAll,
  519. outWriter, window, analyzeLayouts);
  520. if (closingWindowName != null) {
  521. currentlyOpenWindowsInClient.remove(closingWindowName);
  522. closingWindowName = null;
  523. }
  524. }
  525. outWriter.close();
  526. }
  527. /**
  528. * TODO document
  529. *
  530. * @param request
  531. * @param response
  532. * @param callback
  533. * @param repaintAll
  534. * @param outWriter
  535. * @param window
  536. * @param analyzeLayouts
  537. * @throws PaintException
  538. * @throws IOException
  539. */
  540. private void paintAfterVariableChanges(Request request, Response response,
  541. Callback callback, boolean repaintAll, final PrintWriter outWriter,
  542. Window window, boolean analyzeLayouts) throws PaintException,
  543. IOException {
  544. if (repaintAll) {
  545. // If repaint is requested, clean all ids in this root window
  546. for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it
  547. .hasNext();) {
  548. final Component c = (Component) idPaintableMap.get(it.next());
  549. if (isChildOf(window, c)) {
  550. it.remove();
  551. paintableIdMap.remove(c);
  552. }
  553. }
  554. // clean WindowCache
  555. OpenWindowCache openWindowCache = currentlyOpenWindowsInClient
  556. .get(window.getName());
  557. if (openWindowCache != null) {
  558. openWindowCache.clear();
  559. }
  560. }
  561. // Removes application if it has stopped during variable changes
  562. if (!application.isRunning()) {
  563. endApplication(request, response, application);
  564. return;
  565. }
  566. // Sets the response type
  567. response.setContentType("application/json; charset=UTF-8");
  568. // some dirt to prevent cross site scripting
  569. outWriter.print("for(;;);[{");
  570. // security key
  571. Object writeSecurityTokenFlag = request
  572. .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
  573. if (writeSecurityTokenFlag != null) {
  574. String seckey = (String) request.getSession().getAttribute(
  575. ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
  576. if (seckey == null) {
  577. seckey = UUID.randomUUID().toString();
  578. request.getSession().setAttribute(
  579. ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey);
  580. }
  581. outWriter.print("\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID
  582. + "\":\"");
  583. outWriter.print(seckey);
  584. outWriter.print("\",");
  585. }
  586. outWriter.print("\"changes\":[");
  587. ArrayList<Paintable> paintables = null;
  588. // If the browser-window has been closed - we do not need to paint it at
  589. // all
  590. if (!window.getName().equals(closingWindowName)) {
  591. List<InvalidLayout> invalidComponentRelativeSizes = null;
  592. // re-get window - may have been changed
  593. Window newWindow = doGetApplicationWindow(request, callback,
  594. application, window);
  595. if (newWindow != window) {
  596. window = newWindow;
  597. repaintAll = true;
  598. }
  599. JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
  600. !repaintAll);
  601. OpenWindowCache windowCache = currentlyOpenWindowsInClient
  602. .get(window.getName());
  603. if (windowCache == null) {
  604. windowCache = new OpenWindowCache();
  605. currentlyOpenWindowsInClient.put(window.getName(), windowCache);
  606. }
  607. // Paints components
  608. if (repaintAll) {
  609. paintables = new ArrayList<Paintable>();
  610. paintables.add(window);
  611. // Reset sent locales
  612. locales = null;
  613. requireLocale(application.getLocale().toString());
  614. } else {
  615. // remove detached components from paintableIdMap so they
  616. // can be GC'ed
  617. for (Iterator<Paintable> it = paintableIdMap.keySet()
  618. .iterator(); it.hasNext();) {
  619. Component p = (Component) it.next();
  620. if (p.getApplication() == null) {
  621. idPaintableMap.remove(paintableIdMap.get(p));
  622. it.remove();
  623. dirtyPaintables.remove(p);
  624. p.removeListener(this);
  625. }
  626. }
  627. paintables = getDirtyVisibleComponents(window);
  628. }
  629. if (paintables != null) {
  630. // We need to avoid painting children before parent.
  631. // This is ensured by ordering list by depth in component
  632. // tree
  633. Collections.sort(paintables, new Comparator<Paintable>() {
  634. public int compare(Paintable o1, Paintable o2) {
  635. Component c1 = (Component) o1;
  636. Component c2 = (Component) o2;
  637. int d1 = 0;
  638. while (c1.getParent() != null) {
  639. d1++;
  640. c1 = c1.getParent();
  641. }
  642. int d2 = 0;
  643. while (c2.getParent() != null) {
  644. d2++;
  645. c2 = c2.getParent();
  646. }
  647. if (d1 < d2) {
  648. return -1;
  649. }
  650. if (d1 > d2) {
  651. return 1;
  652. }
  653. return 0;
  654. }
  655. });
  656. for (final Iterator<Paintable> i = paintables.iterator(); i
  657. .hasNext();) {
  658. final Paintable p = i.next();
  659. // TODO CLEAN
  660. if (p instanceof Window) {
  661. final Window w = (Window) p;
  662. if (w.getTerminal() == null) {
  663. w.setTerminal(application.getMainWindow()
  664. .getTerminal());
  665. }
  666. }
  667. /*
  668. * This does not seem to happen in tk5, but remember this
  669. * case: else if (p instanceof Component) { if (((Component)
  670. * p).getParent() == null || ((Component)
  671. * p).getApplication() == null) { // Component requested
  672. * repaint, but is no // longer attached: skip
  673. * paintablePainted(p); continue; } }
  674. */
  675. // TODO we may still get changes that have been
  676. // rendered already (changes with only cached flag)
  677. if (paintTarget.needsToBePainted(p)) {
  678. paintTarget.startTag("change");
  679. paintTarget.addAttribute("format", "uidl");
  680. final String pid = getPaintableId(p);
  681. paintTarget.addAttribute("pid", pid);
  682. p.paint(paintTarget);
  683. paintTarget.endTag("change");
  684. }
  685. paintablePainted(p);
  686. if (analyzeLayouts) {
  687. Window w = (Window) p;
  688. invalidComponentRelativeSizes = ComponentSizeValidator
  689. .validateComponentRelativeSizes(w.getContent(),
  690. null, null);
  691. // Also check any existing subwindows
  692. if (w.getChildWindows() != null) {
  693. for (Window subWindow : w.getChildWindows()) {
  694. invalidComponentRelativeSizes = ComponentSizeValidator
  695. .validateComponentRelativeSizes(
  696. subWindow.getContent(),
  697. invalidComponentRelativeSizes,
  698. null);
  699. }
  700. }
  701. }
  702. }
  703. }
  704. paintTarget.close();
  705. outWriter.print("]"); // close changes
  706. outWriter.print(", \"meta\" : {");
  707. boolean metaOpen = false;
  708. if (repaintAll) {
  709. metaOpen = true;
  710. outWriter.write("\"repaintAll\":true");
  711. if (analyzeLayouts) {
  712. outWriter.write(", \"invalidLayouts\":");
  713. outWriter.write("[");
  714. if (invalidComponentRelativeSizes != null) {
  715. boolean first = true;
  716. for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
  717. if (!first) {
  718. outWriter.write(",");
  719. } else {
  720. first = false;
  721. }
  722. invalidLayout.reportErrors(outWriter, this,
  723. System.err);
  724. }
  725. }
  726. outWriter.write("]");
  727. }
  728. }
  729. SystemMessages ci = null;
  730. try {
  731. Method m = application.getClass().getMethod(
  732. "getSystemMessages", (Class[]) null);
  733. ci = (Application.SystemMessages) m.invoke(null,
  734. (Object[]) null);
  735. } catch (NoSuchMethodException e1) {
  736. e1.printStackTrace();
  737. } catch (IllegalArgumentException e) {
  738. e.printStackTrace();
  739. } catch (IllegalAccessException e) {
  740. e.printStackTrace();
  741. } catch (InvocationTargetException e) {
  742. e.printStackTrace();
  743. }
  744. // meta instruction for client to enable auto-forward to
  745. // sessionExpiredURL after timer expires.
  746. if (ci != null && ci.getSessionExpiredMessage() == null
  747. && ci.getSessionExpiredCaption() == null
  748. && ci.isSessionExpiredNotificationEnabled()) {
  749. int newTimeoutInterval = request.getSession()
  750. .getMaxInactiveInterval();
  751. if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
  752. String escapedURL = ci.getSessionExpiredURL() == null ? ""
  753. : ci.getSessionExpiredURL().replace("/", "\\/");
  754. if (metaOpen) {
  755. outWriter.write(",");
  756. }
  757. outWriter.write("\"timedRedirect\":{\"interval\":"
  758. + (newTimeoutInterval + 15) + ",\"url\":\""
  759. + escapedURL + "\"}");
  760. metaOpen = true;
  761. }
  762. timeoutInterval = newTimeoutInterval;
  763. }
  764. outWriter.print("}, \"resources\" : {");
  765. // Precache custom layouts
  766. String themeName = window.getTheme();
  767. String requestThemeName = request.getParameter("theme");
  768. if (requestThemeName != null) {
  769. themeName = requestThemeName;
  770. }
  771. if (themeName == null) {
  772. themeName = AbstractApplicationServlet.getDefaultTheme();
  773. }
  774. // TODO We should only precache the layouts that are not
  775. // cached already (plagiate from usedPaintableTypes)
  776. int resourceIndex = 0;
  777. for (final Iterator<Object> i = paintTarget.getUsedResources()
  778. .iterator(); i.hasNext();) {
  779. final String resource = (String) i.next();
  780. InputStream is = null;
  781. try {
  782. is = callback.getThemeResourceAsStream(themeName, resource);
  783. } catch (final Exception e) {
  784. // FIXME: Handle exception
  785. e.printStackTrace();
  786. }
  787. if (is != null) {
  788. outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
  789. + resource + "\" : ");
  790. final StringBuffer layout = new StringBuffer();
  791. try {
  792. final InputStreamReader r = new InputStreamReader(is,
  793. "UTF-8");
  794. final char[] buffer = new char[20000];
  795. int charsRead = 0;
  796. while ((charsRead = r.read(buffer)) > 0) {
  797. layout.append(buffer, 0, charsRead);
  798. }
  799. r.close();
  800. } catch (final java.io.IOException e) {
  801. // FIXME: Handle exception
  802. System.err.println("Resource transfer failed: "
  803. + request.getRequestID() + ". ("
  804. + e.getMessage() + ")");
  805. }
  806. outWriter.print("\""
  807. + JsonPaintTarget.escapeJSON(layout.toString())
  808. + "\"");
  809. } else {
  810. // FIXME: Handle exception
  811. System.err.println("CustomLayout not found: " + resource);
  812. }
  813. }
  814. outWriter.print("}");
  815. Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget
  816. .getUsedPaintableTypes();
  817. boolean typeMappingsOpen = false;
  818. for (Class<? extends Paintable> class1 : usedPaintableTypes) {
  819. if (windowCache.cache(class1)) {
  820. // client does not know the mapping key for this type, send
  821. // mapping to client
  822. if (!typeMappingsOpen) {
  823. typeMappingsOpen = true;
  824. outWriter.print(", \"typeMappings\" : { ");
  825. } else {
  826. outWriter.print(" , ");
  827. }
  828. String canonicalName = class1.getCanonicalName();
  829. outWriter.print("\"");
  830. outWriter.print(canonicalName);
  831. outWriter.print("\" : ");
  832. outWriter.print(getTagForType(class1));
  833. }
  834. }
  835. if (typeMappingsOpen) {
  836. outWriter.print(" }");
  837. }
  838. // add any pending locale definitions requested by the client
  839. printLocaleDeclarations(outWriter);
  840. if (dragAndDropService != null) {
  841. dragAndDropService.printJSONResponse(outWriter);
  842. }
  843. outWriter.print("}]");
  844. }
  845. outWriter.close();
  846. }
  847. /**
  848. * TODO document
  849. *
  850. * If this method returns false, something was submitted that we did not
  851. * expect; this is probably due to the client being out-of-sync and sending
  852. * variable changes for non-existing pids
  853. *
  854. * @return true if successful, false if there was an inconsistency
  855. */
  856. private boolean handleVariables(Request request, Response response,
  857. Callback callback, Application application2, Window window)
  858. throws IOException, InvalidUIDLSecurityKeyException {
  859. boolean success = true;
  860. int contentLength = request.getContentLength();
  861. if (contentLength > 0) {
  862. String changes = readRequest(request);
  863. // Manage bursts one by one
  864. final String[] bursts = changes.split(VAR_BURST_SEPARATOR);
  865. // Security: double cookie submission pattern unless disabled by
  866. // property
  867. if (!"true"
  868. .equals(application2
  869. .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))) {
  870. if (bursts.length == 1 && "init".equals(bursts[0])) {
  871. // init request; don't handle any variables, key sent in
  872. // response.
  873. request.setAttribute(WRITE_SECURITY_TOKEN_FLAG, true);
  874. return true;
  875. } else {
  876. // ApplicationServlet has stored the security token in the
  877. // session; check that it matched the one sent in the UIDL
  878. String sessId = (String) request.getSession().getAttribute(
  879. ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
  880. if (sessId == null || !sessId.equals(bursts[0])) {
  881. throw new InvalidUIDLSecurityKeyException(
  882. "Security key mismatch");
  883. }
  884. }
  885. }
  886. for (int bi = 1; bi < bursts.length; bi++) {
  887. // extract variables to two dim string array
  888. final String[] tmp = bursts[bi].split(VAR_RECORD_SEPARATOR);
  889. final String[][] variableRecords = new String[tmp.length][4];
  890. for (int i = 0; i < tmp.length; i++) {
  891. variableRecords[i] = tmp[i].split(VAR_FIELD_SEPARATOR);
  892. }
  893. for (int i = 0; i < variableRecords.length; i++) {
  894. String[] variable = variableRecords[i];
  895. String[] nextVariable = null;
  896. if (i + 1 < variableRecords.length) {
  897. nextVariable = variableRecords[i + 1];
  898. }
  899. final VariableOwner owner = getVariableOwner(variable[VAR_PID]);
  900. if (owner != null && owner.isEnabled()) {
  901. // TODO this should be Map<String, Object>, but the
  902. // VariableOwner API does not guarantee the key is a
  903. // string
  904. Map<String, Object> m;
  905. if (nextVariable != null
  906. && variable[VAR_PID]
  907. .equals(nextVariable[VAR_PID])) {
  908. // we have more than one value changes in row for
  909. // one variable owner, collect em in HashMap
  910. m = new HashMap<String, Object>();
  911. m.put(variable[VAR_NAME],
  912. convertVariableValue(
  913. variable[VAR_TYPE].charAt(0),
  914. variable[VAR_VALUE]));
  915. } else {
  916. // use optimized single value map
  917. m = Collections.singletonMap(
  918. variable[VAR_NAME],
  919. convertVariableValue(
  920. variable[VAR_TYPE].charAt(0),
  921. variable[VAR_VALUE]));
  922. }
  923. // collect following variable changes for this owner
  924. while (nextVariable != null
  925. && variable[VAR_PID]
  926. .equals(nextVariable[VAR_PID])) {
  927. i++;
  928. variable = nextVariable;
  929. if (i + 1 < variableRecords.length) {
  930. nextVariable = variableRecords[i + 1];
  931. } else {
  932. nextVariable = null;
  933. }
  934. m.put(variable[VAR_NAME],
  935. convertVariableValue(
  936. variable[VAR_TYPE].charAt(0),
  937. variable[VAR_VALUE]));
  938. }
  939. try {
  940. owner.changeVariables(request, m);
  941. // Special-case of closing browser-level windows:
  942. // track browser-windows currently open in client
  943. if (owner instanceof Window
  944. && ((Window) owner).getParent() == null) {
  945. final Boolean close = (Boolean) m.get("close");
  946. if (close != null && close.booleanValue()) {
  947. closingWindowName = ((Window) owner)
  948. .getName();
  949. }
  950. }
  951. } catch (Exception e) {
  952. if (owner instanceof Component) {
  953. handleChangeVariablesError(application2,
  954. (Component) owner, e, m);
  955. } else {
  956. // TODO DragDropService error handling
  957. throw new RuntimeException(e);
  958. }
  959. }
  960. } else {
  961. // Handle special case where window-close is called
  962. // after the window has been removed from the
  963. // application or the application has closed
  964. if ("close".equals(variable[VAR_NAME])
  965. && "true".equals(variable[VAR_VALUE])) {
  966. // Silently ignore this
  967. continue;
  968. }
  969. // Ignore variable change
  970. String msg = "Warning: Ignoring variable change for ";
  971. if (owner != null) {
  972. msg += "disabled component " + owner.getClass();
  973. String caption = ((Component) owner).getCaption();
  974. if (caption != null) {
  975. msg += ", caption=" + caption;
  976. }
  977. } else {
  978. msg += "non-existent component, VAR_PID="
  979. + variable[VAR_PID];
  980. success = false;
  981. }
  982. System.err.println(msg);
  983. continue;
  984. }
  985. }
  986. // In case that there were multiple bursts, we know that this is
  987. // a special synchronous case for closing window. Thus we are
  988. // not interested in sending any UIDL changes back to client.
  989. // Still we must clear component tree between bursts to ensure
  990. // that no removed components are updated. The painting after
  991. // the last burst is handled normally by the calling method.
  992. if (bi < bursts.length - 1) {
  993. // We will be discarding all changes
  994. final PrintWriter outWriter = new PrintWriter(
  995. new CharArrayWriter());
  996. paintAfterVariableChanges(request, response, callback,
  997. true, outWriter, window, false);
  998. }
  999. }
  1000. }
  1001. return success;
  1002. }
  1003. private VariableOwner getVariableOwner(String string) {
  1004. VariableOwner owner = (VariableOwner) idPaintableMap.get(string);
  1005. if (owner == null && string.startsWith("DD")) {
  1006. return getDragAndDropService();
  1007. }
  1008. return owner;
  1009. }
  1010. private VariableOwner getDragAndDropService() {
  1011. if (dragAndDropService == null) {
  1012. dragAndDropService = new DragAndDropService(this);
  1013. }
  1014. return dragAndDropService;
  1015. }
  1016. /**
  1017. * Reads the request data from the Request and returns it converted to an
  1018. * UTF-8 string.
  1019. *
  1020. * @param request
  1021. * @return
  1022. * @throws IOException
  1023. */
  1024. private static String readRequest(Request request) throws IOException {
  1025. int requestLength = request.getContentLength();
  1026. byte[] buffer = new byte[requestLength];
  1027. InputStream inputStream = request.getInputStream();
  1028. int bytesRemaining = requestLength;
  1029. while (bytesRemaining > 0) {
  1030. int bytesToRead = Math.min(bytesRemaining, MAX_BUFFER_SIZE);
  1031. int bytesRead = inputStream.read(buffer, requestLength
  1032. - bytesRemaining, bytesToRead);
  1033. if (bytesRead == -1) {
  1034. break;
  1035. }
  1036. bytesRemaining -= bytesRead;
  1037. }
  1038. String result = new String(buffer, "utf-8");
  1039. return result;
  1040. }
  1041. public class ErrorHandlerErrorEvent implements ErrorEvent, Serializable {
  1042. private final Throwable throwable;
  1043. public ErrorHandlerErrorEvent(Throwable throwable) {
  1044. this.throwable = throwable;
  1045. }
  1046. public Throwable getThrowable() {
  1047. return throwable;
  1048. }
  1049. }
  1050. /**
  1051. * Handles an error (exception) that occurred when processing variable
  1052. * changes from the client or a failure of a file upload.
  1053. *
  1054. * For {@link AbstractField} components,
  1055. * {@link AbstractField#handleError(com.vaadin.ui.AbstractComponent.ComponentErrorEvent)}
  1056. * is called. In all other cases (or if the field does not handle the
  1057. * error), {@link ErrorListener#terminalError(ErrorEvent)} for the
  1058. * application error handler is called.
  1059. *
  1060. * @param application
  1061. * @param owner
  1062. * component that the error concerns
  1063. * @param e
  1064. * exception that occurred
  1065. * @param m
  1066. * map from variable names to values
  1067. */
  1068. private void handleChangeVariablesError(Application application,
  1069. Component owner, Exception e, Map<? extends Object, Object> m) {
  1070. boolean handled = false;
  1071. ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent(
  1072. owner, e, m);
  1073. if (owner instanceof AbstractField) {
  1074. try {
  1075. handled = ((AbstractField) owner).handleError(errorEvent);
  1076. } catch (Exception handlerException) {
  1077. /*
  1078. * If there is an error in the component error handler we pass
  1079. * the that error to the application error handler and continue
  1080. * processing the actual error
  1081. */
  1082. application.getErrorHandler().terminalError(
  1083. new ErrorHandlerErrorEvent(handlerException));
  1084. handled = false;
  1085. }
  1086. }
  1087. if (!handled) {
  1088. application.getErrorHandler().terminalError(errorEvent);
  1089. }
  1090. }
  1091. private Object convertVariableValue(char variableType, String strValue) {
  1092. Object val = null;
  1093. switch (variableType) {
  1094. case VTYPE_ARRAY:
  1095. val = convertArray(strValue);
  1096. break;
  1097. case VTYPE_MAP:
  1098. val = convertMap(strValue);
  1099. break;
  1100. case VTYPE_STRINGARRAY:
  1101. val = convertStringArray(strValue);
  1102. break;
  1103. case VTYPE_STRING:
  1104. val = strValue;
  1105. break;
  1106. case VTYPE_INTEGER:
  1107. val = Integer.valueOf(strValue);
  1108. break;
  1109. case VTYPE_LONG:
  1110. val = Long.valueOf(strValue);
  1111. break;
  1112. case VTYPE_FLOAT:
  1113. val = Float.valueOf(strValue);
  1114. break;
  1115. case VTYPE_DOUBLE:
  1116. val = Double.valueOf(strValue);
  1117. break;
  1118. case VTYPE_BOOLEAN:
  1119. val = Boolean.valueOf(strValue);
  1120. break;
  1121. case VTYPE_PAINTABLE:
  1122. val = idPaintableMap.get(strValue);
  1123. break;
  1124. }
  1125. return val;
  1126. }
  1127. private Object convertMap(String strValue) {
  1128. String[] parts = strValue.split(VAR_ARRAYITEM_SEPARATOR);
  1129. HashMap<String, Object> map = new HashMap<String, Object>();
  1130. for (int i = 0; i < parts.length; i += 2) {
  1131. String key = parts[i];
  1132. if (key.length() > 0) {
  1133. char variabletype = key.charAt(0);
  1134. Object value = convertVariableValue(variabletype, parts[i + 1]);
  1135. map.put(key.substring(1), value);
  1136. }
  1137. }
  1138. return map;
  1139. }
  1140. private String[] convertStringArray(String strValue) {
  1141. // need to return delimiters and filter them out; otherwise empty
  1142. // strings are lost
  1143. // an extra empty delimiter at the end is automatically eliminated
  1144. StringTokenizer tokenizer = new StringTokenizer(strValue,
  1145. VAR_ARRAYITEM_SEPARATOR, true);
  1146. List<String> tokens = new ArrayList<String>();
  1147. String prevToken = VAR_ARRAYITEM_SEPARATOR;
  1148. while (tokenizer.hasMoreTokens()) {
  1149. String token = tokenizer.nextToken();
  1150. if (!VAR_ARRAYITEM_SEPARATOR.equals(token)) {
  1151. tokens.add(token);
  1152. } else if (VAR_ARRAYITEM_SEPARATOR.equals(prevToken)) {
  1153. tokens.add("");
  1154. }
  1155. prevToken = token;
  1156. }
  1157. return tokens.toArray(new String[tokens.size()]);
  1158. }
  1159. private Object convertArray(String strValue) {
  1160. String[] val = strValue.split(VAR_ARRAYITEM_SEPARATOR);
  1161. if (val.length == 0 || (val.length == 1 && val[0].length() == 0)) {
  1162. return new Object[0];
  1163. }
  1164. Object[] values = new Object[val.length];
  1165. for (int i = 0; i < values.length; i++) {
  1166. String string = val[i];
  1167. // first char of string is type
  1168. char variableType = string.charAt(0);
  1169. values[i] = convertVariableValue(variableType, string.substring(1));
  1170. }
  1171. return values;
  1172. }
  1173. /**
  1174. * Prints the queued (pending) locale definitions to a {@link PrintWriter}
  1175. * in a (UIDL) format that can be sent to the client and used there in
  1176. * formatting dates, times etc.
  1177. *
  1178. * @param outWriter
  1179. */
  1180. private void printLocaleDeclarations(PrintWriter outWriter) {
  1181. /*
  1182. * ----------------------------- Sending Locale sensitive date
  1183. * -----------------------------
  1184. */
  1185. // Send locale informations to client
  1186. outWriter.print(", \"locales\":[");
  1187. for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
  1188. final Locale l = generateLocale(locales.get(pendingLocalesIndex));
  1189. // Locale name
  1190. outWriter.print("{\"name\":\"" + l.toString() + "\",");
  1191. /*
  1192. * Month names (both short and full)
  1193. */
  1194. final DateFormatSymbols dfs = new DateFormatSymbols(l);
  1195. final String[] short_months = dfs.getShortMonths();
  1196. final String[] months = dfs.getMonths();
  1197. outWriter.print("\"smn\":[\""
  1198. + // ShortMonthNames
  1199. short_months[0] + "\",\"" + short_months[1] + "\",\""
  1200. + short_months[2] + "\",\"" + short_months[3] + "\",\""
  1201. + short_months[4] + "\",\"" + short_months[5] + "\",\""
  1202. + short_months[6] + "\",\"" + short_months[7] + "\",\""
  1203. + short_months[8] + "\",\"" + short_months[9] + "\",\""
  1204. + short_months[10] + "\",\"" + short_months[11] + "\""
  1205. + "],");
  1206. outWriter.print("\"mn\":[\""
  1207. + // MonthNames
  1208. months[0] + "\",\"" + months[1] + "\",\"" + months[2]
  1209. + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
  1210. + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
  1211. + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
  1212. + months[10] + "\",\"" + months[11] + "\"" + "],");
  1213. /*
  1214. * Weekday names (both short and full)
  1215. */
  1216. final String[] short_days = dfs.getShortWeekdays();
  1217. final String[] days = dfs.getWeekdays();
  1218. outWriter.print("\"sdn\":[\""
  1219. + // ShortDayNames
  1220. short_days[1] + "\",\"" + short_days[2] + "\",\""
  1221. + short_days[3] + "\",\"" + short_days[4] + "\",\""
  1222. + short_days[5] + "\",\"" + short_days[6] + "\",\""
  1223. + short_days[7] + "\"" + "],");
  1224. outWriter.print("\"dn\":[\""
  1225. + // DayNames
  1226. days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
  1227. + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
  1228. + days[7] + "\"" + "],");
  1229. /*
  1230. * First day of week (0 = sunday, 1 = monday)
  1231. */
  1232. final Calendar cal = new GregorianCalendar(l);
  1233. outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
  1234. /*
  1235. * Date formatting (MM/DD/YYYY etc.)
  1236. */
  1237. DateFormat dateFormat = DateFormat.getDateTimeInstance(
  1238. DateFormat.SHORT, DateFormat.SHORT, l);
  1239. if (!(dateFormat instanceof SimpleDateFormat)) {
  1240. System.err
  1241. .println("Unable to get default date pattern for locale "
  1242. + l.toString());
  1243. dateFormat = new SimpleDateFormat();
  1244. }
  1245. final String df = ((SimpleDateFormat) dateFormat).toPattern();
  1246. int timeStart = df.indexOf("H");
  1247. if (timeStart < 0) {
  1248. timeStart = df.indexOf("h");
  1249. }
  1250. final int ampm_first = df.indexOf("a");
  1251. // E.g. in Korean locale AM/PM is before h:mm
  1252. // TODO should take that into consideration on client-side as well,
  1253. // now always h:mm a
  1254. if (ampm_first > 0 && ampm_first < timeStart) {
  1255. timeStart = ampm_first;
  1256. }
  1257. // Hebrew locale has time before the date
  1258. final boolean timeFirst = timeStart == 0;
  1259. String dateformat;
  1260. if (timeFirst) {
  1261. int dateStart = df.indexOf(' ');
  1262. if (ampm_first > dateStart) {
  1263. dateStart = df.indexOf(' ', ampm_first);
  1264. }
  1265. dateformat = df.substring(dateStart + 1);
  1266. } else {
  1267. dateformat = df.substring(0, timeStart - 1);
  1268. }
  1269. outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
  1270. /*
  1271. * Time formatting (24 or 12 hour clock and AM/PM suffixes)
  1272. */
  1273. final String timeformat = df.substring(timeStart, df.length());
  1274. /*
  1275. * Doesn't return second or milliseconds.
  1276. *
  1277. * We use timeformat to determine 12/24-hour clock
  1278. */
  1279. final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
  1280. // TODO there are other possibilities as well, like 'h' in french
  1281. // (ignore them, too complicated)
  1282. final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
  1283. : ":";
  1284. // outWriter.print("\"tf\":\"" + timeformat + "\",");
  1285. outWriter.print("\"thc\":" + twelve_hour_clock + ",");
  1286. outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
  1287. if (twelve_hour_clock) {
  1288. final String[] ampm = dfs.getAmPmStrings();
  1289. outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
  1290. + "\"]");
  1291. }
  1292. outWriter.print("}");
  1293. if (pendingLocalesIndex < locales.size() - 1) {
  1294. outWriter.print(",");
  1295. }
  1296. }
  1297. outWriter.print("]"); // Close locales
  1298. }
  1299. /**
  1300. * TODO New method - document me!
  1301. *
  1302. * @param request
  1303. * @param callback
  1304. * @param application
  1305. * @param assumedWindow
  1306. * @return
  1307. */
  1308. protected Window doGetApplicationWindow(Request request, Callback callback,
  1309. Application application, Window assumedWindow) {
  1310. Window window = null;
  1311. // If the client knows which window to use, use it if possible
  1312. String windowClientRequestedName = request.getParameter("windowName");
  1313. if (assumedWindow != null
  1314. && application.getWindows().contains(assumedWindow)) {
  1315. windowClientRequestedName = assumedWindow.getName();
  1316. }
  1317. if (windowClientRequestedName != null) {
  1318. window = application.getWindow(windowClientRequestedName);
  1319. if (window != null) {
  1320. return window;
  1321. }
  1322. }
  1323. // If client does not know what window it wants
  1324. if (window == null && !request.isRunningInPortlet()) {
  1325. // This is only supported if the application is running inside a
  1326. // servlet
  1327. // Get the path from URL
  1328. String path = callback.getRequestPathInfo(request);
  1329. if (path != null && path.startsWith("/UIDL")) {
  1330. path = path.substring("/UIDL".length());
  1331. }
  1332. // If the path is specified, create name from it
  1333. if (path != null && path.length() > 0 && !path.equals("/")) {
  1334. String windowUrlName = null;
  1335. if (path.charAt(0) == '/') {
  1336. path = path.substring(1);
  1337. }
  1338. final int index = path.indexOf('/');
  1339. if (index < 0) {
  1340. windowUrlName = path;
  1341. path = "";
  1342. } else {
  1343. windowUrlName = path.substring(0, index);
  1344. path = path.substring(index + 1);
  1345. }
  1346. window = application.getWindow(windowUrlName);
  1347. }
  1348. }
  1349. // By default, use mainwindow
  1350. if (window == null) {
  1351. window = application.getMainWindow();
  1352. // Return null if no main window was found
  1353. if (window == null) {
  1354. return null;
  1355. }
  1356. }
  1357. // If the requested window is already open, resolve conflict
  1358. if (currentlyOpenWindowsInClient.containsKey(window.getName())) {
  1359. String newWindowName = window.getName();
  1360. synchronized (AbstractCommunicationManager.class) {
  1361. while (currentlyOpenWindowsInClient.containsKey(newWindowName)) {
  1362. newWindowName = window.getName() + "_"
  1363. + nextUnusedWindowSuffix++;
  1364. }
  1365. }
  1366. window = application.getWindow(newWindowName);
  1367. // If everything else fails, use main window even in case of
  1368. // conflicts
  1369. if (window == null) {
  1370. window = application.getMainWindow();
  1371. }
  1372. }
  1373. return window;
  1374. }
  1375. /**
  1376. * Ends the Application.
  1377. *
  1378. * The browser is redirected to the Application logout URL set with
  1379. * {@link Application#setLogoutURL(String)}, or to the application URL if no
  1380. * logout URL is given.
  1381. *
  1382. * @param request
  1383. * the request instance.
  1384. * @param response
  1385. * the response to write to.
  1386. * @param application
  1387. * the Application to end.
  1388. * @throws IOException
  1389. * if the writing failed due to input/output error.
  1390. */
  1391. private void endApplication(Request request, Response response,
  1392. Application application) throws IOException {
  1393. String logoutUrl = application.getLogoutURL();
  1394. if (logoutUrl == null) {
  1395. logoutUrl = application.getURL().toString();
  1396. }
  1397. // clients JS app is still running, send a special json file to tell
  1398. // client that application has quit and where to point browser now
  1399. // Set the response type
  1400. final OutputStream out = response.getOutputStream();
  1401. response.setContentType("application/json; charset=UTF-8");
  1402. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  1403. new OutputStreamWriter(out, "UTF-8")));
  1404. outWriter.print("for(;;);[{");
  1405. outWriter.print("\"redirect\":{");
  1406. outWriter.write("\"url\":\"" + logoutUrl + "\"}}]");
  1407. outWriter.flush();
  1408. outWriter.close();
  1409. out.flush();
  1410. }
  1411. /**
  1412. * Gets the Paintable Id. If Paintable has debug id set it will be used
  1413. * prefixed with "PID_S". Otherwise a sequenced ID is created.
  1414. *
  1415. * @param paintable
  1416. * @return the paintable Id.
  1417. */
  1418. public String getPaintableId(Paintable paintable) {
  1419. String id = paintableIdMap.get(paintable);
  1420. if (id == null) {
  1421. // use testing identifier as id if set
  1422. id = paintable.getDebugId();
  1423. if (id == null) {
  1424. id = "PID" + Integer.toString(idSequence++);
  1425. } else {
  1426. id = "PID_S" + id;
  1427. }
  1428. Paintable old = idPaintableMap.put(id, paintable);
  1429. if (old != null && old != paintable) {
  1430. /*
  1431. * Two paintables have the same id. We still make sure the old
  1432. * one is a component which is still attached to the
  1433. * application. This is just a precaution and should not be
  1434. * absolutely necessary.
  1435. */
  1436. if (old instanceof Component
  1437. && ((Component) old).getApplication() != null) {
  1438. throw new IllegalStateException("Two paintables ("
  1439. + paintable.getClass().getSimpleName() + ","
  1440. + old.getClass().getSimpleName()
  1441. + ") have been assigned the same id: "
  1442. + paintable.getDebugId());
  1443. }
  1444. }
  1445. paintableIdMap.put(paintable, id);
  1446. }
  1447. return id;
  1448. }
  1449. public boolean hasPaintableId(Paintable paintable) {
  1450. return paintableIdMap.containsKey(paintable);
  1451. }
  1452. /**
  1453. * Returns dirty components which are in given window. Components in an
  1454. * invisible subtrees are omitted.
  1455. *
  1456. * @param w
  1457. * root window for which dirty components is to be fetched
  1458. * @return
  1459. */
  1460. private ArrayList<Paintable> getDirtyVisibleComponents(Window w) {
  1461. final ArrayList<Paintable> resultset = new ArrayList<Paintable>(
  1462. dirtyPaintables);
  1463. // The following algorithm removes any components that would be painted
  1464. // as a direct descendant of other components from the dirty components
  1465. // list. The result is that each component should be painted exactly
  1466. // once and any unmodified components will be painted as "cached=true".
  1467. for (final Iterator<Paintable> i = dirtyPaintables.iterator(); i
  1468. .hasNext();) {
  1469. final Paintable p = i.next();
  1470. if (p instanceof Component) {
  1471. final Component component = (Component) p;
  1472. if (component.getApplication() == null) {
  1473. // component is detached after requestRepaint is called
  1474. resultset.remove(p);
  1475. i.remove();
  1476. } else {
  1477. Window componentsRoot = component.getWindow();
  1478. if (componentsRoot == null) {
  1479. // This should not happen unless somebody has overriden
  1480. // getApplication or getWindow in an illegal way.
  1481. throw new IllegalStateException(
  1482. "component.getWindow() returned null for a component attached to the application");
  1483. }
  1484. if (componentsRoot.getParent() != null) {
  1485. // this is a subwindow
  1486. componentsRoot = (Window) componentsRoot.getParent();
  1487. }
  1488. if (componentsRoot != w) {
  1489. resultset.remove(p);
  1490. } else if (component.getParent() != null
  1491. && !component.getParent().isVisible()) {
  1492. /*
  1493. * Do not return components in an invisible subtree.
  1494. *
  1495. * Components that are invisible in visible subree, must
  1496. * be rendered (to let client know that they need to be
  1497. * hidden).
  1498. */
  1499. resultset.remove(p);
  1500. }
  1501. }
  1502. }
  1503. }
  1504. return resultset;
  1505. }
  1506. /**
  1507. * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent)
  1508. */
  1509. public void repaintRequested(RepaintRequestEvent event) {
  1510. final Paintable p = event.getPaintable();
  1511. if (!dirtyPaintables.contains(p)) {
  1512. dirtyPaintables.add(p);
  1513. }
  1514. }
  1515. /**
  1516. * Internally mark a {@link Paintable} as painted and start collecting new
  1517. * repaint requests for it.
  1518. *
  1519. * @param paintable
  1520. */
  1521. private void paintablePainted(Paintable paintable) {
  1522. dirtyPaintables.remove(paintable);
  1523. paintable.requestRepaintRequests();
  1524. }
  1525. /**
  1526. * Implementation of {@link URIHandler.ErrorEvent} interface.
  1527. */
  1528. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent,
  1529. Serializable {
  1530. private final URIHandler owner;
  1531. private final Throwable throwable;
  1532. /**
  1533. *
  1534. * @param owner
  1535. * @param throwable
  1536. */
  1537. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  1538. this.owner = owner;
  1539. this.throwable = throwable;
  1540. }
  1541. /**
  1542. * @see com.vaadin.terminal.Terminal.ErrorEvent#getThrowable()
  1543. */
  1544. public Throwable getThrowable() {
  1545. return throwable;
  1546. }
  1547. /**
  1548. * @see com.vaadin.terminal.URIHandler.ErrorEvent#getURIHandler()
  1549. */
  1550. public URIHandler getURIHandler() {
  1551. return owner;
  1552. }
  1553. }
  1554. /**
  1555. * Queues a locale to be sent to the client (browser) for date and time
  1556. * entry etc. All locale specific information is derived from server-side
  1557. * {@link Locale} instances and sent to the client when needed, eliminating
  1558. * the need to use the {@link Locale} class and all the framework behind it
  1559. * on the client.
  1560. *
  1561. * @see Locale#toString()
  1562. *
  1563. * @param value
  1564. */
  1565. public void requireLocale(String value) {
  1566. if (locales == null) {
  1567. locales = new ArrayList<String>();
  1568. locales.add(application.getLocale().toString());
  1569. pendingLocalesIndex = 0;
  1570. }
  1571. if (!locales.contains(value)) {
  1572. locales.add(value);
  1573. }
  1574. }
  1575. /**
  1576. * Constructs a {@link Locale} instance to be sent to the client based on a
  1577. * short locale description string.
  1578. *
  1579. * @see #requireLocale(String)
  1580. *
  1581. * @param value
  1582. * @return
  1583. */
  1584. private Locale generateLocale(String value) {
  1585. final String[] temp = value.split("_");
  1586. if (temp.length == 1) {
  1587. return new Locale(temp[0]);
  1588. } else if (temp.length == 2) {
  1589. return new Locale(temp[0], temp[1]);
  1590. } else {
  1591. return new Locale(temp[0], temp[1], temp[2]);
  1592. }
  1593. }
  1594. /*
  1595. * Upload progress listener notifies upload component once when Jakarta
  1596. * FileUpload can determine content length. Used to detect files total size,
  1597. * uploads progress can be tracked inside upload.
  1598. */
  1599. private class UploadProgressListener implements ProgressListener,
  1600. Serializable {
  1601. Upload uploadComponent;
  1602. boolean updated = false;
  1603. public void setUpload(Upload u) {
  1604. uploadComponent = u;
  1605. }
  1606. public void update(long bytesRead, long contentLength, int items) {
  1607. if (!updated && uploadComponent != null) {
  1608. uploadComponent.setUploadSize(contentLength);
  1609. updated = true;
  1610. }
  1611. }
  1612. }
  1613. /**
  1614. * Helper method to test if a component contains another
  1615. *
  1616. * @param parent
  1617. * @param child
  1618. */
  1619. private static boolean isChildOf(Component parent, Component child) {
  1620. Component p = child.getParent();
  1621. while (p != null) {
  1622. if (parent == p) {
  1623. return true;
  1624. }
  1625. p = p.getParent();
  1626. }
  1627. return false;
  1628. }
  1629. protected class InvalidUIDLSecurityKeyException extends
  1630. GeneralSecurityException {
  1631. InvalidUIDLSecurityKeyException(String message) {
  1632. super(message);
  1633. }
  1634. }
  1635. /**
  1636. * Calls the Window URI handler for a request and returns the
  1637. * {@link DownloadStream} returned by the handler.
  1638. *
  1639. * If the window is the main window of an application, the (deprecated)
  1640. * {@link Application#handleURI(java.net.URL, String)} is called first to
  1641. * handle {@link ApplicationResource}s, and the window handler is only
  1642. * called if it returns null.
  1643. *
  1644. * @param window
  1645. * the target window of the request
  1646. * @param request
  1647. * the request instance
  1648. * @param response
  1649. * the response to write to
  1650. * @return DownloadStream if the request was handled and further processing
  1651. * should be suppressed, null otherwise.
  1652. * @see com.vaadin.terminal.URIHandler
  1653. */
  1654. protected DownloadStream handleURI(Window window, Request request,
  1655. Response response, Callback callback) {
  1656. String uri = callback.getRequestPathInfo(request);
  1657. // If no URI is available
  1658. if (uri == null) {
  1659. uri = "";
  1660. } else {
  1661. // Removes the leading /
  1662. while (uri.startsWith("/") && uri.length() > 0) {
  1663. uri = uri.substring(1);
  1664. }
  1665. }
  1666. // Handles the uri
  1667. try {
  1668. URL context = application.getURL();
  1669. if (window == application.getMainWindow()) {
  1670. DownloadStream stream = null;
  1671. /*
  1672. * Application.handleURI run first. Handles possible
  1673. * ApplicationResources.
  1674. */
  1675. stream = application.handleURI(context, uri);
  1676. if (stream == null) {
  1677. stream = window.handleURI(context, uri);
  1678. }
  1679. return stream;
  1680. } else {
  1681. // Resolve the prefix end index
  1682. final int index = uri.indexOf('/');
  1683. if (index > 0) {
  1684. String prefix = uri.substring(0, index);
  1685. URL windowContext;
  1686. windowContext = new URL(context, prefix + "/");
  1687. final String windowUri = (uri.length() > prefix.length() + 1) ? uri
  1688. .substring(prefix.length() + 1) : "";
  1689. return window.handleURI(windowContext, windowUri);
  1690. } else {
  1691. return null;
  1692. }
  1693. }
  1694. } catch (final Throwable t) {
  1695. application.getErrorHandler().terminalError(
  1696. new URIHandlerErrorImpl(application, t));
  1697. return null;
  1698. }
  1699. }
  1700. private HashMap<Class<? extends Paintable>, Integer> typeToKey = new HashMap<Class<? extends Paintable>, Integer>();
  1701. private int nextTypeKey = 0;
  1702. String getTagForType(Class<? extends Paintable> class1) {
  1703. Integer object = typeToKey.get(class1);
  1704. if (object == null) {
  1705. object = nextTypeKey++;
  1706. typeToKey.put(class1, object);
  1707. }
  1708. return object.toString();
  1709. }
  1710. /**
  1711. * Helper class for terminal to keep track of data that client is expected
  1712. * to know.
  1713. *
  1714. * TODO make customlayout templates (from theme) to be cached here.
  1715. */
  1716. class OpenWindowCache implements Serializable {
  1717. private Set<Object> res = new HashSet<Object>();
  1718. /**
  1719. *
  1720. * @param paintable
  1721. * @return true if the given class was added to cache
  1722. */
  1723. boolean cache(Object object) {
  1724. return res.add(object);
  1725. }
  1726. public void clear() {
  1727. res.clear();
  1728. }
  1729. }
  1730. }