1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional;
  18. import java.io.BufferedInputStream;
  19. import java.io.BufferedOutputStream;
  20. import java.io.File;
  21. import java.io.FileInputStream;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStream;
  26. import java.util.Vector;
  27. import java.util.Enumeration;
  28. import javax.xml.parsers.ParserConfigurationException;
  29. import javax.xml.parsers.SAXParserFactory;
  30. import javax.xml.transform.ErrorListener;
  31. import javax.xml.transform.Source;
  32. import javax.xml.transform.SourceLocator;
  33. import javax.xml.transform.Templates;
  34. import javax.xml.transform.Transformer;
  35. import javax.xml.transform.TransformerException;
  36. import javax.xml.transform.TransformerFactory;
  37. import javax.xml.transform.URIResolver;
  38. import javax.xml.transform.sax.SAXSource;
  39. import javax.xml.transform.stream.StreamResult;
  40. import javax.xml.transform.stream.StreamSource;
  41. import javax.xml.transform.TransformerConfigurationException;
  42. import org.apache.tools.ant.BuildException;
  43. import org.apache.tools.ant.taskdefs.XSLTLiaison2;
  44. import org.apache.tools.ant.taskdefs.XSLTProcess;
  45. import org.apache.tools.ant.taskdefs.XSLTLogger;
  46. import org.apache.tools.ant.taskdefs.XSLTLoggerAware;
  47. import org.apache.tools.ant.types.XMLCatalog;
  48. import org.apache.tools.ant.util.JAXPUtils;
  49. import org.xml.sax.EntityResolver;
  50. import org.xml.sax.InputSource;
  51. import org.xml.sax.SAXException;
  52. import org.xml.sax.XMLReader;
  53. /**
  54. * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1)
  55. *
  56. * @since Ant 1.3
  57. */
  58. public class TraXLiaison implements XSLTLiaison2, ErrorListener, XSLTLoggerAware {
  59. /**
  60. * the name of the factory implementation class to use
  61. * or null for default JAXP lookup.
  62. */
  63. private String factoryName = null;
  64. /** The trax TransformerFactory */
  65. private TransformerFactory tfactory = null;
  66. /** stylesheet to use for transformation */
  67. private File stylesheet;
  68. private XSLTLogger logger;
  69. /** possible resolver for publicIds */
  70. private EntityResolver entityResolver;
  71. /** transformer to use for processing files */
  72. private Transformer transformer;
  73. /** The In memory version of the stylesheet */
  74. private Templates templates;
  75. /**
  76. * The modification time of the stylesheet from which the templates
  77. * are read
  78. */
  79. private long templatesModTime;
  80. /** possible resolver for URIs */
  81. private URIResolver uriResolver;
  82. /** transformer output properties */
  83. private Vector outputProperties = new Vector();
  84. /** stylesheet parameters */
  85. private Vector params = new Vector();
  86. /** factory attributes */
  87. private Vector attributes = new Vector();
  88. public TraXLiaison() throws Exception {
  89. }
  90. public void setStylesheet(File stylesheet) throws Exception {
  91. if (this.stylesheet != null) {
  92. // resetting the stylesheet - reset transformer
  93. transformer = null;
  94. // do we need to reset templates as well
  95. if (!this.stylesheet.equals(stylesheet)
  96. || (stylesheet.lastModified() != templatesModTime)) {
  97. templates = null;
  98. }
  99. }
  100. this.stylesheet = stylesheet;
  101. }
  102. public void transform(File infile, File outfile) throws Exception {
  103. if (transformer == null) {
  104. createTransformer();
  105. }
  106. InputStream fis = null;
  107. OutputStream fos = null;
  108. try {
  109. fis = new BufferedInputStream(new FileInputStream(infile));
  110. fos = new BufferedOutputStream(new FileOutputStream(outfile));
  111. StreamResult res = new StreamResult(fos);
  112. // not sure what could be the need of this...
  113. res.setSystemId(JAXPUtils.getSystemId(outfile));
  114. Source src = getSource(fis, infile);
  115. transformer.transform(src, res);
  116. } finally {
  117. // make sure to close all handles, otherwise the garbage
  118. // collector will close them...whenever possible and
  119. // Windows may complain about not being able to delete files.
  120. try {
  121. if (fis != null) {
  122. fis.close();
  123. }
  124. } catch (IOException ignored) {
  125. // ignore
  126. }
  127. try {
  128. if (fos != null) {
  129. fos.close();
  130. }
  131. } catch (IOException ignored) {
  132. // ignore
  133. }
  134. }
  135. }
  136. /**
  137. * Get the source instance from the stream and id of the file.
  138. * @param is the stream containing the stylesheet data.
  139. * @param infile the file that will be used for the systemid.
  140. * @return the configured source instance matching the stylesheet.
  141. * @throws Exception if there is a problem creating the source.
  142. */
  143. private Source getSource(InputStream is, File infile)
  144. throws ParserConfigurationException, SAXException {
  145. // todo: is this comment still relevant ??
  146. // FIXME: need to use a SAXSource as the source for the transform
  147. // so we can plug in our own entity resolver
  148. Source src = null;
  149. if (entityResolver != null) {
  150. if (getFactory().getFeature(SAXSource.FEATURE)) {
  151. SAXParserFactory spFactory = SAXParserFactory.newInstance();
  152. spFactory.setNamespaceAware(true);
  153. XMLReader reader = spFactory.newSAXParser().getXMLReader();
  154. reader.setEntityResolver(entityResolver);
  155. src = new SAXSource(reader, new InputSource(is));
  156. } else {
  157. throw new IllegalStateException("xcatalog specified, but "
  158. + "parser doesn't support SAX");
  159. }
  160. } else {
  161. // WARN: Don't use the StreamSource(File) ctor. It won't work with
  162. // xalan prior to 2.2 because of systemid bugs.
  163. src = new StreamSource(is);
  164. }
  165. src.setSystemId(JAXPUtils.getSystemId(infile));
  166. return src;
  167. }
  168. /**
  169. * Read in templates from the stylesheet
  170. */
  171. private void readTemplates()
  172. throws IOException, TransformerConfigurationException,
  173. ParserConfigurationException, SAXException {
  174. // Use a stream so that you can close it yourself quickly
  175. // and avoid keeping the handle until the object is garbaged.
  176. // (always keep control), otherwise you won't be able to delete
  177. // the file quickly on windows.
  178. InputStream xslStream = null;
  179. try {
  180. xslStream
  181. = new BufferedInputStream(new FileInputStream(stylesheet));
  182. templatesModTime = stylesheet.lastModified();
  183. Source src = getSource(xslStream, stylesheet);
  184. templates = getFactory().newTemplates(src);
  185. } finally {
  186. if (xslStream != null) {
  187. xslStream.close();
  188. }
  189. }
  190. }
  191. /**
  192. * Create a new transformer based on the liaison settings
  193. * @return the newly created and configured transformer.
  194. * @throws Exception thrown if there is an error during creation.
  195. * @see #setStylesheet(java.io.File)
  196. * @see #addParam(java.lang.String, java.lang.String)
  197. * @see #setOutputProperty(java.lang.String, java.lang.String)
  198. */
  199. private void createTransformer() throws Exception {
  200. if (templates == null) {
  201. readTemplates();
  202. }
  203. transformer = templates.newTransformer();
  204. // configure the transformer...
  205. transformer.setErrorListener(this);
  206. if (uriResolver != null) {
  207. transformer.setURIResolver(uriResolver);
  208. }
  209. for (int i = 0; i < params.size(); i++) {
  210. final String[] pair = (String[]) params.elementAt(i);
  211. transformer.setParameter(pair[0], pair[1]);
  212. }
  213. for (int i = 0; i < outputProperties.size(); i++) {
  214. final String[] pair = (String[]) outputProperties.elementAt(i);
  215. transformer.setOutputProperty(pair[0], pair[1]);
  216. }
  217. }
  218. /**
  219. * return the Transformer factory associated to this liaison.
  220. * @return the Transformer factory associated to this liaison.
  221. * @throws BuildException thrown if there is a problem creating
  222. * the factory.
  223. * @see #setFactory(String)
  224. * @since Ant 1.5.2
  225. */
  226. private TransformerFactory getFactory() throws BuildException {
  227. if (tfactory != null) {
  228. return tfactory;
  229. }
  230. // not initialized yet, so create the factory
  231. if (factoryName == null) {
  232. tfactory = TransformerFactory.newInstance();
  233. } else {
  234. try {
  235. Class clazz = Class.forName(factoryName);
  236. tfactory = (TransformerFactory) clazz.newInstance();
  237. } catch (Exception e) {
  238. throw new BuildException(e);
  239. }
  240. }
  241. tfactory.setErrorListener(this);
  242. // specific attributes for the transformer
  243. for (int i = 0; i < attributes.size(); i++) {
  244. final Object[] pair = (Object[]) attributes.elementAt(i);
  245. tfactory.setAttribute((String) pair[0], pair[1]);
  246. }
  247. if (uriResolver != null) {
  248. tfactory.setURIResolver(uriResolver);
  249. }
  250. return tfactory;
  251. }
  252. /**
  253. * Set the factory name to use instead of JAXP default lookup.
  254. * @param name the fully qualified class name of the factory to use
  255. * or null for the default JAXP look up mechanism.
  256. * @since Ant 1.6
  257. */
  258. public void setFactory(String name) {
  259. factoryName = name;
  260. }
  261. /**
  262. * Set a custom attribute for the JAXP factory implementation.
  263. * @param name the attribute name.
  264. * @param value the value of the attribute, usually a boolean
  265. * string or object.
  266. * @since Ant 1.6
  267. */
  268. public void setAttribute(String name, Object value) {
  269. final Object[] pair = new Object[]{name, value};
  270. attributes.addElement(pair);
  271. }
  272. /**
  273. * Set the output property for the current transformer.
  274. * Note that the stylesheet must be set prior to calling
  275. * this method.
  276. * @param name the output property name.
  277. * @param value the output property value.
  278. * @since Ant 1.5
  279. * @since Ant 1.5
  280. */
  281. public void setOutputProperty(String name, String value) {
  282. final String[] pair = new String[]{name, value};
  283. outputProperties.addElement(pair);
  284. }
  285. /** Set the class to resolve entities during the transformation
  286. */
  287. public void setEntityResolver(EntityResolver aResolver) {
  288. entityResolver = aResolver;
  289. }
  290. /** Set the class to resolve URIs during the transformation
  291. */
  292. public void setURIResolver(URIResolver aResolver) {
  293. uriResolver = aResolver;
  294. }
  295. public void addParam(String name, String value) {
  296. final String[] pair = new String[]{name, value};
  297. params.addElement(pair);
  298. }
  299. public void setLogger(XSLTLogger l) {
  300. logger = l;
  301. }
  302. public void error(TransformerException e) {
  303. logError(e, "Error");
  304. }
  305. public void fatalError(TransformerException e) {
  306. logError(e, "Fatal Error");
  307. throw new BuildException("Fatal error during transformation", e);
  308. }
  309. public void warning(TransformerException e) {
  310. logError(e, "Warning");
  311. }
  312. private void logError(TransformerException e, String type) {
  313. if (logger == null) {
  314. return;
  315. }
  316. StringBuffer msg = new StringBuffer();
  317. SourceLocator locator = e.getLocator();
  318. if (locator != null) {
  319. String systemid = locator.getSystemId();
  320. if (systemid != null) {
  321. String url = systemid;
  322. if (url.startsWith("file:///")) {
  323. url = url.substring(8);
  324. }
  325. msg.append(url);
  326. } else {
  327. msg.append("Unknown file");
  328. }
  329. int line = locator.getLineNumber();
  330. if (line != -1) {
  331. msg.append(":" + line);
  332. int column = locator.getColumnNumber();
  333. if (column != -1) {
  334. msg.append(":" + column);
  335. }
  336. }
  337. }
  338. msg.append(": " + type + "! ");
  339. msg.append(e.getMessage());
  340. if (e.getCause() != null) {
  341. msg.append(" Cause: " + e.getCause());
  342. }
  343. logger.log(msg.toString());
  344. }
  345. // kept for backwards compatibility
  346. /**
  347. * @deprecated use org.apache.tools.ant.util.JAXPUtils#getSystemId instead
  348. */
  349. protected String getSystemId(File file) {
  350. return JAXPUtils.getSystemId(file);
  351. }
  352. /**
  353. * Specific configuration for the TRaX liaison.
  354. * @param xsltTask the XSLTProcess task instance from which this liasion
  355. * is to be configured.
  356. */
  357. public void configure(XSLTProcess xsltTask) {
  358. XSLTProcess.Factory factory = xsltTask.getFactory();
  359. if (factory != null) {
  360. setFactory(factory.getName());
  361. // configure factory attributes
  362. for (Enumeration attrs = factory.getAttributes();
  363. attrs.hasMoreElements();) {
  364. XSLTProcess.Factory.Attribute attr =
  365. (XSLTProcess.Factory.Attribute) attrs.nextElement();
  366. setAttribute(attr.getName(), attr.getValue());
  367. }
  368. }
  369. XMLCatalog xmlCatalog = xsltTask.getXMLCatalog();
  370. // use XMLCatalog as the entity resolver and URI resolver
  371. if (xmlCatalog != null) {
  372. setEntityResolver(xmlCatalog);
  373. setURIResolver(xmlCatalog);
  374. }
  375. // configure output properties
  376. for (Enumeration props = xsltTask.getOutputProperties();
  377. props.hasMoreElements();) {
  378. XSLTProcess.OutputProperty prop
  379. = (XSLTProcess.OutputProperty) props.nextElement();
  380. setOutputProperty(prop.getName(), prop.getValue());
  381. }
  382. }
  383. }