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.

CommunicationManager.java 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Interfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license.pdf. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.terminal.gwt.server;
  19. import java.io.BufferedWriter;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.InputStreamReader;
  23. import java.io.OutputStream;
  24. import java.io.OutputStreamWriter;
  25. import java.io.PrintWriter;
  26. import java.text.DateFormatSymbols;
  27. import java.text.SimpleDateFormat;
  28. import java.util.ArrayList;
  29. import java.util.Calendar;
  30. import java.util.Collection;
  31. import java.util.Collections;
  32. import java.util.Comparator;
  33. import java.util.GregorianCalendar;
  34. import java.util.HashMap;
  35. import java.util.HashSet;
  36. import java.util.Iterator;
  37. import java.util.LinkedHashSet;
  38. import java.util.LinkedList;
  39. import java.util.List;
  40. import java.util.Locale;
  41. import java.util.Map;
  42. import java.util.Set;
  43. import java.util.WeakHashMap;
  44. import javax.servlet.ServletException;
  45. import javax.servlet.ServletOutputStream;
  46. import javax.servlet.http.HttpServletRequest;
  47. import javax.servlet.http.HttpServletResponse;
  48. import org.apache.commons.fileupload.FileItemIterator;
  49. import org.apache.commons.fileupload.FileItemStream;
  50. import org.apache.commons.fileupload.FileUploadException;
  51. import org.apache.commons.fileupload.ProgressListener;
  52. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  53. import org.apache.commons.fileupload.util.Streams;
  54. import com.itmill.toolkit.Application;
  55. import com.itmill.toolkit.Application.WindowAttachEvent;
  56. import com.itmill.toolkit.Application.WindowDetachEvent;
  57. import com.itmill.toolkit.terminal.DownloadStream;
  58. import com.itmill.toolkit.terminal.Paintable;
  59. import com.itmill.toolkit.terminal.URIHandler;
  60. import com.itmill.toolkit.terminal.UploadStream;
  61. import com.itmill.toolkit.terminal.VariableOwner;
  62. import com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent;
  63. import com.itmill.toolkit.ui.Component;
  64. import com.itmill.toolkit.ui.FrameWindow;
  65. import com.itmill.toolkit.ui.Upload;
  66. import com.itmill.toolkit.ui.Window;
  67. /**
  68. * Application manager processes changes and paints for single application
  69. * instance.
  70. *
  71. * @author IT Mill Ltd.
  72. * @version
  73. * @VERSION@
  74. * @since 5.0
  75. */
  76. public class CommunicationManager implements Paintable.RepaintRequestListener,
  77. Application.WindowAttachListener, Application.WindowDetachListener {
  78. private static String GET_PARAM_REPAINT_ALL = "repaintAll";
  79. private static int DEFAULT_BUFFER_SIZE = 32 * 1024;
  80. private static int MAX_BUFFER_SIZE = 64 * 1024;
  81. private HashSet dirtyPaintabletSet = new HashSet();
  82. private WeakHashMap paintableIdMap = new WeakHashMap();
  83. private WeakHashMap idPaintableMap = new WeakHashMap();
  84. private int idSequence = 0;
  85. private Application application;
  86. private Set removedWindows = new HashSet();
  87. private JsonPaintTarget paintTarget;
  88. private List locales;
  89. private int pendingLocalesIndex;
  90. private ApplicationServlet applicationServlet;
  91. public CommunicationManager(Application application,
  92. ApplicationServlet applicationServlet) {
  93. this.application = application;
  94. this.applicationServlet = applicationServlet;
  95. requireLocale(application.getLocale().toString());
  96. }
  97. /**
  98. *
  99. *
  100. */
  101. public void takeControl() {
  102. application.addListener((Application.WindowAttachListener) this);
  103. application.addListener((Application.WindowDetachListener) this);
  104. }
  105. /**
  106. *
  107. *
  108. */
  109. public void releaseControl() {
  110. application.removeListener((Application.WindowAttachListener) this);
  111. application.removeListener((Application.WindowDetachListener) this);
  112. }
  113. /**
  114. * Handles file upload request submitted via Upload component.
  115. *
  116. * @param request
  117. * @param response
  118. * @throws IOException
  119. */
  120. public void handleFileUpload(HttpServletRequest request,
  121. HttpServletResponse response) throws IOException {
  122. // Create a new file upload handler
  123. ServletFileUpload upload = new ServletFileUpload();
  124. UploadProgressListener pl = new UploadProgressListener();
  125. upload.setProgressListener(pl);
  126. // Parse the request
  127. FileItemIterator iter;
  128. try {
  129. iter = upload.getItemIterator(request);
  130. /*
  131. * ATM this loop is run only once as we are uploading one file per
  132. * request.
  133. */
  134. while (iter.hasNext()) {
  135. FileItemStream item = iter.next();
  136. String name = item.getFieldName();
  137. final String filename = item.getName();
  138. final String mimeType = item.getContentType();
  139. final InputStream stream = item.openStream();
  140. if (item.isFormField()) {
  141. // ignored, upload requests contian only files
  142. } else {
  143. String pid = name.split("_")[0];
  144. Upload uploadComponent = (Upload) idPaintableMap.get(pid);
  145. if (uploadComponent == null) {
  146. throw new FileUploadException(
  147. "Upload component not found");
  148. }
  149. synchronized (application) {
  150. // put upload component into receiving state
  151. uploadComponent.startUpload();
  152. }
  153. UploadStream upstream = new UploadStream() {
  154. public String getContentName() {
  155. return filename;
  156. }
  157. public String getContentType() {
  158. return mimeType;
  159. }
  160. public InputStream getStream() {
  161. return stream;
  162. }
  163. public String getStreamName() {
  164. return "stream";
  165. }
  166. };
  167. // tell UploadProgressListener which component is receiving
  168. // file
  169. pl.setUpload(uploadComponent);
  170. uploadComponent.receiveUpload(upstream);
  171. }
  172. }
  173. } catch (FileUploadException e) {
  174. e.printStackTrace();
  175. }
  176. // Send short response to acknowledge client that request was done
  177. response.setContentType("text/html");
  178. OutputStream out = response.getOutputStream();
  179. PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  180. new OutputStreamWriter(out, "UTF-8")));
  181. outWriter.print("<html><body>download handled</body></html>");
  182. outWriter.flush();
  183. out.close();
  184. }
  185. /**
  186. * Handles UIDL request
  187. *
  188. * @param request
  189. * @param response
  190. * @throws IOException
  191. */
  192. public void handleUidlRequest(HttpServletRequest request,
  193. HttpServletResponse response) throws IOException {
  194. // repaint requested or session has timed out and new one is created
  195. boolean repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null)
  196. || request.getSession().isNew();
  197. // If repaint is requested, clean all ids
  198. if (repaintAll) {
  199. idPaintableMap.clear();
  200. paintableIdMap.clear();
  201. }
  202. OutputStream out = response.getOutputStream();
  203. PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  204. new OutputStreamWriter(out, "UTF-8")));
  205. try {
  206. // Is this a download request from application
  207. DownloadStream download = null;
  208. // The rest of the process is synchronized with the application
  209. // in order to guarantee that no parallel variable handling is
  210. // made
  211. synchronized (application) {
  212. // Change all variables based on request parameters
  213. Map unhandledParameters = handleVariables(request, application);
  214. // Handles the URI if the application is still running
  215. if (application.isRunning())
  216. download = handleURI(application, request, response);
  217. // If this is not a download request
  218. if (download == null) {
  219. // Finds the window within the application
  220. Window window = null;
  221. if (application.isRunning())
  222. window = getApplicationWindow(request, application);
  223. // Handles the unhandled parameters if the application is
  224. // still running
  225. if (window != null && unhandledParameters != null
  226. && !unhandledParameters.isEmpty())
  227. window.handleParameters(unhandledParameters);
  228. // Removes application if it has stopped
  229. if (!application.isRunning()) {
  230. endApplication(request, response, application);
  231. return;
  232. }
  233. // Returns if no window found
  234. if (window == null)
  235. return;
  236. // Sets the response type
  237. response.setContentType("application/json; charset=UTF-8");
  238. // some dirt to prevent cross site scripting
  239. outWriter.print(")/*{");
  240. outWriter.print("\"changes\":[");
  241. paintTarget = new JsonPaintTarget(this, outWriter,
  242. !repaintAll);
  243. // Paints components
  244. Set paintables;
  245. if (repaintAll) {
  246. paintables = new LinkedHashSet();
  247. paintables.add(window);
  248. // Reset sent locales
  249. locales = null;
  250. requireLocale(application.getLocale().toString());
  251. } else
  252. paintables = getDirtyComponents();
  253. if (paintables != null) {
  254. // Creates "working copy" of the current state.
  255. List currentPaintables = new ArrayList(paintables);
  256. // Sorts the paintable so that parent windows
  257. // are always painted before child windows
  258. Collections.sort(currentPaintables, new Comparator() {
  259. public int compare(Object o1, Object o2) {
  260. // If first argumement is now window
  261. // the second is "smaller" if it is.
  262. if (!(o1 instanceof Window)) {
  263. return (o2 instanceof Window) ? 1 : 0;
  264. }
  265. // Now, if second is not window the
  266. // first is smaller.
  267. if (!(o2 instanceof Window)) {
  268. return -1;
  269. }
  270. // Both are windows.
  271. String n1 = ((Window) o1).getName();
  272. String n2 = ((Window) o2).getName();
  273. if (o1 instanceof FrameWindow) {
  274. if (((FrameWindow) o1).getFrameset()
  275. .getFrame(n2) != null) {
  276. return -1;
  277. } else if (!(o2 instanceof FrameWindow)) {
  278. return -1;
  279. }
  280. }
  281. if (o2 instanceof FrameWindow) {
  282. if (((FrameWindow) o2).getFrameset()
  283. .getFrame(n1) != null) {
  284. return 1;
  285. } else if (!(o1 instanceof FrameWindow)) {
  286. return 1;
  287. }
  288. }
  289. return 0;
  290. }
  291. });
  292. for (Iterator i = currentPaintables.iterator(); i
  293. .hasNext();) {
  294. Paintable p = (Paintable) i.next();
  295. // TODO CLEAN
  296. if (p instanceof Window) {
  297. Window w = (Window) p;
  298. if (w.getTerminal() == null)
  299. w.setTerminal(application.getMainWindow()
  300. .getTerminal());
  301. }
  302. /*
  303. * This does not seem to happen in tk5, but remember
  304. * this case: else if (p instanceof Component) { if
  305. * (((Component) p).getParent() == null ||
  306. * ((Component) p).getApplication() == null) { //
  307. * Component requested repaint, but is no // longer
  308. * attached: skip paintablePainted(p); continue; } }
  309. */
  310. paintTarget.startTag("change");
  311. paintTarget.addAttribute("format", "uidl");
  312. String pid = getPaintableId(p);
  313. paintTarget.addAttribute("pid", pid);
  314. // Track paints to identify empty paints
  315. paintTarget.setTrackPaints(true);
  316. p.paint(paintTarget);
  317. // If no paints add attribute empty
  318. if (paintTarget.getNumberOfPaints() <= 0) {
  319. paintTarget.addAttribute("visible", false);
  320. }
  321. paintTarget.endTag("change");
  322. paintablePainted(p);
  323. }
  324. }
  325. paintTarget.close();
  326. outWriter.print("]"); // close changes
  327. outWriter.print(", \"meta\" : {");
  328. boolean metaOpen = false;
  329. // add meta instruction for client to set focus if it is set
  330. Paintable f = (Paintable) application.consumeFocus();
  331. if (f != null) {
  332. if (metaOpen)
  333. outWriter.append(",");
  334. outWriter.write("\"focus\":\"" + getPaintableId(f)
  335. + "\"");
  336. }
  337. outWriter.print("}, \"resources\" : {");
  338. // Precache custom layouts
  339. String themeName = window.getTheme();
  340. if (request.getParameter("theme") != null) {
  341. themeName = request.getParameter("theme");
  342. }
  343. if (themeName == null)
  344. themeName = "default";
  345. // TODO We should only precache the layouts that are not
  346. // cached already
  347. int resourceIndex = 0;
  348. for (Iterator i = paintTarget.getPreCachedResources()
  349. .iterator(); i.hasNext();) {
  350. String resource = (String) i.next();
  351. InputStream is = null;
  352. try {
  353. is = applicationServlet
  354. .getServletContext()
  355. .getResourceAsStream(
  356. ApplicationServlet.THEME_DIRECTORY_PATH
  357. + themeName
  358. + "/"
  359. + resource);
  360. } catch (Exception e) {
  361. Log.info(e.getMessage());
  362. }
  363. if (is != null) {
  364. outWriter.print((resourceIndex++ > 0 ? ", " : "")
  365. + "\"" + resource + "\" : ");
  366. StringBuffer layout = new StringBuffer();
  367. try {
  368. InputStreamReader r = new InputStreamReader(is);
  369. char[] buffer = new char[20000];
  370. int charsRead = 0;
  371. while ((charsRead = r.read(buffer)) > 0)
  372. layout.append(buffer, 0, charsRead);
  373. r.close();
  374. } catch (java.io.IOException e) {
  375. Log.info("Resource transfer failed: "
  376. + request.getRequestURI() + ". ("
  377. + e.getMessage() + ")");
  378. }
  379. outWriter.print("\""
  380. + JsonPaintTarget.escapeJSON(layout
  381. .toString()) + "\"");
  382. }
  383. }
  384. outWriter.print("}");
  385. printLocaleDeclarations(outWriter);
  386. outWriter.flush();
  387. outWriter.close();
  388. out.flush();
  389. } else {
  390. // For download request, transfer the downloaded data
  391. handleDownload(download, request, response);
  392. }
  393. }
  394. out.flush();
  395. out.close();
  396. } catch (Throwable e) {
  397. // Writes the error report to client
  398. OutputStreamWriter w = new OutputStreamWriter(out);
  399. PrintWriter err = new PrintWriter(w);
  400. err
  401. .write("<html><head><title>Application Internal Error</title></head><body>");
  402. err.write("<h1>" + e.toString() + "</h1><pre>\n");
  403. e.printStackTrace(new PrintWriter(err));
  404. err.write("\n</pre></body></html>");
  405. err.close();
  406. } finally {
  407. }
  408. }
  409. private Map handleVariables(HttpServletRequest request,
  410. Application application2) {
  411. Map params = new HashMap(request.getParameterMap());
  412. String changes = (String) ((params.get("changes") instanceof String[]) ? ((String[]) params
  413. .get("changes"))[0]
  414. : params.get("changes"));
  415. params.remove("changes");
  416. if (changes != null) {
  417. String[] ca = changes.split("\u0001");
  418. System.out.println("Changes = " + changes);
  419. for (int i = 0; i < ca.length; i++) {
  420. String[] vid = ca[i].split("_");
  421. VariableOwner owner = (VariableOwner) idPaintableMap
  422. .get(vid[0]);
  423. if (owner != null) {
  424. Map m;
  425. if (i + 2 >= ca.length
  426. || !vid[0].equals(ca[i + 2].split("_")[0])) {
  427. if (ca.length > i + 1) {
  428. m = new SingleValueMap(vid[1],
  429. convertVariableValue(vid[2].charAt(0),
  430. ca[++i]));
  431. } else {
  432. m = new SingleValueMap(vid[1],
  433. convertVariableValue(vid[2].charAt(0), ""));
  434. }
  435. } else {
  436. m = new HashMap();
  437. m.put(vid[1], convertVariableValue(vid[2].charAt(0),
  438. ca[++i]));
  439. }
  440. while (i + 1 < ca.length
  441. && vid[0].equals(ca[i + 1].split("_")[0])) {
  442. vid = ca[++i].split("_");
  443. m.put(vid[1], convertVariableValue(vid[2].charAt(0),
  444. ca[++i]));
  445. }
  446. owner.changeVariables(request, m);
  447. }
  448. }
  449. }
  450. return params;
  451. }
  452. private Object convertVariableValue(char variableType, String strValue) {
  453. Object val = null;
  454. System.out.println("converting " + strValue + " of type "
  455. + variableType);
  456. switch (variableType) {
  457. case 'a':
  458. val = strValue.split(",");
  459. break;
  460. case 's':
  461. val = strValue;
  462. break;
  463. case 'i':
  464. val = Integer.valueOf(strValue);
  465. break;
  466. case 'l':
  467. val = Long.valueOf(strValue);
  468. case 'f':
  469. val = Float.valueOf(strValue);
  470. break;
  471. case 'd':
  472. val = Double.valueOf(strValue);
  473. break;
  474. case 'b':
  475. val = Boolean.valueOf(strValue);
  476. break;
  477. }
  478. System.out.println("result: " + val + " of type "
  479. + (val == null ? "-" : val.getClass().toString()));
  480. return val;
  481. }
  482. private void printLocaleDeclarations(PrintWriter outWriter) {
  483. /*
  484. * ----------------------------- Sending Locale sensitive date
  485. * -----------------------------
  486. */
  487. // Store JVM default locale for later restoration
  488. // (we'll have to change the default locale for a while)
  489. Locale jvmDefault = Locale.getDefault();
  490. // Send locale informations to client
  491. outWriter.print(", \"locales\":[");
  492. for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
  493. Locale l = generateLocale((String) locales.get(pendingLocalesIndex));
  494. // Locale name
  495. outWriter.print("{\"name\":\"" + l.toString() + "\",");
  496. /*
  497. * Month names (both short and full)
  498. */
  499. DateFormatSymbols dfs = new DateFormatSymbols(l);
  500. String[] short_months = dfs.getShortMonths();
  501. String[] months = dfs.getMonths();
  502. outWriter.print("\"smn\":[\""
  503. + // ShortMonthNames
  504. short_months[0] + "\",\"" + short_months[1] + "\",\""
  505. + short_months[2] + "\",\"" + short_months[3] + "\",\""
  506. + short_months[4] + "\",\"" + short_months[5] + "\",\""
  507. + short_months[6] + "\",\"" + short_months[7] + "\",\""
  508. + short_months[8] + "\",\"" + short_months[9] + "\",\""
  509. + short_months[10] + "\",\"" + short_months[11] + "\""
  510. + "],");
  511. outWriter.print("\"mn\":[\""
  512. + // MonthNames
  513. months[0] + "\",\"" + months[1] + "\",\"" + months[2]
  514. + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
  515. + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
  516. + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
  517. + months[10] + "\",\"" + months[11] + "\"" + "],");
  518. /*
  519. * Weekday names (both short and full)
  520. */
  521. String[] short_days = dfs.getShortWeekdays();
  522. String[] days = dfs.getWeekdays();
  523. outWriter.print("\"sdn\":[\""
  524. + // ShortDayNames
  525. short_days[1] + "\",\"" + short_days[2] + "\",\""
  526. + short_days[3] + "\",\"" + short_days[4] + "\",\""
  527. + short_days[5] + "\",\"" + short_days[6] + "\",\""
  528. + short_days[7] + "\"" + "],");
  529. outWriter.print("\"dn\":[\""
  530. + // DayNames
  531. days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
  532. + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
  533. + days[7] + "\"" + "],");
  534. /*
  535. * First day of week (0 = sunday, 1 = monday)
  536. */
  537. Calendar cal = new GregorianCalendar(l);
  538. outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
  539. /*
  540. * Date formatting (MM/DD/YYYY etc.)
  541. */
  542. // Force our locale as JVM default for a while (SimpleDateFormat
  543. // uses JVM default)
  544. Locale.setDefault(l);
  545. String df = new SimpleDateFormat().toPattern();
  546. int timeStart = df.indexOf("H");
  547. if (timeStart < 0)
  548. timeStart = df.indexOf("h");
  549. int ampm_first = df.indexOf("a");
  550. // E.g. in Korean locale AM/PM is before h:mm
  551. // TODO should take that into consideration on client-side as well,
  552. // now always h:mm a
  553. if (ampm_first > 0 && ampm_first < timeStart)
  554. timeStart = ampm_first;
  555. String dateformat = df.substring(0, timeStart - 1);
  556. outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
  557. /*
  558. * Time formatting (24 or 12 hour clock and AM/PM suffixes)
  559. */
  560. String timeformat = df.substring(timeStart, df.length()); // Doesn't
  561. // return
  562. // second
  563. // or
  564. // milliseconds
  565. // We use timeformat to determine 12/24-hour clock
  566. boolean twelve_hour_clock = timeformat.contains("a");
  567. // TODO there are other possibilities as well, like 'h' in french
  568. // (ignore them, too complicated)
  569. String hour_min_delimiter = timeformat.contains(".") ? "." : ":";
  570. // outWriter.print("\"tf\":\"" + timeformat + "\",");
  571. outWriter.print("\"thc\":" + twelve_hour_clock + ",");
  572. outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
  573. if (twelve_hour_clock) {
  574. String[] ampm = dfs.getAmPmStrings();
  575. outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
  576. + "\"]");
  577. }
  578. outWriter.print("}");
  579. if (pendingLocalesIndex < locales.size() - 1)
  580. outWriter.print(",");
  581. }
  582. outWriter.print("]"); // Close locales
  583. // Restore JVM default locale
  584. Locale.setDefault(jvmDefault);
  585. }
  586. /**
  587. * Gets the existing application or create a new one. Get a window within an
  588. * application based on the requested URI.
  589. *
  590. * @param request
  591. * the HTTP Request.
  592. * @param application
  593. * the Application to query for window.
  594. * @return Window mathing the given URI or null if not found.
  595. * @throws ServletException
  596. * if an exception has occurred that interferes with the
  597. * servlet's normal operation.
  598. */
  599. private Window getApplicationWindow(HttpServletRequest request,
  600. Application application) throws ServletException {
  601. Window window = null;
  602. // Find the window where the request is handled
  603. String path = request.getPathInfo();
  604. // Remove UIDL from the path
  605. path = path.substring("/UIDL".length());
  606. // Main window as the URI is empty
  607. if (path == null || path.length() == 0 || path.equals("/"))
  608. window = application.getMainWindow();
  609. // Try to search by window name
  610. else {
  611. String windowName = null;
  612. if (path.charAt(0) == '/')
  613. path = path.substring(1);
  614. int index = path.indexOf('/');
  615. if (index < 0) {
  616. windowName = path;
  617. path = "";
  618. } else {
  619. windowName = path.substring(0, index);
  620. path = path.substring(index + 1);
  621. }
  622. window = application.getWindow(windowName);
  623. // By default, we use main window
  624. if (window == null)
  625. window = application.getMainWindow();
  626. }
  627. return window;
  628. }
  629. /**
  630. * Handles the requested URI. An application can add handlers to do special
  631. * processing, when a certain URI is requested. The handlers are invoked
  632. * before any windows URIs are processed and if a DownloadStream is returned
  633. * it is sent to the client.
  634. *
  635. * @param application
  636. * the Application owning the URI.
  637. * @param request
  638. * the HTTP request instance.
  639. * @param response
  640. * the HTTP response to write to.
  641. * @return boolean <code>true</code> if the request was handled and
  642. * further processing should be suppressed, otherwise
  643. * <code>false</code>.
  644. * @see com.itmill.toolkit.terminal.URIHandler
  645. */
  646. private DownloadStream handleURI(Application application,
  647. HttpServletRequest request, HttpServletResponse response) {
  648. String uri = request.getPathInfo();
  649. // If no URI is available
  650. if (uri == null || uri.length() == 0 || uri.equals("/"))
  651. return null;
  652. // Remove the leading /
  653. while (uri.startsWith("/") && uri.length() > 0)
  654. uri = uri.substring(1);
  655. // Handle the uri
  656. DownloadStream stream = null;
  657. try {
  658. stream = application.handleURI(application.getURL(), uri);
  659. } catch (Throwable t) {
  660. application.terminalError(new URIHandlerErrorImpl(application, t));
  661. }
  662. return stream;
  663. }
  664. /**
  665. * Handles the requested URI. An application can add handlers to do special
  666. * processing, when a certain URI is requested. The handlers are invoked
  667. * before any windows URIs are processed and if a DownloadStream is returned
  668. * it is sent to the client.
  669. *
  670. * @param stream
  671. * the downloadable stream.
  672. *
  673. * @param request
  674. * the HTTP request instance.
  675. * @param response
  676. * the HTTP response to write to.
  677. *
  678. * @see com.itmill.toolkit.terminal.URIHandler
  679. */
  680. private void handleDownload(DownloadStream stream,
  681. HttpServletRequest request, HttpServletResponse response) {
  682. // Download from given stream
  683. InputStream data = stream.getStream();
  684. if (data != null) {
  685. // Sets content type
  686. response.setContentType(stream.getContentType());
  687. // Sets cache headers
  688. long cacheTime = stream.getCacheTime();
  689. if (cacheTime <= 0) {
  690. response.setHeader("Cache-Control", "no-cache");
  691. response.setHeader("Pragma", "no-cache");
  692. response.setDateHeader("Expires", 0);
  693. } else {
  694. response.setHeader("Cache-Control", "max-age=" + cacheTime
  695. / 1000);
  696. response.setDateHeader("Expires", System.currentTimeMillis()
  697. + cacheTime);
  698. response.setHeader("Pragma", "cache"); // Required to apply
  699. // caching in some
  700. // Tomcats
  701. }
  702. // Copy download stream parameters directly
  703. // to HTTP headers.
  704. Iterator i = stream.getParameterNames();
  705. if (i != null) {
  706. while (i.hasNext()) {
  707. String param = (String) i.next();
  708. response.setHeader((String) param, stream
  709. .getParameter(param));
  710. }
  711. }
  712. int bufferSize = stream.getBufferSize();
  713. if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE)
  714. bufferSize = DEFAULT_BUFFER_SIZE;
  715. byte[] buffer = new byte[bufferSize];
  716. int bytesRead = 0;
  717. try {
  718. OutputStream out = response.getOutputStream();
  719. while ((bytesRead = data.read(buffer)) > 0) {
  720. out.write(buffer, 0, bytesRead);
  721. out.flush();
  722. }
  723. out.close();
  724. } catch (IOException ignored) {
  725. }
  726. }
  727. }
  728. /**
  729. * Ends the Application.
  730. *
  731. * @param request
  732. * the HTTP request instance.
  733. * @param response
  734. * the HTTP response to write to.
  735. * @param application
  736. * the Application to end.
  737. * @throws IOException
  738. * if the writing failed due to input/output error.
  739. */
  740. private void endApplication(HttpServletRequest request,
  741. HttpServletResponse response, Application application)
  742. throws IOException {
  743. String logoutUrl = application.getLogoutURL();
  744. if (logoutUrl == null)
  745. logoutUrl = application.getURL().toString();
  746. // clients JS app is still running, send a special json file to
  747. // tell client that application has quit and where to point browser now
  748. // Set the response type
  749. response.setContentType("application/json; charset=UTF-8");
  750. ServletOutputStream out = response.getOutputStream();
  751. PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  752. new OutputStreamWriter(out, "UTF-8")));
  753. outWriter.print(")/*{");
  754. outWriter.print("\"redirect\":{");
  755. outWriter.write("\"url\":\"" + logoutUrl + "\"}");
  756. outWriter.flush();
  757. outWriter.close();
  758. out.flush();
  759. }
  760. /**
  761. * Gets the Paintable Id.
  762. *
  763. * @param paintable
  764. * @return the paintable Id.
  765. */
  766. public synchronized String getPaintableId(Paintable paintable) {
  767. String id = (String) paintableIdMap.get(paintable);
  768. if (id == null) {
  769. id = "PID" + Integer.toString(idSequence++);
  770. paintableIdMap.put(paintable, id);
  771. idPaintableMap.put(id, paintable);
  772. }
  773. return id;
  774. }
  775. public synchronized boolean hasPaintableId(Paintable paintable) {
  776. return paintableIdMap.containsKey(paintable);
  777. }
  778. /**
  779. *
  780. * @return
  781. */
  782. public synchronized Set getDirtyComponents() {
  783. // TODO not compatible w/ subtree caching
  784. // Remove unnecessary repaints from the list
  785. Object[] paintables = dirtyPaintabletSet.toArray();
  786. /*
  787. * for (int i = 0; i < paintables.length; i++) { if (paintables[i]
  788. * instanceof Component) { Component c = (Component) paintables[i];
  789. * // Check if any of the parents of c already exist in the list
  790. * Component p = c.getParent(); while (p != null) { if
  791. * (dirtyPaintabletSet.contains(p)) {
  792. * // Remove component c from the dirty paintables as its // parent is
  793. * also dirty dirtyPaintabletSet.remove(c); p = null; } else p =
  794. * p.getParent(); } } }
  795. */
  796. return Collections.unmodifiableSet(dirtyPaintabletSet);
  797. }
  798. /**
  799. * Clears the Dirty Components.
  800. *
  801. */
  802. public synchronized void clearDirtyComponents() {
  803. dirtyPaintabletSet.clear();
  804. }
  805. /**
  806. * @see com.itmill.toolkit.terminal.Paintable.RepaintRequestListener#repaintRequested(com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent)
  807. */
  808. public void repaintRequested(RepaintRequestEvent event) {
  809. Paintable p = event.getPaintable();
  810. dirtyPaintabletSet.add(p);
  811. // For FrameWindows we mark all frames (windows) dirty
  812. if (p instanceof FrameWindow) {
  813. FrameWindow fw = (FrameWindow) p;
  814. repaintFrameset(fw.getFrameset());
  815. }
  816. }
  817. /**
  818. * Recursively request repaint for all frames in frameset.
  819. *
  820. * @param fs
  821. * the Framewindow.Frameset.
  822. */
  823. private void repaintFrameset(FrameWindow.Frameset fs) {
  824. List frames = fs.getFrames();
  825. for (Iterator i = frames.iterator(); i.hasNext();) {
  826. FrameWindow.Frame f = (FrameWindow.Frame) i.next();
  827. if (f instanceof FrameWindow.Frameset) {
  828. repaintFrameset((FrameWindow.Frameset) f);
  829. } else {
  830. Window w = f.getWindow();
  831. if (w != null) {
  832. w.requestRepaint();
  833. }
  834. }
  835. }
  836. }
  837. /**
  838. *
  839. * @param p
  840. */
  841. public void paintablePainted(Paintable p) {
  842. dirtyPaintabletSet.remove(p);
  843. p.requestRepaintRequests();
  844. }
  845. /**
  846. *
  847. * @param paintable
  848. * @return
  849. */
  850. public boolean isDirty(Paintable paintable) {
  851. return (dirtyPaintabletSet.contains(paintable));
  852. }
  853. /**
  854. * @see com.itmill.toolkit.Application.WindowAttachListener#windowAttached(com.itmill.toolkit.Application.WindowAttachEvent)
  855. */
  856. public void windowAttached(WindowAttachEvent event) {
  857. event.getWindow().addListener(this);
  858. dirtyPaintabletSet.add(event.getWindow());
  859. }
  860. /**
  861. * @see com.itmill.toolkit.Application.WindowDetachListener#windowDetached(com.itmill.toolkit.Application.WindowDetachEvent)
  862. */
  863. public void windowDetached(WindowDetachEvent event) {
  864. event.getWindow().removeListener(this);
  865. // Notify client of the close operation
  866. removedWindows.add(event.getWindow());
  867. }
  868. /**
  869. *
  870. * @return
  871. */
  872. public synchronized Set getRemovedWindows() {
  873. return Collections.unmodifiableSet(removedWindows);
  874. }
  875. /**
  876. *
  877. * @param w
  878. */
  879. private void removedWindowNotified(Window w) {
  880. this.removedWindows.remove(w);
  881. }
  882. private final class SingleValueMap implements Map {
  883. private final String name;
  884. private final Object value;
  885. private SingleValueMap(String name, Object value) {
  886. this.name = name;
  887. this.value = value;
  888. }
  889. public void clear() {
  890. throw new UnsupportedOperationException();
  891. }
  892. public boolean containsKey(Object key) {
  893. if (name == null)
  894. return key == null;
  895. return name.equals(key);
  896. }
  897. public boolean containsValue(Object v) {
  898. if (value == null)
  899. return v == null;
  900. return value.equals(v);
  901. }
  902. public Set entrySet() {
  903. Set s = new HashSet();
  904. s.add(new Map.Entry() {
  905. public Object getKey() {
  906. return name;
  907. }
  908. public Object getValue() {
  909. return value;
  910. }
  911. public Object setValue(Object value) {
  912. throw new UnsupportedOperationException();
  913. }
  914. });
  915. return s;
  916. }
  917. public Object get(Object key) {
  918. if (!name.equals(key))
  919. return null;
  920. return value;
  921. }
  922. public boolean isEmpty() {
  923. return false;
  924. }
  925. public Set keySet() {
  926. Set s = new HashSet();
  927. s.add(name);
  928. return s;
  929. }
  930. public Object put(Object key, Object value) {
  931. throw new UnsupportedOperationException();
  932. }
  933. public void putAll(Map t) {
  934. throw new UnsupportedOperationException();
  935. }
  936. public Object remove(Object key) {
  937. throw new UnsupportedOperationException();
  938. }
  939. public int size() {
  940. return 1;
  941. }
  942. public Collection values() {
  943. LinkedList s = new LinkedList();
  944. s.add(value);
  945. return s;
  946. }
  947. }
  948. /**
  949. * Implementation of URIHandler.ErrorEvent interface.
  950. */
  951. public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
  952. private URIHandler owner;
  953. private Throwable throwable;
  954. /**
  955. *
  956. * @param owner
  957. * @param throwable
  958. */
  959. private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
  960. this.owner = owner;
  961. this.throwable = throwable;
  962. }
  963. /**
  964. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  965. */
  966. public Throwable getThrowable() {
  967. return this.throwable;
  968. }
  969. /**
  970. * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
  971. */
  972. public URIHandler getURIHandler() {
  973. return this.owner;
  974. }
  975. }
  976. public void requireLocale(String value) {
  977. if (locales == null) {
  978. locales = new ArrayList();
  979. locales.add(application.getLocale().toString());
  980. pendingLocalesIndex = 0;
  981. }
  982. if (!locales.contains(value))
  983. locales.add(value);
  984. }
  985. private Locale generateLocale(String value) {
  986. String[] temp = value.split("_");
  987. if (temp.length == 1)
  988. return new Locale(temp[0]);
  989. else if (temp.length == 2)
  990. return new Locale(temp[0], temp[1]);
  991. else
  992. return new Locale(temp[0], temp[1], temp[2]);
  993. }
  994. /*
  995. * Upload progress listener notifies upload component once when Jakarta
  996. * FileUpload can determine content length. Used to detect files total size,
  997. * uploads progress can be tracked inside upload.
  998. */
  999. private class UploadProgressListener implements ProgressListener {
  1000. Upload uploadComponent;
  1001. boolean updated = false;
  1002. public void setUpload(Upload u) {
  1003. uploadComponent = u;
  1004. }
  1005. public void update(long bytesRead, long contentLength, int items) {
  1006. if (!updated && uploadComponent != null) {
  1007. uploadComponent.setUploadSize(contentLength);
  1008. updated = true;
  1009. }
  1010. }
  1011. }
  1012. }