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.

AjaxPaintTarget.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Intarfaces 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/license.txt. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see license/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.web;
  19. import com.itmill.toolkit.Application;
  20. import com.itmill.toolkit.terminal.ApplicationResource;
  21. import com.itmill.toolkit.terminal.ExternalResource;
  22. import com.itmill.toolkit.terminal.PaintException;
  23. import com.itmill.toolkit.terminal.PaintTarget;
  24. import com.itmill.toolkit.terminal.Paintable;
  25. import com.itmill.toolkit.terminal.Resource;
  26. import com.itmill.toolkit.terminal.ThemeResource;
  27. import com.itmill.toolkit.terminal.UploadStream;
  28. import com.itmill.toolkit.terminal.VariableOwner;
  29. import java.io.BufferedWriter;
  30. import java.io.OutputStream;
  31. import java.io.OutputStreamWriter;
  32. import java.io.PrintWriter;
  33. import java.io.UnsupportedEncodingException;
  34. import java.util.Stack;
  35. /**
  36. * User Interface Description Language Target.
  37. *
  38. * @author IT Mill Ltd.
  39. * @version
  40. * @VERSION@
  41. * @since 3.1
  42. */
  43. public class AjaxPaintTarget implements PaintTarget {
  44. /* Document type declarations */
  45. private final static String UIDL_XML_DECL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
  46. private final static String UIDL_ARG_NAME = "name";
  47. private final static String UIDL_ARG_VALUE = "value";
  48. private final static String UIDL_ARG_ID = "id";
  49. private Stack mOpenTags;
  50. private boolean mTagArgumentListOpen;
  51. private PrintWriter uidlBuffer;
  52. private AjaxVariableMap variableMap;
  53. private boolean closed = false;
  54. private AjaxApplicationManager manager;
  55. private boolean trackPaints = false;
  56. private int numberOfPaints = 0;
  57. /**
  58. * Create a new XMLPrintWriter, without automatic line flushing.
  59. *
  60. *
  61. * @param out
  62. * A character-output stream.
  63. */
  64. public AjaxPaintTarget(AjaxVariableMap variableMap, AjaxApplicationManager manager,
  65. OutputStream output) throws PaintException {
  66. // Set the cache
  67. this.manager = manager;
  68. // Set the variable map
  69. this.variableMap = variableMap;
  70. // Set the target for UIDL writing
  71. try {
  72. this.uidlBuffer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output,"UTF-8")));
  73. } catch (UnsupportedEncodingException e) {
  74. throw new RuntimeException("Internal error");
  75. }
  76. // Initialize tag-writing
  77. mOpenTags = new Stack();
  78. mTagArgumentListOpen = false;
  79. //Add document declaration
  80. this.print(UIDL_XML_DECL + "\n\n");
  81. // Add UIDL start tag and its attributes
  82. this.startTag("changes");
  83. }
  84. /**
  85. * Ensures that the currently open element tag is closed.
  86. */
  87. private void ensureClosedTag() {
  88. if (mTagArgumentListOpen) {
  89. append(">");
  90. mTagArgumentListOpen = false;
  91. }
  92. }
  93. /**
  94. * Method append.
  95. *
  96. * @param string
  97. */
  98. private void append(String string) {
  99. uidlBuffer.print(string);
  100. }
  101. /**
  102. * Print element start tag.
  103. *
  104. * <pre>
  105. * Todo:
  106. * Checking of input values
  107. *
  108. * </pre>
  109. *
  110. * @param tagName
  111. * The name of the start tag
  112. *
  113. */
  114. public void startTag(String tagName) throws PaintException {
  115. // In case of null data output nothing:
  116. if (tagName == null)
  117. throw new NullPointerException();
  118. // Incerement paint tracker
  119. if (this.isTrackPaints()) {
  120. this.numberOfPaints++;
  121. }
  122. //Ensure that the target is open
  123. if (this.closed)
  124. throw new PaintException(
  125. "Attempted to write to a closed PaintTarget.");
  126. // Make sure that the open start tag is closed before
  127. // anything is written.
  128. ensureClosedTag();
  129. // Check tagName and attributes here
  130. mOpenTags.push(tagName);
  131. // Print the tag with attributes
  132. append("\n<" + tagName);
  133. mTagArgumentListOpen = true;
  134. }
  135. /**
  136. * Print element end tag.
  137. *
  138. * If the parent tag is closed before every child tag is closed an
  139. * PaintException is raised.
  140. *
  141. * @param tag
  142. * The name of the end tag
  143. */
  144. public void endTag(String tagName) throws PaintException {
  145. // In case of null data output nothing:
  146. if (tagName == null)
  147. throw new NullPointerException();
  148. //Ensure that the target is open
  149. if (this.closed)
  150. throw new PaintException(
  151. "Attempted to write to a closed PaintTarget.");
  152. String lastTag = "";
  153. lastTag = (String) mOpenTags.pop();
  154. if (!tagName.equalsIgnoreCase(lastTag))
  155. throw new PaintException("Invalid UIDL: wrong ending tag: '"
  156. + tagName + "' expected: '" + lastTag + "'.");
  157. // Make sure that the open start tag is closed before
  158. // anything is written.
  159. if (mTagArgumentListOpen) {
  160. append(">");
  161. mTagArgumentListOpen = false;
  162. }
  163. //Write the end (closing) tag
  164. append("</" + lastTag + ">\n");
  165. flush();
  166. }
  167. /**
  168. * Substitute the XML sensitive characters with predefined XML entities.
  169. *
  170. * @return A new string instance where all occurrences of XML sensitive
  171. * characters are substituted with entities.
  172. */
  173. static public String escapeXML(String xml) {
  174. if (xml == null || xml.length() <= 0)
  175. return "";
  176. return escapeXML(new StringBuffer(xml)).toString();
  177. }
  178. /**
  179. * Substitute the XML sensitive characters with predefined XML entities.
  180. *
  181. * @param xml
  182. * the String to be substituted
  183. * @return A new StringBuffer instance where all occurrences of XML
  184. * sensitive characters are substituted with entities.
  185. *
  186. */
  187. static public StringBuffer escapeXML(StringBuffer xml) {
  188. if (xml == null || xml.length() <= 0)
  189. return new StringBuffer("");
  190. StringBuffer result = new StringBuffer(xml.length() * 2);
  191. for (int i = 0; i < xml.length(); i++) {
  192. char c = xml.charAt(i);
  193. String s = toXmlChar(c);
  194. if (s != null) {
  195. result.append(s);
  196. } else {
  197. result.append(c);
  198. }
  199. }
  200. return result;
  201. }
  202. /**
  203. * Substitute a XML sensitive character with predefined XML entity.
  204. *
  205. * @param c
  206. * Character to be replaced with an entity.
  207. * @return String of the entity or null if character is not to be replaced
  208. * with an entity.
  209. */
  210. private static String toXmlChar(char c) {
  211. switch (c) {
  212. case '&':
  213. return "&amp;"; // & => &amp;
  214. case '>':
  215. return "&gt;"; // > => &gt;
  216. case '<':
  217. return "&lt;"; // < => &lt;
  218. case '"':
  219. return "&quot;"; // " => &quot;
  220. case '\'':
  221. return "&apos;"; // ' => &apos;
  222. default:
  223. return null;
  224. }
  225. }
  226. /**
  227. * Print XML.
  228. *
  229. * Writes pre-formatted XML to stream. Well-formness of XML is checked.
  230. *
  231. * <pre>
  232. *
  233. * TODO: XML checking should be made
  234. *
  235. * </pre>
  236. */
  237. private void print(String str) {
  238. // In case of null data output nothing:
  239. if (str == null)
  240. return;
  241. // Make sure that the open start tag is closed before
  242. // anything is written.
  243. ensureClosedTag();
  244. // Write what was given
  245. append(str);
  246. }
  247. /**
  248. * Print XML-escaped text.
  249. *
  250. */
  251. public void addText(String str) throws PaintException {
  252. addUIDL(escapeXML(str));
  253. }
  254. /**
  255. * Adds a boolean attribute to component. Atributes must be added before any
  256. * content is written.
  257. *
  258. * @param name
  259. * Attribute name
  260. * @param value
  261. * Attribute value
  262. */
  263. public void addAttribute(String name, boolean value) throws PaintException {
  264. addAttribute(name, String.valueOf(value));
  265. }
  266. /**
  267. * Adds a resource attribute to component. Atributes must be added before
  268. * any content is written.
  269. *
  270. * @param name
  271. * Attribute name
  272. * @param value
  273. * Attribute value
  274. */
  275. public void addAttribute(String name, Resource value) throws PaintException {
  276. if (value instanceof ExternalResource) {
  277. addAttribute(name, ((ExternalResource) value).getURL());
  278. } else if (value instanceof ApplicationResource) {
  279. ApplicationResource r = (ApplicationResource) value;
  280. Application a = r.getApplication();
  281. if (a == null)
  282. throw new PaintException(
  283. "Application not specified for resorce "
  284. + value.getClass().getName());
  285. String uri = a.getURL().getPath();
  286. if (uri.charAt(uri.length() - 1) != '/')
  287. uri += "/";
  288. uri += a.getRelativeLocation(r);
  289. addAttribute(name, uri);
  290. } else if (value instanceof ThemeResource) {
  291. String uri = "theme://"+((ThemeResource)value).getResourceId();
  292. addAttribute(name,uri);
  293. } else
  294. throw new PaintException("Ajax adapter does not "
  295. + "support resources of type: "
  296. + value.getClass().getName());
  297. }
  298. /**
  299. * Adds a integer attribute to component. Atributes must be added before any
  300. * content is written.
  301. *
  302. * @param name
  303. * Attribute name
  304. * @param value
  305. * Attribute value
  306. * @return this object
  307. */
  308. public void addAttribute(String name, int value) throws PaintException {
  309. addAttribute(name, String.valueOf(value));
  310. }
  311. /**
  312. * Adds a long attribute to component. Atributes must be added before any
  313. * content is written.
  314. *
  315. * @param name
  316. * Attribute name
  317. * @param value
  318. * Attribute value
  319. * @return this object
  320. */
  321. public void addAttribute(String name, long value) throws PaintException {
  322. addAttribute(name, String.valueOf(value));
  323. }
  324. /**
  325. * Adds a string attribute to component. Atributes must be added before any
  326. * content is written.
  327. *
  328. * @param name
  329. * Boolean attribute name
  330. * @param value
  331. * Boolean attribute value
  332. * @return this object
  333. */
  334. public void addAttribute(String name, String value) throws PaintException {
  335. // In case of null data output nothing:
  336. if ((value == null) || (name == null))
  337. throw new NullPointerException(
  338. "Parameters must be non-null strings");
  339. //Ensure that the target is open
  340. if (this.closed)
  341. throw new PaintException(
  342. "Attempted to write to a closed PaintTarget.");
  343. // Check that argument list is writable.
  344. if (!mTagArgumentListOpen)
  345. throw new PaintException("XML argument list not open.");
  346. append(" " + name + "=\"" + escapeXML(value) + "\"");
  347. }
  348. /**
  349. * Add a string type variable.
  350. *
  351. * @param owner
  352. * Listener for variable changes
  353. * @param name
  354. * Variable name
  355. * @param value
  356. * Variable initial value
  357. * @return Reference to this.
  358. */
  359. public void addVariable(VariableOwner owner, String name, String value)
  360. throws PaintException {
  361. String code = variableMap.registerVariable(name, String.class, value,
  362. owner);
  363. startTag("string");
  364. addAttribute(UIDL_ARG_ID, code);
  365. addAttribute(UIDL_ARG_NAME, name);
  366. addText(value);
  367. endTag("string");
  368. }
  369. /**
  370. * Add a int type variable.
  371. *
  372. * @param owner
  373. * Listener for variable changes
  374. * @param name
  375. * Variable name
  376. * @param value
  377. * Variable initial value
  378. * @return Reference to this.
  379. */
  380. public void addVariable(VariableOwner owner, String name, int value)
  381. throws PaintException {
  382. String code = variableMap.registerVariable(name, Integer.class,
  383. new Integer(value), owner);
  384. startTag("integer");
  385. addAttribute(UIDL_ARG_ID, code);
  386. addAttribute(UIDL_ARG_NAME, name);
  387. addAttribute(UIDL_ARG_VALUE, String.valueOf(value));
  388. endTag("integer");
  389. }
  390. /**
  391. * Add a boolean type variable.
  392. *
  393. * @param owner
  394. * Listener for variable changes
  395. * @param name
  396. * Variable name
  397. * @param value
  398. * Variable initial value
  399. * @return Reference to this.
  400. */
  401. public void addVariable(VariableOwner owner, String name, boolean value)
  402. throws PaintException {
  403. String code = variableMap.registerVariable(name, Boolean.class,
  404. new Boolean(value), owner);
  405. startTag("boolean");
  406. addAttribute(UIDL_ARG_ID, code);
  407. addAttribute(UIDL_ARG_NAME, name);
  408. addAttribute(UIDL_ARG_VALUE, String.valueOf(value));
  409. endTag("boolean");
  410. }
  411. /**
  412. * Add a string array type variable.
  413. *
  414. * @param owner
  415. * Listener for variable changes
  416. * @param name
  417. * Variable name
  418. * @param value
  419. * Variable initial value
  420. * @return Reference to this.
  421. */
  422. public void addVariable(VariableOwner owner, String name, String[] value)
  423. throws PaintException {
  424. String code = variableMap.registerVariable(name, String[].class, value,
  425. owner);
  426. startTag("array");
  427. addAttribute(UIDL_ARG_ID, code);
  428. addAttribute(UIDL_ARG_NAME, name);
  429. for (int i = 0; i < value.length; i++)
  430. addSection("ai", value[i]);
  431. endTag("array");
  432. }
  433. /**
  434. * Add a upload stream type variable.
  435. *
  436. * @param owner
  437. * Listener for variable changes
  438. * @param name
  439. * Variable name
  440. * @param value
  441. * Variable initial value
  442. * @return Reference to this.
  443. */
  444. public void addUploadStreamVariable(VariableOwner owner, String name)
  445. throws PaintException {
  446. String code = variableMap.registerVariable(name, UploadStream.class,
  447. null, owner);
  448. startTag("uploadstream");
  449. addAttribute(UIDL_ARG_ID, code);
  450. addAttribute(UIDL_ARG_NAME, name);
  451. endTag("uploadstream");
  452. }
  453. /**
  454. * Print single text section.
  455. *
  456. * Prints full text section. The section data is escaped from XML tags and
  457. * surrounded by XML start and end-tags.
  458. */
  459. public void addSection(String sectionTagName, String sectionData)
  460. throws PaintException {
  461. startTag(sectionTagName);
  462. addText(sectionData);
  463. endTag(sectionTagName);
  464. }
  465. /** Add XML dirctly to UIDL */
  466. public void addUIDL(String xml) throws PaintException {
  467. //Ensure that the target is open
  468. if (this.closed)
  469. throw new PaintException(
  470. "Attempted to write to a closed PaintTarget.");
  471. // Make sure that the open start tag is closed before
  472. // anything is written.
  473. ensureClosedTag();
  474. // Escape and write what was given
  475. if (xml != null)
  476. append(xml);
  477. }
  478. /**
  479. * Add XML section with namespace
  480. *
  481. * @see com.itmill.toolkit.terminal.PaintTarget#addXMLSection(String,
  482. * String, String)
  483. */
  484. public void addXMLSection(String sectionTagName, String sectionData,
  485. String namespace) throws PaintException {
  486. //Ensure that the target is open
  487. if (this.closed)
  488. throw new PaintException(
  489. "Attempted to write to a closed PaintTarget.");
  490. startTag(sectionTagName);
  491. if (namespace != null)
  492. addAttribute("xmlns", namespace);
  493. append(">");
  494. mTagArgumentListOpen = false;
  495. if (sectionData != null)
  496. append(sectionData);
  497. endTag(sectionTagName);
  498. }
  499. /**
  500. * Get the UIDL already printed to stream. Paint target must be closed
  501. * before the getUIDL() cn be called.
  502. */
  503. public String getUIDL() {
  504. if (this.closed) {
  505. return uidlBuffer.toString();
  506. }
  507. throw new IllegalStateException(
  508. "Tried to read UIDL from open PaintTarget");
  509. }
  510. /**
  511. * Close the paint target. Paint target must be closed before the getUIDL()
  512. * cn be called. Subsequent attempts to write to paint target. If the target
  513. * was already closed, call to this function is ignored. will generate an
  514. * exception.
  515. */
  516. public void close() throws PaintException {
  517. if (!this.closed) {
  518. this.endTag("changes");
  519. flush();
  520. // Close all
  521. this.uidlBuffer.close();
  522. this.closed = true;
  523. }
  524. }
  525. /**
  526. * Method flush.
  527. */
  528. private void flush() {
  529. this.uidlBuffer.flush();
  530. }
  531. /**
  532. * @see com.itmill.toolkit.terminal.PaintTarget#startTag(com.itmill.toolkit.terminal.Paintable,
  533. * java.lang.String)
  534. */
  535. public boolean startTag(Paintable paintable, String tag)
  536. throws PaintException {
  537. startTag(tag);
  538. String id = manager.getPaintableId(paintable);
  539. paintable.addListener(manager);
  540. addAttribute("id", id);
  541. return false;
  542. }
  543. /*
  544. * (non-Javadoc)
  545. *
  546. * @see com.itmill.toolkit.terminal.PaintTarget#addCharacterData(java.lang.String)
  547. */
  548. public void addCharacterData(String text) throws PaintException {
  549. // TODO: This should check the validity of characters
  550. ensureClosedTag();
  551. append(escapeXML(text));
  552. }
  553. public boolean isTrackPaints() {
  554. return trackPaints;
  555. }
  556. /** Get number of paints.
  557. *
  558. * @return
  559. */
  560. public int getNumberOfPaints() {
  561. return numberOfPaints;
  562. }
  563. /** Set tracking to true or false.
  564. *
  565. * This also resets the number of paints.
  566. * @param trackPaints
  567. * @see #getNumberOfPaints()
  568. */
  569. public void setTrackPaints(boolean enabled) {
  570. this.trackPaints = enabled;
  571. this.numberOfPaints = 0;
  572. }
  573. }