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

Last change on this file since 28965 was 27617, checked in by sjm84, 11 years ago

Various improvements and fixes mostly to do with adding depositor functionality

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