Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

CommunicationManager.java 59KB

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