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.

UIDLTransformer.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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 org.xml.sax.InputSource;
  20. import org.xml.sax.SAXException;
  21. import org.xml.sax.SAXParseException;
  22. import org.xml.sax.XMLReader;
  23. import org.xml.sax.helpers.XMLReaderFactory;
  24. import com.itmill.toolkit.terminal.PaintException;
  25. import java.io.BufferedOutputStream;
  26. import java.io.IOException;
  27. import java.io.OutputStream;
  28. import java.io.StringReader;
  29. import java.util.Collection;
  30. import java.util.LinkedList;
  31. import java.util.Hashtable;
  32. import java.util.Iterator;
  33. import javax.xml.transform.sax.SAXSource;
  34. import javax.xml.transform.stream.StreamResult;
  35. import javax.xml.transform.ErrorListener;
  36. import javax.xml.transform.SourceLocator;
  37. import javax.xml.transform.OutputKeys;
  38. /** Class implementing the UIDLTransformer.
  39. *
  40. * The thansformer should not be created directly; it should be contructed
  41. * using getTransformer() provided by UIDLTransformerFactory.
  42. *
  43. * After the transform has been done, the transformer can be recycled with
  44. * releaseTransformer() by UIDLTransformerFactory.
  45. *
  46. * @author IT Mill Ltd.
  47. * @version @VERSION@
  48. * @since 3.0
  49. */
  50. public class UIDLTransformer {
  51. /** XSLT factory */
  52. protected static javax.xml.transform.TransformerFactory xsltFactory;
  53. static {
  54. xsltFactory = javax.xml.transform.TransformerFactory.newInstance();
  55. if (xsltFactory == null)
  56. throw new RuntimeException(
  57. "Could not instantiate "
  58. + "transformer factory. Maybe XSLT processor is "
  59. + "not included in classpath.");
  60. }
  61. /** Source of the transform containing UIDL */
  62. private WebPaintTarget paintTarget;
  63. /** Holds the type of the transformer. */
  64. private UIDLTransformerType transformerType;
  65. /** Prepared XSLT transformer for UIDL transformations */
  66. private javax.xml.transform.Transformer uidlTransformer;
  67. /** Error handled used */
  68. private TransformerErrorHandler errorHandler;
  69. /** Theme repository used for late error reporting */
  70. private ThemeSource themeSource;
  71. private ApplicationServlet webAdapterServlet;
  72. /** UIDLTransformer constructor.
  73. * @param type Type of the transformer
  74. * @param themes Theme implemented by the transformer
  75. * @throws UIDLTransformerException UIDLTransformer exception is thrown,
  76. * if the transform can not be created.
  77. */
  78. public UIDLTransformer(
  79. UIDLTransformerType type,
  80. ThemeSource themes,
  81. ApplicationServlet webAdapterServlet)
  82. throws UIDLTransformerException {
  83. this.transformerType = type;
  84. this.themeSource = themes;
  85. this.webAdapterServlet = webAdapterServlet;
  86. // Register error handler
  87. errorHandler = new TransformerErrorHandler();
  88. xsltFactory.setErrorListener(errorHandler);
  89. try {
  90. // Create XML Reader to be used by
  91. // XSLReader as the actual parser object.
  92. XMLReader parser = XMLReaderFactory.createXMLReader();
  93. // Create XML reader for concatenating
  94. // multiple XSL files as one.
  95. XMLReader xmlReader =
  96. new XSLReader(
  97. parser,
  98. themes.getXSLStreams(
  99. type.getTheme(),
  100. type.getWebBrowser()));
  101. xmlReader.setErrorHandler(errorHandler);
  102. // Create own SAXSource using a dummy inputSource.
  103. SAXSource source = new SAXSource(xmlReader, new InputSource());
  104. uidlTransformer = xsltFactory.newTransformer(source);
  105. if (uidlTransformer != null) {
  106. // Register transformer error handler
  107. uidlTransformer.setErrorListener(errorHandler);
  108. // Ensure HTML output
  109. uidlTransformer.setOutputProperty(OutputKeys.METHOD, "html");
  110. // Ensure no indent
  111. uidlTransformer.setOutputProperty(OutputKeys.INDENT, "no");
  112. }
  113. // Check if transform itself failed, meaning either
  114. // UIDL error or error in XSL/T semantics (like XPath)
  115. if (errorHandler.hasFatalErrors()) {
  116. throw new UIDLTransformerException(
  117. "XSL Transformer creation failed",
  118. errorHandler.getFirstFatalError(),
  119. errorHandler.getUIDLErrorReport()
  120. + "<br /><br />"
  121. + errorHandler.getXSLErrorReport(
  122. themeSource,
  123. transformerType));
  124. }
  125. } catch (Exception e) {
  126. // Pass the new XHTML coded error forwards
  127. throw new UIDLTransformerException(
  128. e.toString(),
  129. e,
  130. errorHandler.getXSLErrorReport(themeSource, transformerType));
  131. }
  132. }
  133. /** Get the type of the transformer.
  134. * @return Type of the transformer.
  135. */
  136. public UIDLTransformerType getTransformerType() {
  137. return this.transformerType;
  138. }
  139. /** Attach the output stream to transformer and get corresponding UIDLStream for
  140. * writing UI description language trough transform to given output.
  141. * @param variableMap The variable map used for UIDL creation.
  142. * @return returns UI description language stream, that can be used for writing UIDL to
  143. * transformer.
  144. */
  145. public WebPaintTarget getPaintTarget(HttpVariableMap variableMap) {
  146. try {
  147. paintTarget =
  148. new WebPaintTarget(
  149. variableMap,
  150. transformerType,
  151. webAdapterServlet,
  152. transformerType.getTheme());
  153. } catch (PaintException e) {
  154. throw new IllegalArgumentException(
  155. "Failed to instantiate new WebPaintTarget: " + e);
  156. }
  157. return paintTarget;
  158. }
  159. /** Reset the transformer, before it can be used again. This also interrupts
  160. * any ongoing transform and thus should not be called before the transform
  161. * is ready. This is automaticalled by the UIDLTransformFactory, when the UIDLTransformer
  162. * has been released.
  163. * @see UIDLTransformerFactory#releaseTransformer(UIDLTransformer)
  164. */
  165. protected void reset() {
  166. if (paintTarget != null) {
  167. try {
  168. paintTarget.close();
  169. } catch (PaintException e) {
  170. // Ignore this exception
  171. }
  172. paintTarget = null;
  173. }
  174. if (errorHandler != null)
  175. errorHandler.clear();
  176. }
  177. /**
  178. * Transform the UIDL to HTML and output to the OutputStream.
  179. *
  180. * @param servletOutputStream - The output stream to render to.
  181. */
  182. public void transform(OutputStream outputStream)
  183. throws UIDLTransformerException {
  184. StreamResult result =
  185. new StreamResult(new BufferedOutputStream(outputStream));
  186. // XSL Transform
  187. try {
  188. InputSource uidl =
  189. new InputSource(new StringReader(paintTarget.getUIDL()));
  190. XMLReader reader =
  191. org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
  192. reader.setErrorHandler(this.errorHandler);
  193. // Validate if requested. We validate the UIDL separately,
  194. // toget the SAXExceptions instead of TransformerExceptions.
  195. // This is required to get the line numbers right.
  196. /* FIXME: Disable due abnormalities in DTD handling.
  197. if (webAdapterServlet.isDebugMode()) {
  198. reader.setFeature(
  199. "http://xml.org/sax/features/validation",
  200. true);
  201. reader.parse(uidl);
  202. uidl =
  203. new InputSource(new StringReader(paintTarget.getUIDL()));
  204. }
  205. */
  206. SAXSource source = new SAXSource(reader, uidl);
  207. uidlTransformer.transform(source, result);
  208. } catch (Exception e) {
  209. // XSL parsing failed. Pass the new XHTML coded error forwards
  210. throw new UIDLTransformerException(
  211. e.toString(),
  212. e,
  213. errorHandler.getUIDLErrorReport());
  214. }
  215. // Check if transform itself failed, meaning either
  216. // UIDL error or error in XSL/T semantics (like XPath)
  217. if (errorHandler.hasFatalErrors()) {
  218. throw new UIDLTransformerException(
  219. "UIDL Transform failed",
  220. errorHandler.getFirstFatalError(),
  221. errorHandler.getUIDLErrorReport()
  222. + "<br /><br />"
  223. + errorHandler.getXSLErrorReport(
  224. themeSource,
  225. transformerType));
  226. }
  227. }
  228. protected class TransformerErrorHandler
  229. implements ErrorListener, org.xml.sax.ErrorHandler {
  230. LinkedList errors = new LinkedList();
  231. LinkedList warnings = new LinkedList();
  232. LinkedList fatals = new LinkedList();
  233. Hashtable rowToErrorMap = new Hashtable();
  234. Hashtable errorToRowMap = new Hashtable();
  235. public boolean hasNoErrors() {
  236. return errors.isEmpty() && warnings.isEmpty() && fatals.isEmpty();
  237. }
  238. public boolean hasFatalErrors() {
  239. return !fatals.isEmpty();
  240. }
  241. public void clear() {
  242. errors.clear();
  243. warnings.clear();
  244. fatals.clear();
  245. }
  246. public String toString() {
  247. return getHTMLErrors("Fatal Errors", fatals)
  248. + "<br />"
  249. + getHTMLErrors("Errors", errors)
  250. + "<br />"
  251. + getHTMLErrors("Warnings", warnings)
  252. + "<br />";
  253. }
  254. private String getHTMLErrors(String title, LinkedList l) {
  255. String r = "";
  256. r = "<b>" + title + "</b><br />";
  257. if (l.size() > 0) {
  258. for (Iterator i = l.iterator(); i.hasNext();) {
  259. Exception e = (Exception) i.next();
  260. if (e
  261. instanceof javax.xml.transform.TransformerException) {
  262. Integer line = (Integer) errorToRowMap.get(e);
  263. r += " - "
  264. + WebPaintTarget.escapeXML(
  265. ((javax.xml.transform.TransformerException) e)
  266. .getMessage());
  267. Throwable cause =
  268. ((javax.xml.transform.TransformerException) e)
  269. .getException();
  270. // Append cause if available
  271. if (cause != null) {
  272. r += ": "
  273. + WebPaintTarget.escapeXML(cause.getMessage());
  274. }
  275. r += line != null
  276. ? " (line:" + line.intValue() + ")"
  277. : " (line unknown)";
  278. r += "<br />\n";
  279. } else {
  280. Integer line = (Integer) errorToRowMap.get(e);
  281. r += " - " + WebPaintTarget.escapeXML(e.toString());
  282. r += line != null
  283. ? " (line:" + line.intValue() + ")"
  284. : " (line unknown)";
  285. r += "<br />\n";
  286. }
  287. }
  288. }
  289. return r;
  290. }
  291. /**
  292. * @see javax.xml.transform.ErrorListener#error(TransformerException)
  293. */
  294. public void error(javax.xml.transform.TransformerException exception) {
  295. if (exception != null) {
  296. errors.addLast(exception);
  297. SourceLocator l = exception.getLocator();
  298. if (l != null) {
  299. rowToErrorMap.put(
  300. new Integer(
  301. ((XSLReader.XSLStreamLocator) l).getLineNumber()),
  302. exception);
  303. errorToRowMap.put(
  304. exception,
  305. new Integer(
  306. ((XSLReader.XSLStreamLocator) l).getLineNumber()));
  307. }
  308. }
  309. }
  310. /**
  311. * @see javax.xml.transform.ErrorListener#fatalError(TransformerException)
  312. */
  313. public void fatalError(
  314. javax.xml.transform.TransformerException exception) {
  315. if (exception != null) {
  316. fatals.addLast(exception);
  317. SourceLocator l = exception.getLocator();
  318. if (l != null) {
  319. rowToErrorMap.put(
  320. new Integer(l.getLineNumber()),
  321. exception);
  322. errorToRowMap.put(
  323. exception,
  324. new Integer(l.getLineNumber()));
  325. }
  326. }
  327. }
  328. /**
  329. * @see javax.xml.transform.ErrorListener#warning(TransformerException)
  330. */
  331. public void warning(
  332. javax.xml.transform.TransformerException exception) {
  333. if (exception != null) {
  334. warnings.addLast(exception);
  335. SourceLocator l = exception.getLocator();
  336. if (l != null) {
  337. rowToErrorMap.put(
  338. new Integer(l.getLineNumber()),
  339. exception);
  340. errorToRowMap.put(
  341. exception,
  342. new Integer(l.getLineNumber()));
  343. }
  344. }
  345. }
  346. /** Gets the formated error report on XSL. */
  347. public String getXSLErrorReport(
  348. ThemeSource themes,
  349. UIDLTransformerType type) {
  350. // Recreate XSL for error reporting
  351. StringBuffer readBuffer = new StringBuffer();
  352. try {
  353. Collection c =
  354. themes.getXSLStreams(type.getTheme(), type.getWebBrowser());
  355. for (Iterator i = c.iterator(); i.hasNext();) {
  356. java.io.InputStream is =
  357. ((ThemeSource.XSLStream) i.next()).getStream();
  358. byte[] buffer = new byte[1024];
  359. int read = 0;
  360. while ((read = is.read(buffer)) >= 0)
  361. readBuffer.append(new String(buffer, 0, read));
  362. }
  363. } catch (IOException ignored) {
  364. } catch (ThemeSource.ThemeException ignored) {
  365. }
  366. String xsl = "XSL Source not avaialable";
  367. if (readBuffer != null)
  368. xsl = readBuffer.toString();
  369. StringBuffer sb = new StringBuffer();
  370. // Print formatted UIDL with errors embedded
  371. int row = 0;
  372. int prev = 0;
  373. int index = 0;
  374. int errornro = 0;
  375. boolean lastLineWasEmpty = false;
  376. sb.append(toString());
  377. sb.append(
  378. "<font size=\"+1\"><a href=\"#err1\">"
  379. + "Go to first error</a></font>"
  380. + "<table width=\"100%\" style=\"border-left: 1px solid black; "
  381. + "border-right: 1px solid black; border-bottom: "
  382. + "1px solid black; border-top: 1px solid black\""
  383. + " cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>"
  384. + "<th bgcolor=\"#ddddff\" colspan=\"2\">"
  385. + "<font size=\"+2\">XSL</font><br />"
  386. + "</th></tr>\n");
  387. while ((index = xsl.indexOf('\n', prev)) >= 0) {
  388. String line = xsl.substring(prev, index);
  389. prev = index + 1;
  390. row++;
  391. Exception exp = (Exception) rowToErrorMap.get(new Integer(row));
  392. line = WebPaintTarget.escapeXML(line);
  393. boolean isEmpty = (line.length() == 0 || line.equals("\r"));
  394. // Code beautification : Comment lines
  395. line = xmlHighlight(line);
  396. String head = "";
  397. String tail = "";
  398. if (exp != null) {
  399. errornro++;
  400. head =
  401. "<a name=\"err"
  402. + String.valueOf(errornro)
  403. + "\"><table width=\"100%\">"
  404. + "<tr><th bgcolor=\"#ff3030\">"
  405. + exp.getLocalizedMessage()
  406. + "</th></tr>"
  407. + "<tr><td bgcolor=\"#ffcccc\">";
  408. tail =
  409. "</tr><tr><th bgcolor=\"#ff3030\">"
  410. + (errornro > 1
  411. ? "<a href=\"#err"
  412. + String.valueOf(errornro - 1)
  413. + "\">Previous error</a> "
  414. : "")
  415. + "<a href=\"#err"
  416. + String.valueOf(errornro + 1)
  417. + "\">Next error</a>"
  418. + "</th></tr></table></a>\n";
  419. }
  420. if (!(isEmpty && lastLineWasEmpty))
  421. sb.append(
  422. "<tr"
  423. + ((row % 10) > 4 ? " bgcolor=\"#eeeeff\"" : "")
  424. + "><td style=\"border-right: 1px solid gray\">&nbsp;"
  425. + String.valueOf(row)
  426. + "&nbsp;</td><td>"
  427. + head
  428. + "<nobr>"
  429. + line
  430. + "</nobr>"
  431. + tail
  432. + "</td></tr>\n");
  433. lastLineWasEmpty = isEmpty;
  434. }
  435. sb.append("</table>\n");
  436. return sb.toString();
  437. }
  438. /** Gets the formated error report on UIDL. */
  439. public String getUIDLErrorReport() {
  440. String uidl = "UIDL Source Not Available.";
  441. if (paintTarget != null)
  442. uidl = paintTarget.getUIDL();
  443. StringBuffer sb = new StringBuffer();
  444. // Print formatted UIDL with errors embedded
  445. int row = 0;
  446. int prev = 0;
  447. int index = 0;
  448. boolean lastLineWasEmpty = false;
  449. // Append error report
  450. sb.append(toString());
  451. // Append UIDL
  452. sb.append(
  453. "<table width=\"100%\" style=\"border-left: 1px solid black; "
  454. + "border-right: 1px solid black; border-bottom: "
  455. + "1px solid black; border-top: 1px solid black\""
  456. + " cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>"
  457. + "<th bgcolor=\"#ddddff\" colspan=\"2\">"
  458. + "<font size=\"+2\">UIDL</font><br />"
  459. + "</th></tr>\n");
  460. while ((index = uidl.indexOf('\n', prev)) >= 0) {
  461. String line = uidl.substring(prev, index);
  462. prev = index + 1;
  463. row++;
  464. line = WebPaintTarget.escapeXML(line);
  465. boolean isEmpty = (line.length() == 0 || line.equals("\r"));
  466. // Highlight source
  467. // line = xmlHighlight(line);
  468. if (!(isEmpty && lastLineWasEmpty))
  469. sb.append(
  470. "<tr"
  471. + ((row % 10) > 4 ? " bgcolor=\"#eeeeff\"" : "")
  472. + "><td style=\"border-right: 1px solid gray\">&nbsp;"
  473. + String.valueOf(row)
  474. + "&nbsp;</td><td>"
  475. + "<nobr>"
  476. + line
  477. + "</nobr>"
  478. + "</td></tr>\n");
  479. lastLineWasEmpty = isEmpty;
  480. }
  481. sb.append("</table>\n");
  482. return sb.toString();
  483. }
  484. /** Highlight the XML source. */
  485. private String xmlHighlight(String xmlSnippet) {
  486. String res = xmlSnippet;
  487. // Code beautification : Comment lines
  488. DebugWindow.replaceAll(
  489. res,
  490. "&lt;!--",
  491. "<SPAN STYLE=\"color: #00dd00\">&lt;!--");
  492. res = DebugWindow.replaceAll(res, "--&gt;", "--&gt;</SPAN>");
  493. // nbsp instead of blanks
  494. String l = "&nbsp;";
  495. while (res.startsWith(" ")) {
  496. l += "&nbsp;";
  497. res = res.substring(1, res.length());
  498. }
  499. res = l + res;
  500. return res;
  501. }
  502. /** Get the first fatal error. */
  503. public Throwable getFirstFatalError() {
  504. return (Throwable) fatals.iterator().next();
  505. }
  506. /**
  507. * @see org.xml.sax.ErrorHandler#error(SAXParseException)
  508. */
  509. public void error(SAXParseException exception) throws SAXException {
  510. errors.addLast(exception);
  511. rowToErrorMap.put(
  512. new Integer(exception.getLineNumber()),
  513. exception);
  514. errorToRowMap.put(
  515. exception,
  516. new Integer(exception.getLineNumber()));
  517. }
  518. /**
  519. * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
  520. */
  521. public void fatalError(SAXParseException exception)
  522. throws SAXException {
  523. fatals.addLast(exception);
  524. rowToErrorMap.put(
  525. new Integer(exception.getLineNumber()),
  526. exception);
  527. errorToRowMap.put(
  528. exception,
  529. new Integer(exception.getLineNumber()));
  530. }
  531. /**
  532. * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
  533. */
  534. public void warning(SAXParseException exception) throws SAXException {
  535. warnings.addLast(exception);
  536. rowToErrorMap.put(
  537. new Integer(exception.getLineNumber()),
  538. exception);
  539. errorToRowMap.put(
  540. exception,
  541. new Integer(exception.getLineNumber()));
  542. }
  543. }
  544. }