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

Last change on this file was 35364, checked in by kjdon, 3 years ago

XMLREaderFactory is deprecated. Use SAXParserFactory instead. Hope I've done this right...

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