001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional;
020:
021: import java.io.BufferedInputStream;
022: import java.io.BufferedOutputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileOutputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.OutputStream;
029: import java.util.Hashtable;
030: import java.util.Vector;
031: import java.util.Enumeration;
032: import java.net.URL;
033: import javax.xml.parsers.ParserConfigurationException;
034: import javax.xml.parsers.SAXParserFactory;
035: import javax.xml.transform.ErrorListener;
036: import javax.xml.transform.Source;
037: import javax.xml.transform.SourceLocator;
038: import javax.xml.transform.Templates;
039: import javax.xml.transform.Transformer;
040: import javax.xml.transform.TransformerException;
041: import javax.xml.transform.TransformerFactory;
042: import javax.xml.transform.URIResolver;
043: import javax.xml.transform.sax.SAXSource;
044: import javax.xml.transform.stream.StreamResult;
045: import javax.xml.transform.stream.StreamSource;
046: import javax.xml.transform.TransformerConfigurationException;
047: import org.apache.tools.ant.BuildException;
048: import org.apache.tools.ant.Project;
049: import org.apache.tools.ant.taskdefs.XSLTLiaison3;
050: import org.apache.tools.ant.taskdefs.XSLTLogger;
051: import org.apache.tools.ant.taskdefs.XSLTLoggerAware;
052: import org.apache.tools.ant.taskdefs.XSLTProcess;
053: import org.apache.tools.ant.types.XMLCatalog;
054: import org.apache.tools.ant.types.Resource;
055: import org.apache.tools.ant.types.resources.FileResource;
056: import org.apache.tools.ant.types.resources.URLResource;
057: import org.apache.tools.ant.util.FileUtils;
058: import org.apache.tools.ant.util.JAXPUtils;
059: import org.xml.sax.EntityResolver;
060: import org.xml.sax.InputSource;
061: import org.xml.sax.SAXException;
062: import org.xml.sax.XMLReader;
063:
064: /**
065: * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1)
066: *
067: * @since Ant 1.3
068: */
069: public class TraXLiaison implements XSLTLiaison3, ErrorListener,
070: XSLTLoggerAware {
071:
072: /**
073: * Helper for transforming filenames to URIs.
074: *
075: * @since Ant 1.7
076: */
077: private static final FileUtils FILE_UTILS = FileUtils
078: .getFileUtils();
079:
080: /**
081: * The current <code>Project</code>
082: */
083: private Project project;
084:
085: /**
086: * the name of the factory implementation class to use
087: * or null for default JAXP lookup.
088: */
089: private String factoryName = null;
090:
091: /** The trax TransformerFactory */
092: private TransformerFactory tfactory = null;
093:
094: /** stylesheet to use for transformation */
095: private Resource stylesheet;
096:
097: private XSLTLogger logger;
098:
099: /** possible resolver for publicIds */
100: private EntityResolver entityResolver;
101:
102: /** transformer to use for processing files */
103: private Transformer transformer;
104:
105: /** The In memory version of the stylesheet */
106: private Templates templates;
107:
108: /**
109: * The modification time of the stylesheet from which the templates
110: * are read
111: */
112: private long templatesModTime;
113:
114: /** possible resolver for URIs */
115: private URIResolver uriResolver;
116:
117: /** transformer output properties */
118: private Vector outputProperties = new Vector();
119:
120: /** stylesheet parameters */
121: private Hashtable params = new Hashtable();
122:
123: /** factory attributes */
124: private Vector attributes = new Vector();
125:
126: /**
127: * Constructor for TraXLiaison.
128: * @throws Exception never
129: */
130: public TraXLiaison() throws Exception {
131: }
132:
133: /**
134: * Set the stylesheet file.
135: * @param stylesheet a <code>File</code> value
136: * @throws Exception on error
137: */
138: public void setStylesheet(File stylesheet) throws Exception {
139: FileResource fr = new FileResource();
140: fr.setProject(project);
141: fr.setFile(stylesheet);
142: setStylesheet(fr);
143: }
144:
145: /**
146: * Set the stylesheet file.
147: * @param stylesheet a {@link org.apache.tools.ant.types.Resource} value
148: * @throws Exception on error
149: */
150: public void setStylesheet(Resource stylesheet) throws Exception {
151: if (this .stylesheet != null) {
152: // resetting the stylesheet - reset transformer
153: transformer = null;
154:
155: // do we need to reset templates as well
156: if (!this .stylesheet.equals(stylesheet)
157: || (stylesheet.getLastModified() != templatesModTime)) {
158: templates = null;
159: }
160: }
161: this .stylesheet = stylesheet;
162: }
163:
164: /**
165: * Transform an input file.
166: * @param infile the file to transform
167: * @param outfile the result file
168: * @throws Exception on error
169: */
170: public void transform(File infile, File outfile) throws Exception {
171: if (transformer == null) {
172: createTransformer();
173: }
174:
175: InputStream fis = null;
176: OutputStream fos = null;
177: try {
178: fis = new BufferedInputStream(new FileInputStream(infile));
179: fos = new BufferedOutputStream(
180: new FileOutputStream(outfile));
181: StreamResult res = new StreamResult(fos);
182: // not sure what could be the need of this...
183: res.setSystemId(JAXPUtils.getSystemId(outfile));
184: Source src = getSource(fis, infile);
185:
186: // set parameters on each transformation, maybe something has changed
187: //(e.g. value of file name parameter)
188: setTransformationParameters();
189:
190: transformer.transform(src, res);
191: } finally {
192: // make sure to close all handles, otherwise the garbage
193: // collector will close them...whenever possible and
194: // Windows may complain about not being able to delete files.
195: try {
196: if (fis != null) {
197: fis.close();
198: }
199: } catch (IOException ignored) {
200: // ignore
201: }
202: try {
203: if (fos != null) {
204: fos.close();
205: }
206: } catch (IOException ignored) {
207: // ignore
208: }
209: }
210: }
211:
212: /**
213: * Get the source instance from the stream and id of the file.
214: * @param is the stream containing the stylesheet data.
215: * @param infile the file that will be used for the systemid.
216: * @return the configured source instance matching the stylesheet.
217: * @throws ParserConfigurationException if a parser cannot be created which
218: * satisfies the requested configuration.
219: * @throws SAXException in case of problem detected by the SAX parser.
220: */
221: private Source getSource(InputStream is, File infile)
222: throws ParserConfigurationException, SAXException {
223: // todo: is this comment still relevant ??
224: // FIXME: need to use a SAXSource as the source for the transform
225: // so we can plug in our own entity resolver
226: Source src = null;
227: if (entityResolver != null) {
228: if (getFactory().getFeature(SAXSource.FEATURE)) {
229: SAXParserFactory spFactory = SAXParserFactory
230: .newInstance();
231: spFactory.setNamespaceAware(true);
232: XMLReader reader = spFactory.newSAXParser()
233: .getXMLReader();
234: reader.setEntityResolver(entityResolver);
235: src = new SAXSource(reader, new InputSource(is));
236: } else {
237: throw new IllegalStateException(
238: "xcatalog specified, but "
239: + "parser doesn't support SAX");
240: }
241: } else {
242: // WARN: Don't use the StreamSource(File) ctor. It won't work with
243: // xalan prior to 2.2 because of systemid bugs.
244: src = new StreamSource(is);
245: }
246: src.setSystemId(JAXPUtils.getSystemId(infile));
247: return src;
248: }
249:
250: private Source getSource(InputStream is, Resource resource)
251: throws ParserConfigurationException, SAXException {
252: // todo: is this comment still relevant ??
253: // FIXME: need to use a SAXSource as the source for the transform
254: // so we can plug in our own entity resolver
255: Source src = null;
256: if (entityResolver != null) {
257: if (getFactory().getFeature(SAXSource.FEATURE)) {
258: SAXParserFactory spFactory = SAXParserFactory
259: .newInstance();
260: spFactory.setNamespaceAware(true);
261: XMLReader reader = spFactory.newSAXParser()
262: .getXMLReader();
263: reader.setEntityResolver(entityResolver);
264: src = new SAXSource(reader, new InputSource(is));
265: } else {
266: throw new IllegalStateException(
267: "xcatalog specified, but "
268: + "parser doesn't support SAX");
269: }
270: } else {
271: // WARN: Don't use the StreamSource(File) ctor. It won't work with
272: // xalan prior to 2.2 because of systemid bugs.
273: src = new StreamSource(is);
274: }
275: // The line below is a hack: the system id must an URI, but it is not
276: // cleat to get the URI of an resource, so just set the name of the
277: // resource as a system id
278: src.setSystemId(resourceToURI(resource));
279: return src;
280: }
281:
282: private String resourceToURI(Resource resource) {
283: if (resource instanceof FileResource) {
284: File f = ((FileResource) resource).getFile();
285: return FILE_UTILS.toURI(f.getAbsolutePath());
286: }
287: if (resource instanceof URLResource) {
288: URL u = ((URLResource) resource).getURL();
289: return String.valueOf(u);
290: } else {
291: return resource.getName();
292: }
293: }
294:
295: /**
296: * Read in templates from the stylesheet
297: */
298: private void readTemplates() throws IOException,
299: TransformerConfigurationException,
300: ParserConfigurationException, SAXException {
301:
302: // Use a stream so that you can close it yourself quickly
303: // and avoid keeping the handle until the object is garbaged.
304: // (always keep control), otherwise you won't be able to delete
305: // the file quickly on windows.
306: InputStream xslStream = null;
307: try {
308: xslStream = new BufferedInputStream(stylesheet
309: .getInputStream());
310: templatesModTime = stylesheet.getLastModified();
311: Source src = getSource(xslStream, stylesheet);
312: templates = getFactory().newTemplates(src);
313: } finally {
314: if (xslStream != null) {
315: xslStream.close();
316: }
317: }
318: }
319:
320: /**
321: * Create a new transformer based on the liaison settings
322: * @throws Exception thrown if there is an error during creation.
323: * @see #setStylesheet(java.io.File)
324: * @see #addParam(java.lang.String, java.lang.String)
325: * @see #setOutputProperty(java.lang.String, java.lang.String)
326: */
327: private void createTransformer() throws Exception {
328: if (templates == null) {
329: readTemplates();
330: }
331:
332: transformer = templates.newTransformer();
333:
334: // configure the transformer...
335: transformer.setErrorListener(this );
336: if (uriResolver != null) {
337: transformer.setURIResolver(uriResolver);
338: }
339: for (int i = 0; i < outputProperties.size(); i++) {
340: final String[] pair = (String[]) outputProperties
341: .elementAt(i);
342: transformer.setOutputProperty(pair[0], pair[1]);
343: }
344: }
345:
346: /**
347: * Sets the paramters for the transformer.
348: */
349: private void setTransformationParameters() {
350: for (final Enumeration enumeration = params.keys(); enumeration
351: .hasMoreElements();) {
352: final String name = (String) enumeration.nextElement();
353: final String value = (String) params.get(name);
354: transformer.setParameter(name, value);
355: }
356: }
357:
358: /**
359: * return the Transformer factory associated to this liaison.
360: * @return the Transformer factory associated to this liaison.
361: * @throws BuildException thrown if there is a problem creating
362: * the factory.
363: * @see #setFactory(String)
364: * @since Ant 1.5.2
365: */
366: private TransformerFactory getFactory() throws BuildException {
367: if (tfactory != null) {
368: return tfactory;
369: }
370: // not initialized yet, so create the factory
371: if (factoryName == null) {
372: tfactory = TransformerFactory.newInstance();
373: } else {
374: try {
375: Class clazz = Class.forName(factoryName);
376: tfactory = (TransformerFactory) clazz.newInstance();
377: } catch (Exception e) {
378: throw new BuildException(e);
379: }
380: }
381: tfactory.setErrorListener(this );
382:
383: // specific attributes for the transformer
384: for (int i = 0; i < attributes.size(); i++) {
385: final Object[] pair = (Object[]) attributes.elementAt(i);
386: tfactory.setAttribute((String) pair[0], pair[1]);
387: }
388:
389: if (uriResolver != null) {
390: tfactory.setURIResolver(uriResolver);
391: }
392: return tfactory;
393: }
394:
395: /**
396: * Set the factory name to use instead of JAXP default lookup.
397: * @param name the fully qualified class name of the factory to use
398: * or null for the default JAXP look up mechanism.
399: * @since Ant 1.6
400: */
401: public void setFactory(String name) {
402: factoryName = name;
403: }
404:
405: /**
406: * Set a custom attribute for the JAXP factory implementation.
407: * @param name the attribute name.
408: * @param value the value of the attribute, usually a boolean
409: * string or object.
410: * @since Ant 1.6
411: */
412: public void setAttribute(String name, Object value) {
413: final Object[] pair = new Object[] { name, value };
414: attributes.addElement(pair);
415: }
416:
417: /**
418: * Set the output property for the current transformer.
419: * Note that the stylesheet must be set prior to calling
420: * this method.
421: * @param name the output property name.
422: * @param value the output property value.
423: * @since Ant 1.5
424: * @since Ant 1.5
425: */
426: public void setOutputProperty(String name, String value) {
427: final String[] pair = new String[] { name, value };
428: outputProperties.addElement(pair);
429: }
430:
431: /**
432: * Set the class to resolve entities during the transformation.
433: * @param aResolver the resolver class.
434: */
435: public void setEntityResolver(EntityResolver aResolver) {
436: entityResolver = aResolver;
437: }
438:
439: /**
440: * Set the class to resolve URIs during the transformation
441: * @param aResolver a <code>EntityResolver</code> value
442: */
443: public void setURIResolver(URIResolver aResolver) {
444: uriResolver = aResolver;
445: }
446:
447: /**
448: * Add a parameter.
449: * @param name the name of the parameter
450: * @param value the value of the parameter
451: */
452: public void addParam(String name, String value) {
453: params.put(name, value);
454: }
455:
456: /**
457: * Set a logger.
458: * @param l a logger.
459: */
460: public void setLogger(XSLTLogger l) {
461: logger = l;
462: }
463:
464: /**
465: * Log an error.
466: * @param e the exception to log.
467: */
468: public void error(TransformerException e) {
469: logError(e, "Error");
470: }
471:
472: /**
473: * Log a fatal error.
474: * @param e the exception to log.
475: */
476: public void fatalError(TransformerException e) {
477: logError(e, "Fatal Error");
478: throw new BuildException("Fatal error during transformation", e);
479: }
480:
481: /**
482: * Log a warning.
483: * @param e the exception to log.
484: */
485: public void warning(TransformerException e) {
486: logError(e, "Warning");
487: }
488:
489: private void logError(TransformerException e, String type) {
490: if (logger == null) {
491: return;
492: }
493:
494: StringBuffer msg = new StringBuffer();
495: SourceLocator locator = e.getLocator();
496: if (locator != null) {
497: String systemid = locator.getSystemId();
498: if (systemid != null) {
499: String url = systemid;
500: if (url.startsWith("file:")) {
501: url = FileUtils.getFileUtils().fromURI(url);
502: }
503: msg.append(url);
504: } else {
505: msg.append("Unknown file");
506: }
507: int line = locator.getLineNumber();
508: if (line != -1) {
509: msg.append(":");
510: msg.append(line);
511: int column = locator.getColumnNumber();
512: if (column != -1) {
513: msg.append(":");
514: msg.append(column);
515: }
516: }
517: }
518: msg.append(": ");
519: msg.append(type);
520: msg.append("! ");
521: msg.append(e.getMessage());
522: if (e.getCause() != null) {
523: msg.append(" Cause: ");
524: msg.append(e.getCause());
525: }
526:
527: logger.log(msg.toString());
528: }
529:
530: // kept for backwards compatibility
531: /**
532: * @param file the filename to use for the systemid
533: * @return the systemid
534: * @deprecated since 1.5.x.
535: * Use org.apache.tools.ant.util.JAXPUtils#getSystemId instead.
536: */
537: protected String getSystemId(File file) {
538: return JAXPUtils.getSystemId(file);
539: }
540:
541: /**
542: * Specific configuration for the TRaX liaison.
543: * @param xsltTask the XSLTProcess task instance from which this liasion
544: * is to be configured.
545: */
546: public void configure(XSLTProcess xsltTask) {
547: project = xsltTask.getProject();
548: XSLTProcess.Factory factory = xsltTask.getFactory();
549: if (factory != null) {
550: setFactory(factory.getName());
551:
552: // configure factory attributes
553: for (Enumeration attrs = factory.getAttributes(); attrs
554: .hasMoreElements();) {
555: XSLTProcess.Factory.Attribute attr = (XSLTProcess.Factory.Attribute) attrs
556: .nextElement();
557: setAttribute(attr.getName(), attr.getValue());
558: }
559: }
560:
561: XMLCatalog xmlCatalog = xsltTask.getXMLCatalog();
562: // use XMLCatalog as the entity resolver and URI resolver
563: if (xmlCatalog != null) {
564: setEntityResolver(xmlCatalog);
565: setURIResolver(xmlCatalog);
566: }
567:
568: // configure output properties
569: for (Enumeration props = xsltTask.getOutputProperties(); props
570: .hasMoreElements();) {
571: XSLTProcess.OutputProperty prop = (XSLTProcess.OutputProperty) props
572: .nextElement();
573: setOutputProperty(prop.getName(), prop.getValue());
574: }
575: }
576: }
|