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

Last change on this file since 25423 was 25423, checked in by ak19, 12 years ago

Better error reporting during transformations. Extended the TransformErrorListener to store the XSLT upon construction and print it out (besides the exception message) upon exception.

  • Property svn:keywords set to Author Date Id Revision
File size: 16.1 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// XML classes
22import javax.xml.transform.Transformer;
23import javax.xml.transform.TransformerFactory;
24import javax.xml.transform.TransformerConfigurationException;
25import javax.xml.transform.TransformerException;
26import javax.xml.transform.ErrorListener;
27
28import javax.xml.transform.stream.StreamSource;
29import javax.xml.transform.dom.DOMSource;
30import javax.xml.transform.stream.StreamResult;
31import javax.xml.transform.dom.DOMResult;
32
33import javax.xml.parsers.DocumentBuilderFactory;
34import javax.xml.parsers.DocumentBuilder;
35import org.w3c.dom.Element;
36import org.w3c.dom.Document;
37
38import org.w3c.dom.Node;
39import org.w3c.dom.NodeList;
40
41// other java classes
42import java.io.StringReader;
43import java.io.StringWriter;
44import java.io.BufferedReader;
45import java.io.FileReader;
46import java.io.File;
47import java.util.HashMap;
48import java.util.Set;
49import java.util.Map;
50import java.util.Iterator;
51
52import org.apache.xml.utils.DefaultErrorHandler;
53
54import org.apache.log4j.*;
55
56/** XMLTransformer - utility class for greenstone
57 *
58 * transforms xml using xslt
59 *
60 * @author <a href="mailto:[email protected]">Katherine Don</a>
61 * @version $Revision: 25423 $
62 */
63public class XMLTransformer {
64
65 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.XMLTransformer.class.getName());
66
67 /** The transformer factory we're using */
68 TransformerFactory t_factory=null;
69
70 /**
71 * The no-arguments constructor.
72 *
73 * Any exceptions thrown are caught internally
74 *
75 * @see javax.xml.transform.TransformerFactory
76 */
77 public XMLTransformer() {
78 // http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/index.html?javax/xml/transform/TransformerFactory.html states that
79 // TransformerFactory.newInstance() looks in jar files for a Factory specified in META-INF/services/javax.xml.transform.TransformerFactory,
80 // else it will use the "platform default"
81 // In this case: xalan.jar's META-INF/services/javax.xml.transform.TransformerFactory contains org.apache.xalan.processor.TransformerFactoryImpl
82 // as required.
83
84 // This means we no longer have to do a System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
85 // followed by a this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
86 // The System.setProperty step to force the TransformerFactory implementation that gets used, conflicts with
87 // Fedora (visiting the Greenstone server pages breaks the Greenstone-tomcat hosted Fedora pages) as Fedora
88 // does not include the xalan.jar and therefore can't then find the xalan TransformerFactory explicitly set.
89
90 // Gone back to forcing use of xalan transformer, since other jars like crimson.jar, which may be on some
91 // classpaths, could be be chosen as the TransformerFactory implementation over xalan. This is what used to
92 // give problems before. Instead, have placed copies of the jars that Fedora needs (xalan.jar and serializer.jar
93 // and the related xsltc.jar which it may need) into packages/tomcat/lib so that it's on the server's classpath
94 // and will be found by Fedora.
95
96 // make sure we are using the xalan transformer
97 System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
98 try {
99 this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
100 //this.t_factory = TransformerFactory.newInstance();
101 } catch (Exception e) {
102 logger.error("exception creating t_factory "+e.getMessage());
103 }
104 }
105
106
107
108 /**
109 * Transform an XML document using a XSLT stylesheet
110 *
111 * @param stylesheet a filename for an XSLT stylesheet
112 * @param xml_in the XML to be transformed
113 * @return the transformed XML
114 */
115 public String transform(String stylesheet, String xml_in) {
116
117 try {
118 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
119 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
120 transformer.setErrorListener(new TransformErrorListener(stylesheet));
121
122 // Use the Transformer to transform an XML Source and send the output to a Result object.
123 StringWriter output = new StringWriter();
124
125 transformer.transform(new StreamSource(new StringReader(xml_in)), new StreamResult(output));
126 return output.toString();
127 } catch (TransformerConfigurationException e) {
128 logger.error("couldn't create transformer object: "+e.getMessageAndLocation());
129 logger.error(e.getLocationAsString());
130 return "";
131 } catch (TransformerException e) {
132 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
133 return "";
134 }
135 }
136
137 public String transformToString(Document stylesheet, Document source) {
138 return transformToString(stylesheet, source, null);
139 }
140
141 public String transformToString(Document stylesheet, Document source, HashMap parameters) {
142
143 try {
144 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
145 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
146 transformer.setErrorListener(new TransformErrorListener(stylesheet));
147 if (parameters != null) {
148 Set params = parameters.entrySet();
149 Iterator i = params.iterator();
150 while (i.hasNext()) {
151 Map.Entry m = (Map.Entry)i.next();
152 transformer.setParameter((String)m.getKey(), m.getValue());
153 }
154 }
155 //transformer.setParameter("page_lang", source.getDocumentElement().getAttribute(GSXML.LANG_ATT));
156
157
158 // Use the Transformer to transform an XML Source and send the output to a Result object.
159 StringWriter output = new StringWriter();
160
161 transformer.transform(new DOMSource(source), new StreamResult(output));
162 return output.toString();
163 } catch (TransformerConfigurationException e) {
164 logger.error("couldn't create transformer object: "+e.getMessageAndLocation());
165 logger.error(e.getLocationAsString());
166 return "";
167 } catch (TransformerException e) {
168 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
169 return "";
170 }
171 }
172
173 public Node transform(Document stylesheet, Document source) {
174 return transform(stylesheet, source, null, null);
175 }
176
177 public Node transform(Document stylesheet, Document source, HashMap parameters) {
178 return transform(stylesheet, source, parameters, null);
179 }
180
181 public Node transform(Document stylesheet, Document source, HashMap parameters, Document docDocType) {
182 try {
183 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
184 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
185 logger.info("XMLTransformer transformer is " + transformer);
186 transformer.setErrorListener(new TransformErrorListener(stylesheet));
187 if (parameters != null) {
188 Set params = parameters.entrySet();
189 Iterator i = params.iterator();
190 while (i.hasNext()) {
191 Map.Entry m = (Map.Entry)i.next();
192 transformer.setParameter((String)m.getKey(), m.getValue());
193 }
194 }
195
196 // When we transform the DOMResult, we need to make sure the result of
197 // the transformation has a DocType. For that to happen, we need to create
198 // the DOMResult using a Document with a predefined docType.
199 // If we don't have a DocType then do the transformation with a DOMResult
200 // that does not contain any doctype (like we use to do before).
201 DOMResult result = docDocType == null ? new DOMResult() : new DOMResult(docDocType);
202 transformer.transform(new DOMSource(source), result);
203 return result.getNode(); // pass the entire document
204 }
205 catch (TransformerConfigurationException e) {
206 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)"
207 + "\ncouldn't create transformer object", e);
208 }
209 catch (TransformerException e) {
210 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)"
211 + "\ncouldn't transform the source", e);
212 }
213 }
214
215 public Node transform(File stylesheet, File source) {
216 try {
217 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
218 transformer.setErrorListener(new TransformErrorListener(stylesheet));
219 DOMResult result = new DOMResult();
220 transformer.transform(new StreamSource(source), result);
221 return result.getNode().getFirstChild();
222 } catch (TransformerConfigurationException e) {
223 return transformError("XMLTransformer.transform(File, File)"
224 + "\ncouldn't create transformer object for files\n"
225 + stylesheet + "\n" + source, e);
226 }
227 catch (TransformerException e) {
228 return transformError("XMLTransformer.transform(File, File)"
229 + "\ncouldn't transform the source for files\n"
230 + stylesheet + "\n" + source, e);
231 }
232 }
233
234 public Node transform(File stylesheet, File source, Document docDocType) {
235 try {
236 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
237 transformer.setErrorListener(new TransformErrorListener(stylesheet));
238 DOMResult result = new DOMResult(docDocType);
239 transformer.transform(new StreamSource(source), result);
240 return result.getNode().getFirstChild();
241 } catch (TransformerConfigurationException e) {
242 return transformError("XMLTransformer.transform(File, File, Doc)"
243 + "\ncouldn't create transformer object for files\n"
244 + stylesheet + "\n" + source, e);
245 }
246 catch (TransformerException e) {
247 return transformError("XMLTransformer.transform(File, File, Doc)"
248 + "\ncouldn't transform the source for files\n"
249 + stylesheet + "\n" + source, e);
250 }
251 }
252
253 // Given a heading string on the sort of transformation error that occurred and the exception object itself,
254 // this method prints the exception to the tomcat window (system.err) and the greenstone log and then returns
255 // an xhtml error page that is constructed from it.
256 protected Node transformError(String heading, TransformerException e) {
257 String message = heading + "\n" + e.getMessage();
258 logger.error(heading + ": " + e.getMessage());
259
260 String location = e.getLocationAsString();
261 if(location != null) {
262 logger.error(location);
263 message = message + "\n" + location;
264 }
265 System.err.println("****\n" + message + "\n****");
266 return constructErrorXHTMLPage(message);
267 }
268
269 // Given an error message, splits it into separate lines based on any newlines present and generates an xhtml page
270 // (xml Element) with paragraphs for each line. This is then returned so that it can be displayed in the browser.
271 public static Element constructErrorXHTMLPage(String message) {
272 try{
273 String[] lines = message.split("\n");
274
275 Document xhtmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
276 // <html></html>
277 Node htmlNode = xhtmlDoc.createElement("html");
278 xhtmlDoc.appendChild(htmlNode);
279 // <head></head>
280 Node headNode = xhtmlDoc.createElement("head");
281 htmlNode.appendChild(headNode);
282 // <title></title>
283 Node titleNode = xhtmlDoc.createElement("title");
284 headNode.appendChild(titleNode);
285 Node titleString = xhtmlDoc.createTextNode("Error occurred");
286 titleNode.appendChild(titleString);
287
288 // <body></body>
289 Node bodyNode = xhtmlDoc.createElement("body");
290 htmlNode.appendChild(bodyNode);
291
292 // finally put the message in the body
293 Node h1Node = xhtmlDoc.createElement("h1");
294 bodyNode.appendChild(h1Node);
295 Node headingString = xhtmlDoc.createTextNode("The following error occurred:");
296 h1Node.appendChild(headingString);
297
298 //Node textNode = xhtmlDoc.createTextNode(message);
299 //bodyNode.appendChild(textNode);
300
301 for (int i = 0; i < lines.length; i++) {
302 Node pNode = xhtmlDoc.createElement("p");
303 Node textNode = xhtmlDoc.createTextNode(lines[i]);
304 pNode.appendChild(textNode);
305 bodyNode.appendChild(pNode);
306 }
307
308 return xhtmlDoc.getDocumentElement();
309
310 }catch(Exception e) {
311 String errmsg = "Exception trying to construct error xhtml page from message: " + message
312 + "\n" + e.getMessage();
313 System.err.println(errmsg);
314 logger.error(errmsg);
315 return null;
316 }
317 }
318
319 // ErrorListener class that can be used to register a handler for any fatal errors, errors and warnings that may
320 // occur when transforming an xml file with an xslt stylesheet. The errors are printed both to the greenstone.log and
321 // to the tomcat console (System.err), and the error message is stored in the errorMessage variable so that it can
322 // be retrieved and be used to generate an xhtml error page.
323 static public class TransformErrorListener implements ErrorListener {
324 protected String errorMessage = null;
325 protected String stylesheet = null;
326 protected String file = null;
327
328 public TransformErrorListener(String xslt) {
329 this.stylesheet = xslt;
330 }
331
332 public TransformErrorListener(Document xslt) {
333 //this.stylesheet = GSXML.xmlNodeToString(xslt);
334 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
335 }
336
337 public TransformErrorListener(File xslt) {
338 stylesheet = "";
339 file = xslt.getAbsolutePath();
340 String error = "Can't locate stylesheet file: " + xslt;
341
342 if(!xslt.exists()) {
343 stylesheet = error;
344 System.err.println("@@@@@@@ " + error);
345 return;
346 }
347 try {
348 BufferedReader in = new BufferedReader(new FileReader(xslt));
349 String line = "";
350 while((line = in.readLine()) != null) {
351 stylesheet = stylesheet + line + "\n";
352 }
353 in.close();
354 in = null;
355 } catch(Exception e) {
356 stylesheet = error;
357 System.err.println("Exception reading file: " + xslt.getAbsolutePath());
358 e.printStackTrace();
359 }
360 }
361
362 // Receive notification of a recoverable error.
363 public void error(TransformerException exception) {
364 handleError("Error:\n", exception);
365 }
366 // Receive notification of a non-recoverable error.
367 public void fatalError(TransformerException exception) {
368 handleError("Fatal Error:\n", exception);
369 }
370 // Receive notification of a warning.
371 public void warning(TransformerException exception) {
372 handleError("Warning:\n", exception);
373 }
374
375 public String toString(TransformerException e) {
376 String msg = "Exception encountered was:\n\t";
377 String location = e.getLocationAsString();
378 if(location != null) {
379 msg = msg + "Location: " + location + "\n\t";
380 }
381
382 return msg + "Message: " + e.getMessage();
383 }
384
385 // clears the errorPage variable after first call to this method
386 public String getErrorMessage() {
387 String errMsg = this.errorMessage;
388 if(this.errorMessage != null) {
389 this.errorMessage = null;
390 }
391 return errMsg;
392 }
393
394 // sets the errorMessage member variable to the data stored in the exception
395 // and writes the errorMessage to the logger and tomcat's System.err
396 protected void handleError(String errorType, TransformerException exception) {
397 this.errorMessage = errorType + toString(exception);
398 if(file != null) {
399 this.errorMessage = this.errorMessage + "\nfilename: " + file;
400 }
401 this.errorMessage += "\nException CAUSE:\n" + exception.getCause();
402 System.err.println("\n****Error transforming xml:\n" + this.errorMessage + "\n****\n");
403 System.err.println("Stylesheet was:\n" + this.stylesheet + "\n\n");
404 logger.error(this.errorMessage);
405 }
406 }
407}
Note: See TracBrowser for help on using the repository browser.