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

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