source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/XMLTransformer.java@ 26513

Last change on this file since 26513 was 26513, checked in by ak19, 11 years ago

Adding in original error message when a transformation of in-memory xml and xslt files fails, on Dr Bainbridge's request

  • Property svn:keywords set to Author Date Id Revision
File size: 33.5 KB
Line 
1/*
2 * XMLTransformer.java
3 * Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19package org.greenstone.gsdl3.util;
20
21import java.io.BufferedReader;
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.FileNotFoundException;
25import java.io.FileWriter;
26import java.io.InputStreamReader;
27import java.io.IOException;
28import java.io.StringReader;
29import java.io.StringWriter;
30import java.io.UnsupportedEncodingException;
31import java.net.MalformedURLException;
32import java.net.URISyntaxException;
33import java.net.URI;
34import java.net.URL;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.Map;
38import java.util.Set;
39
40import javax.xml.parsers.DocumentBuilderFactory;
41import javax.xml.transform.ErrorListener;
42import javax.xml.transform.Source;
43import javax.xml.transform.Transformer;
44import javax.xml.transform.TransformerConfigurationException;
45import javax.xml.transform.TransformerException;
46import javax.xml.transform.TransformerFactory;
47import javax.xml.transform.URIResolver;
48import javax.xml.transform.dom.DOMResult;
49import javax.xml.transform.dom.DOMSource;
50import javax.xml.transform.stream.StreamResult;
51import javax.xml.transform.stream.StreamSource;
52
53import org.apache.log4j.Logger;
54import org.greenstone.util.GlobalProperties;
55import org.w3c.dom.Document;
56import org.w3c.dom.Element;
57import org.w3c.dom.Node;
58
59/**
60 * XMLTransformer - utility class for greenstone
61 *
62 * transforms xml using xslt
63 *
64 * @author Katherine Don
65 * @version $Revision: 26513 $
66 */
67public class XMLTransformer
68{
69 private static int debugFileCount = 0; // for unique filenames when debugging XML transformations with physical files
70
71 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.XMLTransformer.class.getName());
72
73 /** The transformer factory we're using */
74 TransformerFactory t_factory = null;
75
76 /**
77 * The no-arguments constructor.
78 *
79 * Any exceptions thrown are caught internally
80 *
81 * @see javax.xml.transform.TransformerFactory
82 */
83 public XMLTransformer()
84 {
85 // http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/index.html?javax/xml/transform/TransformerFactory.html states that
86 // TransformerFactory.newInstance() looks in jar files for a Factory specified in META-INF/services/javax.xml.transform.TransformerFactory,
87 // else it will use the "platform default"
88 // In this case: xalan.jar's META-INF/services/javax.xml.transform.TransformerFactory contains org.apache.xalan.processor.TransformerFactoryImpl
89 // as required.
90
91 // This means we no longer have to do a System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
92 // followed by a this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
93 // The System.setProperty step to force the TransformerFactory implementation that gets used, conflicts with
94 // Fedora (visiting the Greenstone server pages breaks the Greenstone-tomcat hosted Fedora pages) as Fedora
95 // does not include the xalan.jar and therefore can't then find the xalan TransformerFactory explicitly set.
96
97 // Gone back to forcing use of xalan transformer, since other jars like crimson.jar, which may be on some
98 // classpaths, could be be chosen as the TransformerFactory implementation over xalan. This is what used to
99 // give problems before. Instead, have placed copies of the jars that Fedora needs (xalan.jar and serializer.jar
100 // and the related xsltc.jar which it may need) into packages/tomcat/lib so that it's on the server's classpath
101 // and will be found by Fedora.
102
103 // make sure we are using the xalan transformer
104 System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
105 try
106 {
107 this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
108 //this.t_factory = TransformerFactory.newInstance();
109 this.t_factory.setErrorListener(new TransformErrorListener()); // handle errors in the xml Source used to instantiate transformers
110 }
111 catch (Exception e)
112 {
113 logger.error("exception creating t_factory " + e.getMessage());
114 }
115 }
116
117 /**
118 * Transform an XML document using a XSLT stylesheet
119 *
120 * @param stylesheet
121 * a filename for an XSLT stylesheet
122 * @param xml_in
123 * the XML to be transformed
124 * @return the transformed XML
125 */
126 public String transform(String stylesheet, String xml_in)
127 {
128
129 try
130 {
131 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
132 transformerErrorListener.setStylesheet(stylesheet);
133 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
134 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
135
136 // Use the Transformer to transform an XML Source and send the output to a Result object.
137 StringWriter output = new StringWriter();
138 StreamSource streamSource = new StreamSource(new StringReader(xml_in));
139 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
140 transformer.transform(streamSource, new StreamResult(output));
141 return output.toString();
142 }
143 catch (TransformerConfigurationException e)
144 {
145 logger.error("couldn't create transformer object: " + e.getMessageAndLocation());
146 logger.error(e.getLocationAsString());
147 return "";
148 }
149 catch (TransformerException e)
150 {
151 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
152 return "";
153 }
154 }
155
156 public String transformToString(Document stylesheet, Document source)
157 {
158 return transformToString(stylesheet, source, null);
159 }
160
161 public String transformToString(Document stylesheet, Document source, HashMap parameters)
162 {
163
164 try
165 {
166 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
167 transformerErrorListener.setStylesheet(stylesheet);
168 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
169 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
170 if (parameters != null)
171 {
172 Set params = parameters.entrySet();
173 Iterator i = params.iterator();
174 while (i.hasNext())
175 {
176 Map.Entry m = (Map.Entry) i.next();
177 transformer.setParameter((String) m.getKey(), m.getValue());
178 }
179 }
180 //transformer.setParameter("page_lang", source.getDocumentElement().getAttribute(GSXML.LANG_ATT));
181
182 // Use the Transformer to transform an XML Source and send the output to a Result object.
183 StringWriter output = new StringWriter();
184 DOMSource domSource = new DOMSource(source);
185
186 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
187 transformer.transform(domSource, new StreamResult(output));
188 return output.toString();
189 }
190 catch (TransformerConfigurationException e)
191 {
192 logger.error("couldn't create transformer object: " + e.getMessageAndLocation());
193 logger.error(e.getLocationAsString());
194 return "";
195 }
196 catch (TransformerException e)
197 {
198 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
199 return "";
200 }
201 }
202
203 /**
204 * Transform an XML document using a XSLT stylesheet, but using a DOMResult
205 * whose node should be set to the Document donated by resultNode
206 */
207 public Node transform_withResultNode(Document stylesheet, Document source, Document resultNode)
208 {
209 return transform(stylesheet, source, null, null, resultNode);
210 }
211
212 public Node transform(Document stylesheet, Document source)
213 {
214 return transform(stylesheet, source, null, null, null);
215 }
216
217 public Node transform(Document stylesheet, Document source, HashMap<String, Comparable> parameters)
218 {
219 return transform(stylesheet, source, parameters, null, null);
220 }
221
222 public Node transform(Document stylesheet, Document source, HashMap<String, Comparable> parameters, Document docDocType)
223 {
224 return transform(stylesheet, source, parameters, docDocType, null);
225 }
226
227 protected Node transform(Document stylesheet, Document source, HashMap<String, Comparable> parameters, Document docDocType, Document resultNode)
228 {
229 try
230 {
231 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
232 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
233 transformerErrorListener.setStylesheet(stylesheet);
234 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
235 //logger.info("XMLTransformer transformer is " + transformer); //done in ErrorListener
236
237 if (parameters != null)
238 {
239 Set params = parameters.entrySet();
240 Iterator i = params.iterator();
241 while (i.hasNext())
242 {
243 Map.Entry m = (Map.Entry) i.next();
244 transformer.setParameter((String) m.getKey(), m.getValue());
245 }
246 }
247
248 // When we transform the DOMResult, we need to make sure the result of
249 // the transformation has a DocType. For that to happen, we need to create
250 // the DOMResult using a Document with a predefined docType.
251 // If we don't have a DocType then do the transformation with a DOMResult
252 // that does not contain any doctype (like we use to do before).
253 DOMResult result = docDocType == null ? new DOMResult() : new DOMResult(docDocType);
254 if (resultNode != null)
255 {
256 result.setNode(resultNode);
257 }
258 DOMSource domSource = new DOMSource(source);
259 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
260 transformer.transform(domSource, result);
261 return result.getNode(); // pass the entire document
262 }
263 catch (TransformerConfigurationException e)
264 {
265 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)" + "\ncouldn't create transformer object", e);
266 }
267 catch (TransformerException e)
268 {
269 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)" + "\ncouldn't transform the source", e);
270 }
271 }
272
273 /**
274 When transforming an XML with an XSLT from the command-line, there's two problems if calling
275 XMLTransformer.transform(File xslt, File xml):
276
277 1. Need to run the transformation process from the location of the stylesheet, else it can't find
278 the stylesheet in order to load it. This is resolved by setting the SystemID for the stylesheet.
279
280 2. The XSLT stylesheet has &lt;xsl:import&gt; statements importing further XSLTs which are furthermore
281 specified by relative paths, requiring that the XSLT (and perhaps also XML) input file is located
282 in the folder from which the relative paths of the import statements are specified. This is resolved
283 by working out the absolute path of each imported XSLT file and setting their SystemIDs too.
284
285 Without solving these problems, things will only work if we 1. run the transformation from the
286 web/interfaces/default/transform folder and 2. need to have the input XSLT file (and XML?)
287 placed in the web/interfacces/default/transform folder too
288
289 Both parts of the solution are described in:
290 http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-filse
291
292 After the systemID on the XSLT input file is set, we can run the command from the toplevel GS3
293 folder.
294 After resolving the URIs of the XSLT files imported by the input XSLT, by setting their systemIDs,
295 the input XSLT (and XML) file need not be located in web/interfaces/default/transform anymore.
296 */
297 public Node transform(File stylesheet, File source, String interfaceName, Document docDocType)
298 {
299 try
300 {
301 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
302 transformerErrorListener.setStylesheet(stylesheet);
303
304 // http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-files
305 Source xsltSource = new StreamSource(new InputStreamReader(new FileInputStream(stylesheet), "UTF-8"));
306 URI systemID = stylesheet.toURI();
307 try {
308 URL url = systemID.toURL();
309 xsltSource.setSystemId(url.toExternalForm());
310 } catch (MalformedURLException mue) {
311 logger.error("Warning: Unable to set systemID for stylesheet to " + systemID);
312 logger.error("Got exception: " + mue.getMessage(), mue);
313 }
314
315 this.t_factory.setURIResolver(new ClasspathResourceURIResolver(interfaceName));
316 Transformer transformer = this.t_factory.newTransformer(xsltSource);
317 // Alternative way of instantiating a newTransformer():
318 //Templates cachedXSLT = this.t_factory.newTemplates(xsltSource);
319 //Transformer transformer = cachedXSLT.newTransformer();
320
321 DOMResult result = (docDocType == null) ? new DOMResult() : new DOMResult(docDocType);
322 StreamSource streamSource = new StreamSource(new InputStreamReader(new FileInputStream(source), "UTF-8"));
323
324 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
325
326 transformer.transform(streamSource, result);
327 return result.getNode().getFirstChild();
328 }
329 catch (TransformerConfigurationException e)
330 {
331 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't create transformer object for files\n" + stylesheet + "\n" + source, e);
332 }
333 catch (TransformerException e)
334 {
335 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't transform the source for files\n" + stylesheet + "\n" + source, e);
336 }
337 catch (UnsupportedEncodingException e)
338 {
339 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't read file due to an unsupported encoding\n" + stylesheet + "\n" + source, e);
340 }
341 catch (FileNotFoundException e)
342 {
343 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't find the file specified\n" + stylesheet + "\n" + source, e);
344 }
345 }
346
347 /**
348 * Class for resolving the relative paths used for &lt;xsl:import&gt;s
349 * when transforming XML with an XSLT
350 */
351 class ClasspathResourceURIResolver implements URIResolver {
352 String interface_name;
353
354 public ClasspathResourceURIResolver(String interfaceName) {
355 interface_name = interfaceName;
356 }
357
358 /**
359 * Override the URIResolver.resolve() method to turn relative paths of imported xslt files into
360 * absolute paths and set these absolute paths as their SystemIDs when loading them into memory.
361 * @see http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-files
362 */
363
364 @Override
365 public Source resolve(String href, String base) throws TransformerException {
366
367 //System.err.println("href: " + href); // e.g. href: layouts/main.xsl
368 //System.err.println("base: " + base); // e.g for *toplevel* base: file:/path/to/gs3-svn/CL1.xslt
369
370
371 // 1. for any xsl imported by the original stylesheet, try to work out its absolute path location
372 // by concatenating the parent folder of the base stylesheet file with the href of the file it
373 // imports (which is a path relative to the base file). This need not hold true for the very 1st
374 // base stylesheet: it may be located somewhere entirely different from the greenstone XSLT
375 // files it refers to. This is the case when running transforms on the commandline for testing
376
377 File importXSLfile = new File(href); // assume the xsl imported has an absolute path
378
379 if(!importXSLfile.isAbsolute()) { // imported xsl specifies a path relative to parent of base
380 try {
381 URI baseURI = new URI(base);
382 importXSLfile = new File(new File(baseURI).getParent(), href);
383
384 if(!importXSLfile.exists()) { // if the imported file does not exist, it's not
385 // relative to the base (the stylesheet file importing it). So look in the
386 // interface's transform subfolder for the xsl file to be imported
387 importXSLfile = new File(GSFile.interfaceStylesheetFile(GlobalProperties.getGSDL3Home(), interface_name, href));
388 }
389 } catch(URISyntaxException use) {
390 importXSLfile = new File(href); // try
391 }
392 }
393
394 String importXSLfilepath = ""; // for printing the expected file path on error
395 try {
396 // path of import XSL file after resolving any .. and . dir paths
397 importXSLfilepath = importXSLfile.getCanonicalPath();
398 } catch(IOException ioe) { // resort to using the absolute path
399 importXSLfilepath = importXSLfile.getAbsolutePath();
400 }
401
402 // 2. now we know where the XSL file being imported lives, so set the systemID to its
403 // absolute path when loading it into a Source object
404 URI systemID = importXSLfile.toURI();
405 Source importXSLsrc = null;
406 try {
407 importXSLsrc = new StreamSource(new InputStreamReader(new FileInputStream(importXSLfile), "UTF-8"));
408 URL url = systemID.toURL();
409 importXSLsrc.setSystemId(url.toExternalForm());
410 } catch (MalformedURLException mue) {
411 logger.error("Warning: Unable to set systemID for imported stylesheet to " + systemID);
412 logger.error("Got exception: " + mue.getMessage(), mue);
413 } catch(FileNotFoundException fne) {
414 logger.error("ERROR: importXSLsrc file does not exist " + importXSLfilepath);
415 logger.error("\tError msg: " + fne.getMessage(), fne);
416 } catch(UnsupportedEncodingException uee) {
417 logger.error("ERROR: could not resolve relative path of import XSL file " + importXSLfilepath
418 + " because of encoding issues: " + uee.getMessage(), uee);
419 }
420 return importXSLsrc;
421 //return new StreamSource(this.getClass().getClassLoader().getResourceAsStream(href)); // won't work
422 }
423 }
424
425 public Node transform(File stylesheet, File source)
426 {
427 return transform(stylesheet, source, null);
428 }
429
430 // debugAsFile is only to be set to true when either the stylesheet or source parameters
431 // are not objects of type File. The debugAsFile variable is passed into the
432 // TransformErrorListener. When set to true, the TransformErrorListener will itself create
433 // two files containing the stylesheet and source XML, and try to transform the new source
434 // file with the stylesheet file for debugging purposes.
435 protected Node transform(File stylesheet, File source, Document docDocType)
436 {
437 try
438 {
439 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
440 transformerErrorListener.setStylesheet(stylesheet);
441 Transformer transformer = this.t_factory.newTransformer(new StreamSource(new InputStreamReader(new FileInputStream(stylesheet), "UTF-8")));
442 DOMResult result = (docDocType == null) ? new DOMResult() : new DOMResult(docDocType);
443 StreamSource streamSource = new StreamSource(new InputStreamReader(new FileInputStream(source), "UTF-8"));
444
445 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
446
447 transformer.transform(streamSource, result);
448 return result.getNode().getFirstChild();
449 }
450 catch (TransformerConfigurationException e)
451 {
452 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't create transformer object for files\n" + stylesheet + "\n" + source, e);
453 }
454 catch (TransformerException e)
455 {
456 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't transform the source for files\n" + stylesheet + "\n" + source, e);
457 }
458 catch (UnsupportedEncodingException e)
459 {
460 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't read file due to an unsupported encoding\n" + stylesheet + "\n" + source, e);
461 }
462 catch (FileNotFoundException e)
463 {
464 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't find the file specified\n" + stylesheet + "\n" + source, e);
465 }
466 }
467
468 // Given a heading string on the sort of transformation error that occurred and the exception object itself,
469 // this method prints the exception to the tomcat window (system.err) and the greenstone log and then returns
470 // an xhtml error page that is constructed from it.
471 protected Node transformError(String heading, Exception e)
472 {
473 String message = heading + "\n" + e.getMessage();
474 logger.error(heading + ": " + e.getMessage());
475
476 if (e instanceof TransformerException)
477 {
478 String location = ((TransformerException) e).getLocationAsString();
479 if (location != null)
480 {
481 logger.error(location);
482 message = message + "\n" + location;
483 }
484 }
485 System.err.println("****\n" + message + "\n****");
486 return constructErrorXHTMLPage(message);
487 }
488
489 // Given an error message, splits it into separate lines based on any newlines present and generates an xhtml page
490 // (xml Element) with paragraphs for each line. This is then returned so that it can be displayed in the browser.
491 public static Element constructErrorXHTMLPage(String message)
492 {
493 try
494 {
495 String[] lines = message.split("\n");
496
497 Document xhtmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
498 // <html></html>
499 Node htmlNode = xhtmlDoc.createElement("html");
500 xhtmlDoc.appendChild(htmlNode);
501 // <head></head>
502 Node headNode = xhtmlDoc.createElement("head");
503 htmlNode.appendChild(headNode);
504 // <title></title>
505 Node titleNode = xhtmlDoc.createElement("title");
506 headNode.appendChild(titleNode);
507 Node titleString = xhtmlDoc.createTextNode("Error occurred");
508 titleNode.appendChild(titleString);
509
510 // <body></body>
511 Node bodyNode = xhtmlDoc.createElement("body");
512 htmlNode.appendChild(bodyNode);
513
514 // finally put the message in the body
515 Node h1Node = xhtmlDoc.createElement("h1");
516 bodyNode.appendChild(h1Node);
517 Node headingString = xhtmlDoc.createTextNode("The following error occurred:");
518 h1Node.appendChild(headingString);
519
520 //Node textNode = xhtmlDoc.createTextNode(message);
521 //bodyNode.appendChild(textNode);
522
523 for (int i = 0; i < lines.length; i++)
524 {
525 Node pNode = xhtmlDoc.createElement("p");
526 Node textNode = xhtmlDoc.createTextNode(lines[i]);
527 pNode.appendChild(textNode);
528 bodyNode.appendChild(pNode);
529 }
530
531 return xhtmlDoc.getDocumentElement();
532
533 }
534 catch (Exception e)
535 {
536 String errmsg = "Exception trying to construct error xhtml page from message: " + message + "\n" + e.getMessage();
537 System.err.println(errmsg);
538 logger.error(errmsg);
539 return null;
540 }
541 }
542
543 // ErrorListener class for both Transformer objects and TransformerFactory objects.
544 // This class can be used to register a handler for any fatal errors, errors and warnings that
545 // may occur when either transforming an xml file with an xslt stylesheet using the XMLTransformer,
546 // or when instantiating a Transformer object using the XMLTransformer's TransformerFactory member var.
547 // The latter case occurs when the xml Source used to instantiate a Transformer from a TransformerFactory
548 // is invalid in some manner, which results in a null Transformer object. However, as no
549 // TransformerConfigurationException or TransformerException are thrown in this case, the errors
550 // would have not been noticed until things go wrong later when trying to use the (null) Transformer.
551 //
552 // The errors caught by this ErrorListener class are printed both to the greenstone.log and to the
553 // tomcat console (System.err), and the error message is stored in the errorMessage variable so that
554 // it can be retrieved and be used to generate an xhtml error page.
555 public class TransformErrorListener implements ErrorListener
556 {
557 protected String errorMessage = null;
558 protected String stylesheet = null;
559 protected Source source = null; // can be DOMSource or StreamSource
560 protected boolean debugAsFile = true; // true if xslt or source are not real physical files
561
562 // *********** METHODS TO BE CALLED WHEN SETTING AN ERROR LISTENER ON TRANSFORMERFACTORY OBJECTS
563 // The default constructor is only for when setting an ErrorListener on TransformerFactory objects
564 public TransformErrorListener()
565 {
566 this.stylesheet = null;
567 this.source = null;
568 XMLTransformer.debugFileCount++;
569 }
570
571 public void setStylesheet(Document xslt)
572 {
573 this.debugAsFile = true;
574 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
575 this.source = null;
576 }
577
578 public void setStylesheet(String xslt)
579 {
580 this.debugAsFile = true;
581 this.stylesheet = xslt;
582 this.source = null;
583 }
584
585 public void setStylesheet(File xslt)
586 {
587 this.debugAsFile = false; // if this constructor is called, we're dealing with physical files for both xslt and source
588 this.stylesheet = xslt.getAbsolutePath();
589 this.source = null;
590 }
591
592 // *********** METHODS TO BE CALLED WHEN SETTING AN ERROR LISTENER ON TRANSFORMERFACTORY OBJECTS
593 // When setting an ErrorListener on Transformer object, the ErrorListener takes a Stylesheet xslt and a Source
594 public TransformErrorListener(String xslt, Source source)
595 {
596 this.stylesheet = xslt;
597 this.source = source;
598 XMLTransformer.debugFileCount++;
599 }
600
601 public TransformErrorListener(Document xslt, Source source)
602 {
603 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
604 this.source = source;
605 XMLTransformer.debugFileCount++;
606 }
607
608 public TransformErrorListener(File xslt, Source source)
609 {
610 this.debugAsFile = false; // if this constructor is called, we're dealing with physical files for both xslt and source
611 this.source = source;
612 this.stylesheet = xslt.getAbsolutePath(); // not necessary to get the string from the file
613 // all we were going to do with it *on error* was write it out to a file anyway
614 }
615
616 // *********** METHODS CALLED AUTOMATICALLY ON ERROR
617
618 // Receive notification of a recoverable error.
619 public void error(TransformerException exception)
620 {
621 handleError("Error:\n", exception);
622 }
623
624 // Receive notification of a non-recoverable error.
625 public void fatalError(TransformerException exception)
626 {
627 handleError("Fatal Error:\n", exception);
628 }
629
630 // Receive notification of a warning.
631 public void warning(TransformerException exception)
632 {
633 handleError("Warning:\n", exception);
634 }
635
636 public String toString(TransformerException e)
637 {
638 String msg = "Exception encountered was:\n\t";
639 String location = e.getLocationAsString();
640 if (location != null)
641 {
642 msg = msg + "Location: " + location + "\n\t";
643 }
644
645 return msg + "Message: " + e.getMessage();
646 }
647
648 // clears the errorPage variable after the first call to this method
649 public String getErrorMessage()
650 {
651 String errMsg = this.errorMessage;
652 if (this.errorMessage != null)
653 {
654 this.errorMessage = null;
655 }
656 return errMsg;
657 }
658
659 // sets the errorMessage member variable to the data stored in the exception
660 // and writes the errorMessage to the logger and tomcat's System.err
661 protected void handleError(String errorType, TransformerException exception)
662 {
663
664 this.errorMessage = errorType + toString(exception);
665
666 // If either the stylesheet or the source to be transformed with it were not files,
667 // so that the transformation was performed in-memory, then the "location" information
668 // during the error handling (if any) wouldn't have been helpful.
669 // To allow proper debugging, we write both stylesheet and source out as physical files
670 // and perform the same transformation again, so that when a transformation error does
671 // occur, the files are not in-memory but can be viewed, and any location information
672 // for the error given by the ErrorListener will be sensible (instead of the unhelpful
673 // "line#0 column#0 in file://somewhere/dummy.xsl").
674 // Note that if the stylesheet and the source it is to transform were both physical
675 // files to start off with, we will not need to perform the same transformation again
676 // since the error reporting would have provided accurate locations for those.
677 if (debugAsFile)
678 {
679 System.err.println("\n****Original error transforming xml:\n" + this.errorMessage + "\n****\n");
680 System.err.println("***** About to perform the transform again with actual files\n******\n");
681 logger.error("Original transformation error: " + this.errorMessage);
682 logger.error("**** About to perform transform again with actual files");
683
684
685 performTransformWithPhysicalFiles(); // will give accurate line numbers
686
687 // No need to print out the current error message (seen in the Else statement below),
688 // as the recursive call to XMLTransformer.transform(File, File, false) in method
689 // performTransformWithPhysicalFiles() will do this for us.
690 }
691 else
692 {
693 // printing out the error message
694 // since !debugAsFile, we are dealing with physical files,
695 // variable stylesheet would have stored the filename instead of contents
696 this.errorMessage = this.errorMessage + "\nstylesheet filename: " + stylesheet;
697
698 this.errorMessage += "\nException CAUSE:\n" + exception.getCause();
699 System.err.println("\n****Error transforming xml:\n" + this.errorMessage + "\n****\n");
700 //System.err.println("Stylesheet was:\n + this.stylesheet + "************END STYLESHEET***********\n\n");
701
702 logger.error(this.errorMessage);
703
704 // now print out the source to a file, and run the stylesheet on it using a transform()
705 // then any error will be referring to one of these two input files.
706 }
707 }
708
709 // This method will redo the transformation that went wrong with *real* files:
710 // it writes out the stylesheet and source XML to files first, then performs the transformation
711 // to get the actual line location of where things went wrong (instead of "line#0 column#0 in dummy.xsl")
712 protected void performTransformWithPhysicalFiles()
713 {
714 File webLogsTmpFolder = new File(GlobalProperties.getGSDL3Home() + File.separator + "logs" + File.separator + "tmp");
715 if (!webLogsTmpFolder.exists())
716 {
717 webLogsTmpFolder.mkdirs(); // create any necessary folders
718 }
719 File styleFile = new File(webLogsTmpFolder + File.separator + "stylesheet" + XMLTransformer.debugFileCount + ".xml");
720 File sourceFile = new File(webLogsTmpFolder + File.separator + "source" + XMLTransformer.debugFileCount + ".xml");
721 try
722 {
723 // write stylesheet to a file called stylesheet_systemID in tmp
724 FileWriter styleSheetWriter = new FileWriter(styleFile);
725 styleSheetWriter.write(stylesheet, 0, stylesheet.length());
726 styleSheetWriter.flush();
727 styleSheetWriter.close();
728 }
729 catch (Exception e)
730 {
731 System.err.println("*** Exception when trying to write out stylesheet to " + styleFile.getAbsolutePath());
732 }
733
734 if (this.source != null)
735 { // ErrorListener was set on a Transformer object
736 try
737 {
738 FileWriter srcWriter = new FileWriter(sourceFile);
739 String contents = "";
740 if (source instanceof DOMSource)
741 {
742 DOMSource domSource = (DOMSource) source;
743 Document doc = (Document) domSource.getNode();
744 contents = GSXML.elementToString(doc.getDocumentElement(), true);
745 //contents = GSXML.xmlNodeToXMLString(domSource.getNode());
746 }
747 else if (source instanceof StreamSource)
748 {
749 StreamSource streamSource = (StreamSource) source;
750 BufferedReader reader = new BufferedReader(streamSource.getReader());
751 String line = "";
752 while ((line = reader.readLine()) != null)
753 {
754 contents = contents + line + "\n";
755 }
756 }
757 srcWriter.write(contents, 0, contents.length());
758 srcWriter.flush();
759 srcWriter.close();
760 }
761 catch (Exception e)
762 {
763 System.err.println("*** Exception when trying to write out stylesheet to " + sourceFile.getAbsolutePath());
764 }
765 }
766
767 System.err.println("*****************************************");
768 System.err.println("Look for stylesheet in: " + styleFile.getAbsolutePath());
769 if (this.source != null)
770 { // ErrorListener was set on a Transformer object
771 System.err.println("Look for source XML in: " + sourceFile.getAbsolutePath());
772 }
773
774 // now perform the transform again, which will assign another TransformErrorListener
775 // but since debuggingAsFile is turned off, we won't recurse into this section of
776 // handling the error again
777 if (this.source != null)
778 { // ErrorListener was set on a Transformer object
779 XMLTransformer.this.transform(styleFile, sourceFile); // calls the File, File version, so debugAsFile will be false
780 }
781 else
782 { // ErrorListener was set on a TransformerFactory object
783
784 // The recursive step in this case is to perform the instantiation
785 // of the Transformer object again.
786 // Only one TransformerFactory object per XMLTransformer,
787 // and only one TransformerHandler object set on any TransformerFactory
788 // But the stylesheet used to create a Transformer from that TransformerFactory
789 // object changes each time, by calls to setStylesheet(),
790 // Therefore, the debugAsFile state for the single TransformerFactory's
791 // TransformerHandler changes each time also.
792
793 try
794 {
795 debugAsFile = false;
796 this.stylesheet = styleFile.getAbsolutePath();
797 //TransformErrorListener transformerErrorListener = (TransformErrorListener)XMLTransformer.this.t_factory.getErrorListener();
798 //transformerErrorListener.setStylesheet(styleFile);
799 Transformer transformer = XMLTransformer.this.t_factory.newTransformer(new StreamSource(styleFile));
800 if (transformer == null)
801 {
802 String msg = "XMLTransformer transformer is " + transformer;
803 logger.info(msg);
804 System.out.println(msg + "\n****\n");
805 }
806 }
807 catch (TransformerConfigurationException e)
808 {
809 String message = "Couldn't create transformer object: " + e.getMessageAndLocation();
810 logger.error(message);
811 logger.error(e.getLocationAsString());
812 System.out.println(message);
813 }
814 }
815 }
816 }
817}
Note: See TracBrowser for help on using the repository browser.