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.

AbstractCommunicationManager.java 67KB

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