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

Last change on this file since 25602 was 25602, checked in by sjm84, 12 years ago

Cosmetic changes: Eclipse autoformats spacing and tabs.

  • Property svn:keywords set to Author Date Id Revision
File size: 21.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 org.greenstone.util.GlobalProperties;
22
23// XML classes
24import javax.xml.transform.Transformer;
25import javax.xml.transform.TransformerFactory;
26import javax.xml.transform.TransformerConfigurationException;
27import javax.xml.transform.TransformerException;
28import javax.xml.transform.ErrorListener;
29
30import javax.xml.transform.stream.StreamSource;
31import javax.xml.transform.dom.DOMSource;
32import javax.xml.transform.Source;
33import javax.xml.transform.stream.StreamResult;
34import javax.xml.transform.dom.DOMResult;
35
36import javax.xml.parsers.DocumentBuilderFactory;
37import javax.xml.parsers.DocumentBuilder;
38import org.w3c.dom.Element;
39import org.w3c.dom.Document;
40
41import org.w3c.dom.Node;
42import org.w3c.dom.NodeList;
43
44// other java classes
45import java.io.StringReader;
46import java.io.StringWriter;
47import java.io.BufferedReader;
48import java.io.FileReader;
49import java.io.FileWriter;
50import java.io.File;
51import java.util.HashMap;
52import java.util.Set;
53import java.util.Map;
54import java.util.Iterator;
55
56import org.apache.xml.utils.DefaultErrorHandler;
57
58import org.apache.log4j.*;
59
60/** XMLTransformer - utility class for greenstone
61 *
62 * transforms xml using xslt
63 *
64 * @author <a href="mailto:[email protected]">Katherine Don</a>
65 * @version $Revision: 25602 $
66 */
67public class XMLTransformer {
68 private static int debugFileCount = 0; // for unique filenames when debugging XML transformations with physical files
69
70 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.XMLTransformer.class.getName());
71
72 /** The transformer factory we're using */
73 TransformerFactory t_factory=null;
74
75 /**
76 * The no-arguments constructor.
77 *
78 * Any exceptions thrown are caught internally
79 *
80 * @see javax.xml.transform.TransformerFactory
81 */
82 public XMLTransformer() {
83 // http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/index.html?javax/xml/transform/TransformerFactory.html states that
84 // TransformerFactory.newInstance() looks in jar files for a Factory specified in META-INF/services/javax.xml.transform.TransformerFactory,
85 // else it will use the "platform default"
86 // In this case: xalan.jar's META-INF/services/javax.xml.transform.TransformerFactory contains org.apache.xalan.processor.TransformerFactoryImpl
87 // as required.
88
89 // This means we no longer have to do a System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
90 // followed by a this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
91 // The System.setProperty step to force the TransformerFactory implementation that gets used, conflicts with
92 // Fedora (visiting the Greenstone server pages breaks the Greenstone-tomcat hosted Fedora pages) as Fedora
93 // does not include the xalan.jar and therefore can't then find the xalan TransformerFactory explicitly set.
94
95 // Gone back to forcing use of xalan transformer, since other jars like crimson.jar, which may be on some
96 // classpaths, could be be chosen as the TransformerFactory implementation over xalan. This is what used to
97 // give problems before. Instead, have placed copies of the jars that Fedora needs (xalan.jar and serializer.jar
98 // and the related xsltc.jar which it may need) into packages/tomcat/lib so that it's on the server's classpath
99 // and will be found by Fedora.
100
101 // make sure we are using the xalan transformer
102 System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
103 try {
104 this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
105 //this.t_factory = TransformerFactory.newInstance();
106 } catch (Exception e) {
107 logger.error("exception creating t_factory "+e.getMessage());
108 }
109 }
110
111
112
113 /**
114 * Transform an XML document using a XSLT stylesheet
115 *
116 * @param stylesheet a filename for an XSLT stylesheet
117 * @param xml_in the XML to be transformed
118 * @return the transformed XML
119 */
120 public String transform(String stylesheet, String xml_in) {
121
122 try {
123 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
124 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
125
126 // Use the Transformer to transform an XML Source and send the output to a Result object.
127 StringWriter output = new StringWriter();
128 StreamSource streamSource = new StreamSource(new StringReader(xml_in));
129 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
130 transformer.transform(streamSource, new StreamResult(output));
131 return output.toString();
132 } catch (TransformerConfigurationException e) {
133 logger.error("couldn't create transformer object: "+e.getMessageAndLocation());
134 logger.error(e.getLocationAsString());
135 return "";
136 } catch (TransformerException e) {
137 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
138 return "";
139 }
140 }
141
142 public String transformToString(Document stylesheet, Document source) {
143 return transformToString(stylesheet, source, null);
144 }
145
146
147 public String transformToString(Document stylesheet, Document source, HashMap parameters) {
148
149 try {
150 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
151 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
152 if (parameters != null) {
153 Set params = parameters.entrySet();
154 Iterator i = params.iterator();
155 while (i.hasNext()) {
156 Map.Entry m = (Map.Entry)i.next();
157 transformer.setParameter((String)m.getKey(), m.getValue());
158 }
159 }
160 //transformer.setParameter("page_lang", source.getDocumentElement().getAttribute(GSXML.LANG_ATT));
161
162
163 // Use the Transformer to transform an XML Source and send the output to a Result object.
164 StringWriter output = new StringWriter();
165 DOMSource domSource = new DOMSource(source);
166
167 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
168 transformer.transform(domSource, new StreamResult(output));
169 return output.toString();
170 } catch (TransformerConfigurationException e) {
171 logger.error("couldn't create transformer object: "+e.getMessageAndLocation());
172 logger.error(e.getLocationAsString());
173 return "";
174 } catch (TransformerException e) {
175 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
176 return "";
177 }
178 }
179
180
181
182 /**
183 * Transform an XML document using a XSLT stylesheet,
184 * but using a DOMResult whose node should be set to the Document donated by resultNode
185 */
186 public Node transform_withResultNode(Document stylesheet, Document source, Document resultNode) {
187 return transform(stylesheet, source, null, null, resultNode);
188 }
189
190 public Node transform(Document stylesheet, Document source) {
191 return transform(stylesheet, source, null, null, null);
192 }
193
194 public Node transform(Document stylesheet, Document source, HashMap parameters) {
195 return transform(stylesheet, source, parameters, null, null);
196 }
197
198 public Node transform(Document stylesheet, Document source, HashMap parameters, Document docDocType) {
199 return transform(stylesheet, source, parameters, docDocType, null);
200 }
201
202 protected Node transform(Document stylesheet, Document source, HashMap parameters, Document docDocType, Document resultNode) {
203 try {
204 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
205 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
206 logger.info("XMLTransformer transformer is " + transformer);
207 if (parameters != null) {
208 Set params = parameters.entrySet();
209 Iterator i = params.iterator();
210 while (i.hasNext()) {
211 Map.Entry m = (Map.Entry)i.next();
212 transformer.setParameter((String)m.getKey(), m.getValue());
213 }
214 }
215
216 // When we transform the DOMResult, we need to make sure the result of
217 // the transformation has a DocType. For that to happen, we need to create
218 // the DOMResult using a Document with a predefined docType.
219 // If we don't have a DocType then do the transformation with a DOMResult
220 // that does not contain any doctype (like we use to do before).
221 DOMResult result = docDocType == null ? new DOMResult() : new DOMResult(docDocType);
222 if(resultNode != null) {
223 result.setNode(resultNode);
224 }
225 DOMSource domSource = new DOMSource(source);
226 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
227 transformer.transform(domSource, result);
228 return result.getNode(); // pass the entire document
229 }
230 catch (TransformerConfigurationException e) {
231 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)"
232 + "\ncouldn't create transformer object", e);
233 }
234 catch (TransformerException e) {
235 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)"
236 + "\ncouldn't transform the source", e);
237 }
238 }
239
240 public Node transform(File stylesheet, File source) {
241 return transform(stylesheet, source, null);
242 }
243
244 // debugAsFile is only to be set to true when either the stylesheet or source parameters
245 // are not objects of type File. The debugAsFile variable is passed into the
246 // TransformErrorListener. When set to true, the TransformErrorListener will itself create
247 // two files containing the stylesheet and source XML, and try to transform the new source
248 // file with the stylesheet file for debugging purposes.
249 protected Node transform(File stylesheet, File source, Document docDocType) {
250 try {
251 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
252 DOMResult result = (docDocType == null) ? new DOMResult() : new DOMResult(docDocType);
253 StreamSource streamSource = new StreamSource(source);
254
255 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
256
257 transformer.transform(streamSource, result);
258 return result.getNode().getFirstChild();
259 } catch (TransformerConfigurationException e) {
260 return transformError("XMLTransformer.transform(File, File)"
261 + "\ncouldn't create transformer object for files\n"
262 + stylesheet + "\n" + source, e);
263 }
264 catch (TransformerException e) {
265 return transformError("XMLTransformer.transform(File, File)"
266 + "\ncouldn't transform the source for files\n"
267 + stylesheet + "\n" + source, e);
268 }
269 }
270
271 // Given a heading string on the sort of transformation error that occurred and the exception object itself,
272 // this method prints the exception to the tomcat window (system.err) and the greenstone log and then returns
273 // an xhtml error page that is constructed from it.
274 protected Node transformError(String heading, TransformerException e) {
275 String message = heading + "\n" + e.getMessage();
276 logger.error(heading + ": " + e.getMessage());
277
278 String location = e.getLocationAsString();
279 if(location != null) {
280 logger.error(location);
281 message = message + "\n" + location;
282 }
283 System.err.println("****\n" + message + "\n****");
284 return constructErrorXHTMLPage(message);
285 }
286
287 // Given an error message, splits it into separate lines based on any newlines present and generates an xhtml page
288 // (xml Element) with paragraphs for each line. This is then returned so that it can be displayed in the browser.
289 public static Element constructErrorXHTMLPage(String message) {
290 try{
291 String[] lines = message.split("\n");
292
293 Document xhtmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
294 // <html></html>
295 Node htmlNode = xhtmlDoc.createElement("html");
296 xhtmlDoc.appendChild(htmlNode);
297 // <head></head>
298 Node headNode = xhtmlDoc.createElement("head");
299 htmlNode.appendChild(headNode);
300 // <title></title>
301 Node titleNode = xhtmlDoc.createElement("title");
302 headNode.appendChild(titleNode);
303 Node titleString = xhtmlDoc.createTextNode("Error occurred");
304 titleNode.appendChild(titleString);
305
306 // <body></body>
307 Node bodyNode = xhtmlDoc.createElement("body");
308 htmlNode.appendChild(bodyNode);
309
310 // finally put the message in the body
311 Node h1Node = xhtmlDoc.createElement("h1");
312 bodyNode.appendChild(h1Node);
313 Node headingString = xhtmlDoc.createTextNode("The following error occurred:");
314 h1Node.appendChild(headingString);
315
316 //Node textNode = xhtmlDoc.createTextNode(message);
317 //bodyNode.appendChild(textNode);
318
319 for (int i = 0; i < lines.length; i++) {
320 Node pNode = xhtmlDoc.createElement("p");
321 Node textNode = xhtmlDoc.createTextNode(lines[i]);
322 pNode.appendChild(textNode);
323 bodyNode.appendChild(pNode);
324 }
325
326 return xhtmlDoc.getDocumentElement();
327
328 }catch(Exception e) {
329 String errmsg = "Exception trying to construct error xhtml page from message: " + message
330 + "\n" + e.getMessage();
331 System.err.println(errmsg);
332 logger.error(errmsg);
333 return null;
334 }
335 }
336
337 // ErrorListener class that can be used to register a handler for any fatal errors, errors and warnings
338 // that may occur when transforming an xml file with an xslt stylesheet using the XMLTransformer.
339 // The errors are printed both to the greenstone.log and to the tomcat console (System.err), and the
340 // error message is stored in the errorMessage variable so that it can be retrieved and be used to
341 // generate an xhtml error page.
342 public class TransformErrorListener implements ErrorListener {
343 protected String errorMessage = null;
344 protected String stylesheet = null;
345 protected Source source = null; // can be DOMSource or StreamSource
346 protected boolean debugAsFile = true; // true if xslt or source are not real physical files
347
348 public TransformErrorListener(String xslt, Source source) {
349 this.stylesheet = xslt;
350 this.source = source;
351 XMLTransformer.debugFileCount++;
352 }
353
354 public TransformErrorListener(Document xslt, Source source) {
355 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
356 this.source = source;
357 XMLTransformer.debugFileCount++;
358 }
359
360 public TransformErrorListener(File xslt, Source source) {
361 this.debugAsFile = false; // if this constructor is called, we're dealing with physical files for both xslt and source
362 this.source = source;
363 this.stylesheet = xslt.getAbsolutePath(); // not necessary to get the string from the file
364 // all we were going to do with it *on error* was write it out to a file anyway
365 }
366
367 // Receive notification of a recoverable error.
368 public void error(TransformerException exception) {
369 handleError("Error:\n", exception);
370 }
371 // Receive notification of a non-recoverable error.
372 public void fatalError(TransformerException exception) {
373 handleError("Fatal Error:\n", exception);
374 }
375 // Receive notification of a warning.
376 public void warning(TransformerException exception) {
377 handleError("Warning:\n", exception);
378 }
379
380 public String toString(TransformerException e) {
381 String msg = "Exception encountered was:\n\t";
382 String location = e.getLocationAsString();
383 if(location != null) {
384 msg = msg + "Location: " + location + "\n\t";
385 }
386
387 return msg + "Message: " + e.getMessage();
388 }
389
390 // clears the errorPage variable after the first call to this method
391 public String getErrorMessage() {
392 String errMsg = this.errorMessage;
393 if(this.errorMessage != null) {
394 this.errorMessage = null;
395 }
396 return errMsg;
397 }
398
399 // sets the errorMessage member variable to the data stored in the exception
400 // and writes the errorMessage to the logger and tomcat's System.err
401 protected void handleError(String errorType, TransformerException exception) {
402
403 this.errorMessage = errorType + toString(exception);
404
405 // If either the stylesheet or the source to be transformed with it were not files,
406 // so that the transformation was performed in-memory, then the "location" information
407 // during the error handling (if any) wouldn't have been helpful.
408 // To allow proper debugging, we write both stylesheet and source out as physical files
409 // and perform the same transformation again, so that when a transformation error does
410 // occur, the files are not in-memory but can be viewed, and any location information
411 // for the error given by the ErrorListener will be sensible (instead of the unhelpful
412 // "line#0 column#0 in file://somewhere/dummy.xsl").
413 // Note that if the stylesheet and the source it is to transform were both physical
414 // files to start off with, we will not need to perform the same transformation again
415 // since the error reporting would have provided accurate locations for those.
416 if(debugAsFile) {
417
418 performTransformWithPhysicalFiles(); // will give accurate line numbers
419
420 // No need to print out the current error message (seen in the Else statement below),
421 // as the recursive call to XMLTransformer.transform(File, File, false) in method
422 // performTransformWithPhysicalFiles() will do this for us.
423 }
424 else {
425 // printing out the error message
426 // since !debugAsFile, we are dealing with physical files,
427 // variable stylesheet would have stored the filename instead of contents
428 this.errorMessage = this.errorMessage + "\nstylesheet filename: " + stylesheet;
429
430 this.errorMessage += "\nException CAUSE:\n" + exception.getCause();
431 System.err.println("\n****Error transforming xml:\n" + this.errorMessage + "\n****\n");
432 //System.err.println("Stylesheet was:\n + this.stylesheet + "************END STYLESHEET***********\n\n");
433
434 logger.error(this.errorMessage);
435
436 // now print out the source to a file, and run the stylesheet on it using a transform()
437 // then any error will be referring to one of these two input files.
438 }
439 }
440
441 // This method will redo the transformation that went wrong with *real* files:
442 // it writes out the stylesheet and source XML to files first, then performs the transformation
443 // to get the actual line location of where things went wrong (instead of "line#0 column#0 in dummy.xsl")
444 protected void performTransformWithPhysicalFiles() {
445 File webLogsTmpFolder = new File(GlobalProperties.getGSDL3Home() + File.separator + "logs" + File.separator + "tmp");
446 if(!webLogsTmpFolder.exists()) {
447 webLogsTmpFolder.mkdirs(); // create any necessary folders
448 }
449 File styleFile = new File(webLogsTmpFolder + File.separator + "stylesheet" + XMLTransformer.debugFileCount + ".xml");
450 File sourceFile = new File(webLogsTmpFolder + File.separator + "source" + XMLTransformer.debugFileCount + ".xml");
451
452 try {
453 // write stylesheet to a file called stylesheet_systemID in tmp
454 FileWriter styleSheetWriter = new FileWriter(styleFile);
455 styleSheetWriter.write(stylesheet, 0, stylesheet.length());
456 styleSheetWriter.flush();
457 styleSheetWriter.close();
458 } catch(Exception e) {
459 System.err.println("*** Exception when trying to write out stylesheet to " + styleFile.getAbsolutePath());
460 }
461
462 try {
463 FileWriter srcWriter = new FileWriter(sourceFile);
464 String contents = "";
465 if(source instanceof DOMSource) {
466 DOMSource domSource = (DOMSource)source;
467 Document doc = (Document)domSource.getNode();
468 contents = GSXML.elementToString(doc.getDocumentElement(), true);
469 //contents = GSXML.xmlNodeToXMLString(domSource.getNode());
470 } else if (source instanceof StreamSource) {
471 StreamSource streamSource = (StreamSource)source;
472 BufferedReader reader = new BufferedReader(streamSource.getReader());
473 String line = "";
474 while((line = reader.readLine()) != null) {
475 contents = contents + line + "\n";
476 }
477 }
478 srcWriter.write(contents, 0, contents.length());
479 srcWriter.flush();
480 srcWriter.close();
481 } catch(Exception e) {
482 System.err.println("*** Exception when trying to write out stylesheet to " + sourceFile.getAbsolutePath());
483 }
484
485 System.err.println("*****************************************");
486 System.err.println("Look for stylesheet in: " + styleFile.getAbsolutePath());
487 System.err.println("Look for source XML in: " + sourceFile.getAbsolutePath());
488
489 // now perform the transform again, which will assign another TransformErrorListener
490 // but since debuggingAsFile is turned off, we won't recurse into this section of
491 // handling the error again
492 XMLTransformer.this.transform(styleFile, sourceFile); // calls the File, File version, so debugAsFile will be false
493
494 }
495 }
496}
Note: See TracBrowser for help on using the repository browser.