001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: */
019: package de.schlund.pfixxml.util;
020:
021: import java.io.ByteArrayOutputStream;
022: import java.io.OutputStream;
023: import java.io.StringWriter;
024: import java.net.MalformedURLException;
025: import java.net.URI;
026: import java.net.URISyntaxException;
027: import java.util.Iterator;
028: import java.util.Map;
029:
030: import javax.xml.transform.ErrorListener;
031: import javax.xml.transform.Result;
032: import javax.xml.transform.Source;
033: import javax.xml.transform.Templates;
034: import javax.xml.transform.Transformer;
035: import javax.xml.transform.TransformerConfigurationException;
036: import javax.xml.transform.TransformerException;
037: import javax.xml.transform.TransformerFactory;
038: import javax.xml.transform.URIResolver;
039: import javax.xml.transform.dom.DOMSource;
040: import javax.xml.transform.sax.SAXSource;
041: import javax.xml.transform.stream.StreamResult;
042: import javax.xml.transform.stream.StreamSource;
043:
044: import org.apache.log4j.Logger;
045: import org.w3c.dom.Document;
046: import org.xml.sax.InputSource;
047:
048: import de.schlund.pfixxml.resources.FileResource;
049: import de.schlund.pfixxml.resources.ResourceUtil;
050: import de.schlund.pfixxml.targets.Target;
051: import de.schlund.pfixxml.targets.TargetGenerationException;
052: import de.schlund.pfixxml.targets.TargetImpl;
053:
054: public class Xslt {
055:
056: private static final Logger LOG = Logger.getLogger(Xslt.class);
057:
058: //-- load documents
059:
060: public synchronized static Transformer createIdentityTransformer(
061: XsltVersion xsltVersion) {
062: try {
063: TransformerFactory trfFact = XsltProvider.getXsltSupport(
064: xsltVersion).getSharedTransformerFactory();
065: return trfFact.newTransformer();
066: } catch (TransformerException e) {
067: throw new RuntimeException(e);
068: }
069: }
070:
071: public static synchronized Transformer createPrettyPrinter(
072: XsltVersion xsltVersion) {
073: try {
074: return XsltProvider.getXsltSupport(xsltVersion)
075: .getPrettyPrinterTemplates().newTransformer();
076: } catch (TransformerConfigurationException e) {
077: throw new RuntimeException(e);
078: }
079: }
080:
081: /**
082: * @deprecated Use {@link #loadTemplates(FileResource)} instead
083: */
084: @Deprecated
085: public static Templates loadTemplates(Path path)
086: throws TransformerConfigurationException {
087: return loadTemplates(XsltVersion.XSLT1, new InputSource(
088: "file://" + path.resolve().getAbsolutePath()), null);
089: }
090:
091: public static Templates loadTemplates(XsltVersion xsltVersion,
092: FileResource path) throws TransformerConfigurationException {
093: return loadTemplates(xsltVersion, path, null);
094: }
095:
096: public static Templates loadTemplates(XsltVersion xsltVersion,
097: FileResource path, TargetImpl parent)
098: throws TransformerConfigurationException {
099: InputSource input;
100: try {
101: input = new InputSource(path.toURL().toString());
102: } catch (MalformedURLException e) {
103: throw new TransformerConfigurationException("\""
104: + path.toString()
105: + "\" does not respresent a valid file", e);
106: }
107: return loadTemplates(xsltVersion, input, parent);
108: }
109:
110: private static Templates loadTemplates(XsltVersion xsltVersion,
111: InputSource input, TargetImpl parent)
112: throws TransformerConfigurationException {
113: Source src = new SAXSource(Xml.createXMLReader(), input);
114: TransformerFactory factory = XsltProvider.getXsltSupport(
115: xsltVersion).getThreadTransformerFactory();
116: if (factory.getErrorListener() == null)
117: factory.setErrorListener(new PFErrorListener());
118: factory.setURIResolver(new FileResolver(parent, xsltVersion));
119: try {
120: Templates retval = factory.newTemplates(src);
121: return retval;
122: } catch (TransformerConfigurationException e) {
123: StringBuffer sb = new StringBuffer();
124: sb
125: .append("TransformerConfigurationException in doLoadTemplates!\n");
126: sb.append("Path: ").append(input.getSystemId())
127: .append("\n");
128: sb.append("Message and Location: ").append(
129: e.getMessageAndLocation()).append("\n");
130: Throwable cause = e.getException();
131: if (cause == null)
132: cause = e.getCause();
133: sb.append("Cause: ").append(
134: (cause != null) ? cause.getMessage() : "none")
135: .append("\n");
136: LOG.error(sb.toString());
137: throw e;
138: }
139: }
140:
141: //-- apply transformation
142:
143: public static void transform(Document xml, Templates templates,
144: Map<String, Object> params, Result result)
145: throws TransformerException {
146: try {
147: doTransform(xml, templates, params, result, false);
148: } catch (UnsupportedOperationException x) {
149: if (result instanceof StreamResult) {
150: OutputStream out = ((StreamResult) result)
151: .getOutputStream();
152: if (out instanceof ByteArrayOutputStream) {
153: LOG
154: .error(
155: "Try to transform again after UnsupportedOperationException",
156: x);
157: ByteArrayOutputStream baos = (ByteArrayOutputStream) out;
158: baos.reset();
159: try {
160: doTransform(xml, templates, params, result,
161: false);
162: } catch (UnsupportedOperationException ex) {
163: LOG
164: .error(
165: "Try to transform and trace after UnsupportedOperationException",
166: ex);
167: baos.reset();
168: doTransform(xml, templates, params, result,
169: true);
170: }
171: }
172: }
173: }
174: }
175:
176: private static void doTransform(Document xml, Templates templates,
177: Map<String, Object> params, Result result, boolean trace)
178: throws TransformerException {
179: XsltVersion xsltVersion = getXsltVersion(templates);
180: Transformer trafo = templates.newTransformer();
181: StringWriter traceWriter = null;
182: if (trace) {
183: traceWriter = new StringWriter();
184: XsltProvider.getXsltSupport(xsltVersion).doTracing(trafo,
185: traceWriter);
186: }
187: long start = 0;
188: if (params != null) {
189: for (Iterator<String> e = params.keySet().iterator(); e
190: .hasNext();) {
191: String name = e.next();
192: Object value = params.get(name);
193: if (name != null && value != null) {
194: trafo.setParameter(name, value);
195: }
196: }
197: }
198: if (LOG.isDebugEnabled())
199: start = System.currentTimeMillis();
200: try {
201: ExtensionFunctionUtils.setExtensionFunctionError(null);
202: trafo.transform(new DOMSource(Xml.parse(xsltVersion, xml)),
203: result);
204: } catch (TransformerException x) {
205: Throwable t = ExtensionFunctionUtils
206: .getExtensionFunctionError();
207: if (t != null) {
208: ExtensionFunctionUtils.setExtensionFunctionError(null);
209: throw new TransformerException(x.getMessage(), x
210: .getLocator(), t);
211: }
212: throw x;
213: } finally {
214: if (trace) {
215: String traceStr = traceWriter.toString();
216: int maxSize = 10000;
217: if (traceStr.length() > maxSize) {
218: traceStr = traceStr.substring(traceStr.length()
219: - maxSize);
220: int ind = traceStr.indexOf('\n');
221: if (ind > -1)
222: traceStr = traceStr.substring(ind);
223: }
224: LOG.error("Last trace steps:\n" + traceStr);
225: }
226: }
227: if (LOG.isDebugEnabled()) {
228: long stop = System.currentTimeMillis();
229: LOG
230: .debug(" ===========> Transforming and serializing took "
231: + (stop - start) + " ms.");
232: }
233: }
234:
235: //--
236:
237: private static XsltVersion getXsltVersion(Templates templates) {
238: Iterator<Map.Entry<XsltVersion, XsltSupport>> it = XsltProvider
239: .getXsltSupport().entrySet().iterator();
240: while (it.hasNext()) {
241: Map.Entry<XsltVersion, XsltSupport> entry = it.next();
242: if (entry.getValue().isInternalTemplate(templates))
243: return entry.getKey();
244: }
245: return null;
246: }
247:
248: static class FileResolver implements URIResolver {
249: private TargetImpl parent;
250: private XsltVersion xsltVersion;
251:
252: public FileResolver(TargetImpl parent, XsltVersion xsltVersion) {
253: this .parent = parent;
254: this .xsltVersion = xsltVersion;
255: }
256:
257: /**
258: * Resolve file url relative to root. Before searching the file system, check
259: * if there is a XML Target defined and use this instead.
260: * @param base ignored, always relative to root
261: * */
262: public Source resolve(String href, String base)
263: throws TransformerException {
264: URI uri;
265: String path;
266: FileResource file;
267: //Rewrite include href to xslt version specific file, that's necessary cause
268: //the XSLT1 and XSLT2 extension functions are incompatible and we want
269: //to support using XSLT1 (Saxon 6.5.x) or XSLT2 (Saxon 8.x) without the
270: //need to have both versions installed, thus the extension functions can't be
271: //referenced within the same stylesheet and we rewrite to the according
272: //version specific stylesheet here
273: if (href.equals("core/xsl/include.xsl")) {
274: if (xsltVersion == XsltVersion.XSLT2)
275: href = "core/xsl/include_xslt2.xsl";
276: }
277:
278: try {
279: uri = new URI(href);
280: } catch (URISyntaxException e) {
281: return new StreamSource(href);
282: }
283: if (uri.getScheme() != null
284: && !uri.getScheme().equals("pfixroot")) {
285: // we don't handle uris with an explicit scheme
286: return new StreamSource(href);
287: }
288: path = uri.getPath();
289: if (uri.getScheme() != null
290: && uri.getScheme().equals("pfixroot")) {
291: if (path.startsWith("/")) {
292: path = path.substring(1);
293: }
294: }
295:
296: if (parent != null) {
297: Target target = parent.getTargetGenerator().getTarget(
298: path);
299: if (target == null) {
300: target = parent.getTargetGenerator()
301: .createXMLLeafTarget(path);
302: }
303:
304: Document dom;
305: try {
306: dom = (Document) target.getDOM();
307: } catch (TargetGenerationException e) {
308: throw new TransformerException(
309: "Could not retrieve target '"
310: + target.getTargetKey()
311: + "' included by stylesheet!", e);
312: }
313:
314: // If Document object is null, the file could not be found or read
315: // so return null to tell the parser the URI could not be resolved
316: if (dom == null) {
317: return null;
318: }
319:
320: Source source = new DOMSource(dom);
321:
322: // There is a bug in Saxon 6.5.3 which causes
323: // a NullPointerException to be thrown, if systemId
324: // is not set
325: source.setSystemId(target.getTargetGenerator()
326: .getDisccachedir().toURI().toString()
327: + "/" + path);
328:
329: // Register included stylesheet with target
330: parent.getAuxDependencyManager().addDependencyTarget(
331: target.getTargetKey());
332: return source;
333: }
334:
335: file = ResourceUtil.getFileResourceFromDocroot(path);
336: try {
337: Source source = new StreamSource(file.toURL()
338: .toString());
339: return source;
340: } catch (MalformedURLException e) {
341: return null;
342: }
343: }
344: }
345:
346: /**
347: * Implementation of ErrorListener interface.
348: */
349: static class PFErrorListener implements ErrorListener {
350: public void warning(TransformerException arg)
351: throws TransformerException {
352: System.err.println("WARNING: " + arg.getMessage());
353: throw arg;
354: }
355:
356: public void error(TransformerException arg)
357: throws TransformerException {
358: System.err.println("ERROR: " + arg.getMessage());
359: throw arg;
360: }
361:
362: public void fatalError(TransformerException arg)
363: throws TransformerException {
364: System.err.println("FATAL ERROR: " + arg.getMessage());
365: throw arg;
366: }
367: }
368: }
|