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

Last change on this file since 32491 was 32491, checked in by kjdon, 6 years ago

added a new transform method for 2 files where you can pass in the entityresolver. this will help the transform to locate dtds etc. eg for gberg coll, the code is looking in tomcat/bin folder for gutbook.dtd. don't know why this is, or why it has changed from before. Anyway, we need to use XMLReader and give it the entityresolver to locate the dtd (from the resources folder in the collection)

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