001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.webadapter;
039:
040: import org.millstone.base.terminal.PaintException;
041: import org.xml.sax.InputSource;
042: import org.xml.sax.SAXException;
043: import org.xml.sax.SAXParseException;
044: import org.xml.sax.XMLReader;
045: import org.xml.sax.helpers.XMLReaderFactory;
046:
047: import java.io.BufferedOutputStream;
048: import java.io.IOException;
049: import java.io.OutputStream;
050: import java.io.StringReader;
051: import java.util.Collection;
052: import java.util.LinkedList;
053: import java.util.Hashtable;
054: import java.util.Iterator;
055:
056: import javax.xml.transform.sax.SAXSource;
057: import javax.xml.transform.stream.StreamResult;
058: import javax.xml.transform.ErrorListener;
059: import javax.xml.transform.SourceLocator;
060: import javax.xml.transform.OutputKeys;
061:
062: /** Class implementing the MillStone WebAdapter UIDLTransformer.
063: *
064: * The thansformer should not be created directly; it should be contructed
065: * using getTransformer() provided by UIDLTransformerFactory.
066: *
067: * After the transform has been done, the transformer can be recycled with
068: * releaseTransformer() by UIDLTransformerFactory.
069: *
070: * @author IT Mill Ltd.
071: * @version 3.1.1
072: * @since 3.0
073: */
074:
075: public class UIDLTransformer {
076:
077: /** XSLT factory */
078: protected static javax.xml.transform.TransformerFactory xsltFactory;
079: static {
080: xsltFactory = javax.xml.transform.TransformerFactory
081: .newInstance();
082: if (xsltFactory == null)
083: throw new RuntimeException("Could not instantiate "
084: + "transformer factory. Maybe XSLT processor is "
085: + "not included in classpath.");
086: }
087:
088: /** Source of the transform containing UIDL */
089: private WebPaintTarget paintTarget;
090:
091: /** Holds the type of the transformer. */
092: private UIDLTransformerType transformerType;
093:
094: /** Prepared XSLT transformer for UIDL transformations */
095: private javax.xml.transform.Transformer uidlTransformer;
096:
097: /** Error handled used */
098: private TransformerErrorHandler errorHandler;
099:
100: /** Theme repository used for late error reporting */
101: private ThemeSource themeSource;
102:
103: private WebAdapterServlet webAdapterServlet;
104:
105: /** UIDLTransformer constructor.
106: * @param type Type of the transformer
107: * @param themes Theme implemented by the transformer
108: * @throws UIDLTransformerException UIDLTransformer exception is thrown,
109: * if the transform can not be created.
110: */
111: public UIDLTransformer(UIDLTransformerType type,
112: ThemeSource themes, WebAdapterServlet webAdapterServlet)
113: throws UIDLTransformerException {
114: this .transformerType = type;
115: this .themeSource = themes;
116: this .webAdapterServlet = webAdapterServlet;
117:
118: // Register error handler
119: errorHandler = new TransformerErrorHandler();
120: xsltFactory.setErrorListener(errorHandler);
121:
122: try {
123:
124: // Create XML Reader to be used by
125: // XSLReader as the actual parser object.
126: XMLReader parser = XMLReaderFactory.createXMLReader();
127:
128: // Create XML reader for concatenating
129: // multiple XSL files as one.
130:
131: XMLReader xmlReader = new XSLReader(parser, themes
132: .getXSLStreams(type.getTheme(), type
133: .getWebBrowser()));
134:
135: xmlReader.setErrorHandler(errorHandler);
136:
137: // Create own SAXSource using a dummy inputSource.
138: SAXSource source = new SAXSource(xmlReader,
139: new InputSource());
140: uidlTransformer = xsltFactory.newTransformer(source);
141:
142: if (uidlTransformer != null) {
143:
144: // Register transformer error handler
145: uidlTransformer.setErrorListener(errorHandler);
146:
147: // Ensure HTML output
148: uidlTransformer.setOutputProperty(OutputKeys.METHOD,
149: "html");
150:
151: // Ensure no indent
152: uidlTransformer.setOutputProperty(OutputKeys.INDENT,
153: "no");
154: }
155:
156: // Check if transform itself failed, meaning either
157: // UIDL error or error in XSL/T semantics (like XPath)
158: if (errorHandler.hasFatalErrors()) {
159: throw new UIDLTransformerException(
160: "XSL Transformer creation failed", errorHandler
161: .getFirstFatalError(), errorHandler
162: .getUIDLErrorReport()
163: + "<br /><br />"
164: + errorHandler.getXSLErrorReport(
165: themeSource, transformerType));
166: }
167:
168: } catch (Exception e) {
169: // Pass the new XHTML coded error forwards
170: throw new UIDLTransformerException(e.toString(), e,
171: errorHandler.getXSLErrorReport(themeSource,
172: transformerType));
173: }
174: }
175:
176: /** Get the type of the transformer.
177: * @return Type of the transformer.
178: */
179: public UIDLTransformerType getTransformerType() {
180: return this .transformerType;
181: }
182:
183: /** Attach the output stream to transformer and get corresponding UIDLStream for
184: * writing UI description language trough transform to given output.
185: * @param variableMap The variable map used for UIDL creation.
186: * @return returns UI description language stream, that can be used for writing UIDL to
187: * transformer.
188: */
189: public WebPaintTarget getPaintTarget(HttpVariableMap variableMap) {
190:
191: try {
192: paintTarget = new WebPaintTarget(variableMap,
193: transformerType, webAdapterServlet, transformerType
194: .getTheme());
195: } catch (PaintException e) {
196: throw new IllegalArgumentException(
197: "Failed to instantiate new WebPaintTarget: " + e);
198: }
199: return paintTarget;
200: }
201:
202: /** Reset the transformer, before it can be used again. This also interrupts
203: * any ongoing transform and thus should not be called before the transform
204: * is ready. This is automaticalled by the UIDLTransformFactory, when the UIDLTransformer
205: * has been released.
206: * @see UIDLTransformerFactory#releaseTransformer(UIDLTransformer)
207: */
208: protected void reset() {
209: if (paintTarget != null) {
210: try {
211: paintTarget.close();
212: } catch (PaintException e) {
213: // Ignore this exception
214: }
215: paintTarget = null;
216: }
217: if (errorHandler != null)
218: errorHandler.clear();
219: }
220:
221: /**
222: * Transform the UIDL to HTML and output to the OutputStream.
223: *
224: * @param servletOutputStream - The output stream to render to.
225: */
226: public void transform(OutputStream outputStream)
227: throws UIDLTransformerException {
228:
229: StreamResult result = new StreamResult(
230: new BufferedOutputStream(outputStream));
231:
232: // XSL Transform
233: try {
234: InputSource uidl = new InputSource(new StringReader(
235: paintTarget.getUIDL()));
236: XMLReader reader = org.xml.sax.helpers.XMLReaderFactory
237: .createXMLReader();
238: reader.setErrorHandler(this .errorHandler);
239:
240: // Validate if requested. We validate the UIDL separately,
241: // toget the SAXExceptions instead of TransformerExceptions.
242: // This is required to get the line numbers right.
243: /* FIXME: Disable due abnormalities in DTD handling.
244: if (webAdapterServlet.isDebugMode()) {
245: reader.setFeature(
246: "http://xml.org/sax/features/validation",
247: true);
248: reader.parse(uidl);
249: uidl =
250: new InputSource(new StringReader(paintTarget.getUIDL()));
251:
252: }
253: */
254: SAXSource source = new SAXSource(reader, uidl);
255:
256: uidlTransformer.transform(source, result);
257: } catch (Exception e) {
258: // XSL parsing failed. Pass the new XHTML coded error forwards
259: throw new UIDLTransformerException(e.toString(), e,
260: errorHandler.getUIDLErrorReport());
261: }
262:
263: // Check if transform itself failed, meaning either
264: // UIDL error or error in XSL/T semantics (like XPath)
265: if (errorHandler.hasFatalErrors()) {
266: throw new UIDLTransformerException("UIDL Transform failed",
267: errorHandler.getFirstFatalError(), errorHandler
268: .getUIDLErrorReport()
269: + "<br /><br />"
270: + errorHandler.getXSLErrorReport(
271: themeSource, transformerType));
272: }
273: }
274:
275: protected class TransformerErrorHandler implements ErrorListener,
276: org.xml.sax.ErrorHandler {
277:
278: LinkedList errors = new LinkedList();
279: LinkedList warnings = new LinkedList();
280: LinkedList fatals = new LinkedList();
281: Hashtable rowToErrorMap = new Hashtable();
282: Hashtable errorToRowMap = new Hashtable();
283:
284: public boolean hasNoErrors() {
285: return errors.isEmpty() && warnings.isEmpty()
286: && fatals.isEmpty();
287: }
288:
289: public boolean hasFatalErrors() {
290: return !fatals.isEmpty();
291: }
292:
293: public void clear() {
294: errors.clear();
295: warnings.clear();
296: fatals.clear();
297: }
298:
299: public String toString() {
300: return getHTMLErrors("Fatal Errors", fatals) + "<br />"
301: + getHTMLErrors("Errors", errors) + "<br />"
302: + getHTMLErrors("Warnings", warnings) + "<br />";
303: }
304:
305: private String getHTMLErrors(String title, LinkedList l) {
306: String r = "";
307: r = "<b>" + title + "</b><br />";
308: if (l.size() > 0) {
309: for (Iterator i = l.iterator(); i.hasNext();) {
310: Exception e = (Exception) i.next();
311: if (e instanceof javax.xml.transform.TransformerException) {
312: Integer line = (Integer) errorToRowMap.get(e);
313: r += " - "
314: + WebPaintTarget
315: .escapeXML(((javax.xml.transform.TransformerException) e)
316: .getMessage());
317: Throwable cause = ((javax.xml.transform.TransformerException) e)
318: .getException();
319:
320: // Append cause if available
321: if (cause != null) {
322: r += ": "
323: + WebPaintTarget.escapeXML(cause
324: .getMessage());
325: }
326: r += line != null ? " (line:" + line.intValue()
327: + ")" : " (line unknown)";
328: r += "<br />\n";
329: } else {
330: Integer line = (Integer) errorToRowMap.get(e);
331: r += " - "
332: + WebPaintTarget
333: .escapeXML(e.toString());
334: r += line != null ? " (line:" + line.intValue()
335: + ")" : " (line unknown)";
336: r += "<br />\n";
337:
338: }
339: }
340: }
341: return r;
342: }
343:
344: /**
345: * @see javax.xml.transform.ErrorListener#error(TransformerException)
346: */
347: public void error(
348: javax.xml.transform.TransformerException exception) {
349: if (exception != null) {
350: errors.addLast(exception);
351: SourceLocator l = exception.getLocator();
352: if (l != null) {
353: rowToErrorMap.put(new Integer(
354: ((XSLReader.XSLStreamLocator) l)
355: .getLineNumber()), exception);
356: errorToRowMap.put(exception, new Integer(
357: ((XSLReader.XSLStreamLocator) l)
358: .getLineNumber()));
359: }
360: }
361: }
362:
363: /**
364: * @see javax.xml.transform.ErrorListener#fatalError(TransformerException)
365: */
366: public void fatalError(
367: javax.xml.transform.TransformerException exception) {
368: if (exception != null) {
369: fatals.addLast(exception);
370: SourceLocator l = exception.getLocator();
371: if (l != null) {
372: rowToErrorMap.put(new Integer(l.getLineNumber()),
373: exception);
374: errorToRowMap.put(exception, new Integer(l
375: .getLineNumber()));
376: }
377: }
378: }
379:
380: /**
381: * @see javax.xml.transform.ErrorListener#warning(TransformerException)
382: */
383: public void warning(
384: javax.xml.transform.TransformerException exception) {
385: if (exception != null) {
386: warnings.addLast(exception);
387: SourceLocator l = exception.getLocator();
388: if (l != null) {
389: rowToErrorMap.put(new Integer(l.getLineNumber()),
390: exception);
391: errorToRowMap.put(exception, new Integer(l
392: .getLineNumber()));
393: }
394: }
395: }
396:
397: /** Gets the formated error report on XSL. */
398: public String getXSLErrorReport(ThemeSource themes,
399: UIDLTransformerType type) {
400:
401: // Recreate XSL for error reporting
402: StringBuffer readBuffer = new StringBuffer();
403: try {
404: Collection c = themes.getXSLStreams(type.getTheme(),
405: type.getWebBrowser());
406: for (Iterator i = c.iterator(); i.hasNext();) {
407:
408: java.io.InputStream is = ((ThemeSource.XSLStream) i
409: .next()).getStream();
410: byte[] buffer = new byte[1024];
411: int read = 0;
412: while ((read = is.read(buffer)) >= 0)
413: readBuffer.append(new String(buffer, 0, read));
414: }
415: } catch (IOException ignored) {
416:
417: } catch (ThemeSource.ThemeException ignored) {
418:
419: }
420:
421: String xsl = "XSL Source not avaialable";
422: if (readBuffer != null)
423: xsl = readBuffer.toString();
424:
425: StringBuffer sb = new StringBuffer();
426:
427: // Print formatted UIDL with errors embedded
428:
429: int row = 0;
430: int prev = 0;
431: int index = 0;
432: int errornro = 0;
433: boolean lastLineWasEmpty = false;
434:
435: sb.append(toString());
436: sb
437: .append("<font size=\"+1\"><a href=\"#err1\">"
438: + "Go to first error</a></font>"
439: + "<table width=\"100%\" style=\"border-left: 1px solid black; "
440: + "border-right: 1px solid black; border-bottom: "
441: + "1px solid black; border-top: 1px solid black\""
442: + " cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>"
443: + "<th bgcolor=\"#ddddff\" colspan=\"2\">"
444: + "<font size=\"+2\">XSL</font><br />"
445: + "</th></tr>\n");
446:
447: while ((index = xsl.indexOf('\n', prev)) >= 0) {
448: String line = xsl.substring(prev, index);
449: prev = index + 1;
450: row++;
451:
452: Exception exp = (Exception) rowToErrorMap
453: .get(new Integer(row));
454: line = WebPaintTarget.escapeXML(line);
455: boolean isEmpty = (line.length() == 0 || line
456: .equals("\r"));
457:
458: // Code beautification : Comment lines
459: line = xmlHighlight(line);
460:
461: String head = "";
462: String tail = "";
463:
464: if (exp != null) {
465: errornro++;
466: head = "<a name=\"err" + String.valueOf(errornro)
467: + "\"><table width=\"100%\">"
468: + "<tr><th bgcolor=\"#ff3030\">"
469: + exp.getLocalizedMessage() + "</th></tr>"
470: + "<tr><td bgcolor=\"#ffcccc\">";
471: tail = "</tr><tr><th bgcolor=\"#ff3030\">"
472: + (errornro > 1 ? "<a href=\"#err"
473: + String.valueOf(errornro - 1)
474: + "\">Previous error</a> " : "")
475: + "<a href=\"#err"
476: + String.valueOf(errornro + 1)
477: + "\">Next error</a>"
478: + "</th></tr></table></a>\n";
479: }
480:
481: if (!(isEmpty && lastLineWasEmpty))
482: sb
483: .append("<tr"
484: + ((row % 10) > 4 ? " bgcolor=\"#eeeeff\""
485: : "")
486: + "><td style=\"border-right: 1px solid gray\"> "
487: + String.valueOf(row)
488: + " </td><td>" + head
489: + "<nobr>" + line + "</nobr>"
490: + tail + "</td></tr>\n");
491:
492: lastLineWasEmpty = isEmpty;
493:
494: }
495:
496: sb.append("</table>\n");
497:
498: return sb.toString();
499: }
500:
501: /** Gets the formated error report on UIDL. */
502: public String getUIDLErrorReport() {
503:
504: String uidl = "UIDL Source Not Available.";
505: if (paintTarget != null)
506: uidl = paintTarget.getUIDL();
507: StringBuffer sb = new StringBuffer();
508:
509: // Print formatted UIDL with errors embedded
510: int row = 0;
511: int prev = 0;
512: int index = 0;
513: boolean lastLineWasEmpty = false;
514:
515: // Append error report
516: sb.append(toString());
517:
518: // Append UIDL
519: sb
520: .append("<table width=\"100%\" style=\"border-left: 1px solid black; "
521: + "border-right: 1px solid black; border-bottom: "
522: + "1px solid black; border-top: 1px solid black\""
523: + " cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>"
524: + "<th bgcolor=\"#ddddff\" colspan=\"2\">"
525: + "<font size=\"+2\">UIDL</font><br />"
526: + "</th></tr>\n");
527:
528: while ((index = uidl.indexOf('\n', prev)) >= 0) {
529: String line = uidl.substring(prev, index);
530: prev = index + 1;
531: row++;
532:
533: line = WebPaintTarget.escapeXML(line);
534: boolean isEmpty = (line.length() == 0 || line
535: .equals("\r"));
536:
537: // Highlight source
538: // line = xmlHighlight(line);
539:
540: if (!(isEmpty && lastLineWasEmpty))
541: sb
542: .append("<tr"
543: + ((row % 10) > 4 ? " bgcolor=\"#eeeeff\""
544: : "")
545: + "><td style=\"border-right: 1px solid gray\"> "
546: + String.valueOf(row)
547: + " </td><td>" + "<nobr>"
548: + line + "</nobr>" + "</td></tr>\n");
549:
550: lastLineWasEmpty = isEmpty;
551: }
552:
553: sb.append("</table>\n");
554:
555: return sb.toString();
556: }
557:
558: /** Highlight the XML source. */
559: private String xmlHighlight(String xmlSnippet) {
560: String res = xmlSnippet;
561:
562: // Code beautification : Comment lines
563: DebugWindow.replaceAll(res, "<!--",
564: "<SPAN STYLE=\"color: #00dd00\"><!--");
565: res = DebugWindow
566: .replaceAll(res, "-->", "--></SPAN>");
567:
568: // nbsp instead of blanks
569: String l = " ";
570: while (res.startsWith(" ")) {
571: l += " ";
572: res = res.substring(1, res.length());
573: }
574: res = l + res;
575:
576: return res;
577: }
578:
579: /** Get the first fatal error. */
580: public Throwable getFirstFatalError() {
581: return (Throwable) fatals.iterator().next();
582: }
583:
584: /**
585: * @see org.xml.sax.ErrorHandler#error(SAXParseException)
586: */
587: public void error(SAXParseException exception)
588: throws SAXException {
589: errors.addLast(exception);
590: rowToErrorMap.put(new Integer(exception.getLineNumber()),
591: exception);
592: errorToRowMap.put(exception, new Integer(exception
593: .getLineNumber()));
594: }
595:
596: /**
597: * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
598: */
599: public void fatalError(SAXParseException exception)
600: throws SAXException {
601: fatals.addLast(exception);
602: rowToErrorMap.put(new Integer(exception.getLineNumber()),
603: exception);
604: errorToRowMap.put(exception, new Integer(exception
605: .getLineNumber()));
606: }
607:
608: /**
609: * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
610: */
611: public void warning(SAXParseException exception)
612: throws SAXException {
613: warnings.addLast(exception);
614: rowToErrorMap.put(new Integer(exception.getLineNumber()),
615: exception);
616: errorToRowMap.put(exception, new Integer(exception
617: .getLineNumber()));
618: }
619:
620: }
621:
622: }
|