1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005-2013
18   *     The copyright to this program is held by its authors.
19   *
20   */
21  package org.crosswire.common.xml;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URI;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import javax.xml.transform.ErrorListener;
31  import javax.xml.transform.Result;
32  import javax.xml.transform.Source;
33  import javax.xml.transform.Templates;
34  import javax.xml.transform.Transformer;
35  import javax.xml.transform.TransformerConfigurationException;
36  import javax.xml.transform.TransformerException;
37  import javax.xml.transform.TransformerFactory;
38  import javax.xml.transform.URIResolver;
39  import javax.xml.transform.sax.SAXResult;
40  import javax.xml.transform.sax.SAXSource;
41  import javax.xml.transform.stream.StreamSource;
42  
43  import org.crosswire.common.util.IOUtil;
44  import org.crosswire.common.util.NetUtil;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  import org.xml.sax.ContentHandler;
48  import org.xml.sax.SAXException;
49  
50  /**
51   * A SAXEventProvider that gets its output data from an XSL stylesheet and
52   * another SAXEventProvider (supplying input XML).
53   * 
54   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
55   * @author Joe Walker
56   */
57  public class TransformingSAXEventProvider extends Transformer implements SAXEventProvider {
58      /**
59       * Simple ctor
60       * 
61       * @param xsluri the URI of XSL
62       * @param xmlsep a SAX Event Provider for an XML document
63       */
64      public TransformingSAXEventProvider(URI xsluri, SAXEventProvider xmlsep) {
65          this.xsluri = xsluri;
66          this.xmlsep = xmlsep;
67          this.outputs = new Properties();
68          this.params = new HashMap<String, Object>();
69      }
70  
71      /**
72       * Compile the XSL or retrieve it from the cache
73       * 
74       * @return the template information
75       * @throws TransformerConfigurationException when there is a problem with configuring the transformer
76       * @throws IOException when there is an I/O error
77       */
78      private TemplateInfo getTemplateInfo() throws TransformerConfigurationException, IOException {
79          // we may have one cached
80          TemplateInfo tinfo = txers.get(xsluri);
81  
82          long modtime = -1;
83          if (TransformingSAXEventProvider.developmentMode) {
84              if (tinfo != null) {
85                  modtime = NetUtil.getLastModified(xsluri);
86  
87                  // But check it is up to date
88                  if (modtime > tinfo.getModtime()) {
89                      txers.remove(xsluri);
90                      tinfo = null;
91                      log.debug("updated style, re-caching. xsl={}", xsluri);
92                  }
93              }
94          }
95  
96          if (tinfo == null) {
97              log.debug("generating templates for {}", xsluri);
98  
99              InputStream xslStream = null;
100             try {
101                 xslStream = NetUtil.getInputStream(xsluri);
102                 if (transfact == null) {
103                     transfact = TransformerFactory.newInstance();
104                 }
105                 Templates templates = transfact.newTemplates(new StreamSource(xslStream));
106 
107                 if (modtime == -1) {
108                     modtime = NetUtil.getLastModified(xsluri);
109                 }
110 
111                 tinfo = new TemplateInfo(templates, modtime);
112 
113                 txers.put(xsluri, tinfo);
114             } finally {
115                 IOUtil.close(xslStream);
116             }
117         }
118 
119         return tinfo;
120     }
121 
122     /* (non-Javadoc)
123      * @see javax.xml.transform.Transformer#transform(javax.xml.transform.Source, javax.xml.transform.Result)
124      */
125     @Override
126     public void transform(Source xmlSource, Result outputTarget) throws TransformerException {
127         TemplateInfo tinfo;
128         try {
129             tinfo = getTemplateInfo();
130         } catch (IOException e) {
131             throw new TransformerException(e);
132         }
133 
134         Transformer transformer = tinfo.getTemplates().newTransformer();
135 
136         for (Object obj : outputs.keySet()) {
137             String key = (String) obj;
138             String val = getOutputProperty(key);
139             transformer.setOutputProperty(key, val);
140         }
141 
142         for (String key : params.keySet()) {
143             Object val = params.get(key);
144             transformer.setParameter(key, val);
145         }
146 
147         if (errors != null) {
148             transformer.setErrorListener(errors);
149         }
150 
151         if (resolver != null) {
152             transformer.setURIResolver(resolver);
153         }
154 
155         transformer.transform(xmlSource, outputTarget);
156     }
157 
158     /* (non-Javadoc)
159      * @see org.crosswire.common.xml.SAXEventProvider#provideSAXEvents(org.xml.sax.ContentHandler)
160      */
161     public void provideSAXEvents(ContentHandler handler) throws SAXException {
162         try {
163             Source xmlSource = new SAXSource(new SAXEventProviderXMLReader(xmlsep), new SAXEventProviderInputSource());
164 
165             SAXResult outputTarget = new SAXResult(handler);
166 
167             transform(xmlSource, outputTarget);
168         } catch (TransformerException ex) {
169             throw new SAXException(ex);
170         }
171     }
172 
173     /* (non-Javadoc)
174      * @see javax.xml.transform.Transformer#getErrorListener()
175      */
176     @Override
177     public ErrorListener getErrorListener() {
178         return errors;
179     }
180 
181     /* (non-Javadoc)
182      * @see javax.xml.transform.Transformer#setErrorListener(javax.xml.transform.ErrorListener)
183      */
184     @Override
185     public void setErrorListener(ErrorListener errors) throws IllegalArgumentException {
186         this.errors = errors;
187     }
188 
189     /* (non-Javadoc)
190      * @see javax.xml.transform.Transformer#getURIResolver()
191      */
192     @Override
193     public URIResolver getURIResolver() {
194         return resolver;
195     }
196 
197     /* (non-Javadoc)
198      * @see javax.xml.transform.Transformer#setURIResolver(javax.xml.transform.URIResolver)
199      */
200     @Override
201     public void setURIResolver(URIResolver resolver) {
202         this.resolver = resolver;
203     }
204 
205     /* (non-Javadoc)
206      * @see javax.xml.transform.Transformer#getOutputProperties()
207      */
208     @Override
209     public Properties getOutputProperties() {
210         return outputs;
211     }
212 
213     /* (non-Javadoc)
214      * @see javax.xml.transform.Transformer#setOutputProperties(java.util.Properties)
215      */
216     @Override
217     public void setOutputProperties(Properties outputs) throws IllegalArgumentException {
218         this.outputs = outputs;
219     }
220 
221     /* (non-Javadoc)
222      * @see javax.xml.transform.Transformer#getOutputProperty(java.lang.String)
223      */
224     @Override
225     public String getOutputProperty(String name) throws IllegalArgumentException {
226         return outputs.getProperty(name);
227     }
228 
229     /* (non-Javadoc)
230      * @see javax.xml.transform.Transformer#setOutputProperty(java.lang.String, java.lang.String)
231      */
232     @Override
233     public void setOutputProperty(String name, String value) throws IllegalArgumentException {
234         outputs.setProperty(name, value);
235     }
236 
237     /* (non-Javadoc)
238      * @see javax.xml.transform.Transformer#clearParameters()
239      */
240     @Override
241     public void clearParameters() {
242         params.clear();
243     }
244 
245     /* (non-Javadoc)
246      * @see javax.xml.transform.Transformer#getParameter(java.lang.String)
247      */
248     @Override
249     public Object getParameter(String name) {
250         return params.get(name);
251     }
252 
253     /* (non-Javadoc)
254      * @see javax.xml.transform.Transformer#setParameter(java.lang.String, java.lang.Object)
255      */
256     @Override
257     public void setParameter(String name, Object value) {
258         params.put(name, value);
259     }
260 
261     /**
262      * In development mode the style sheet is checked for modifications before use and if so, it is recompiled.
263      * 
264      * @param developmentMode the developmentMode to set
265      */
266     public static void setDevelopmentMode(boolean developmentMode) {
267         TransformingSAXEventProvider.developmentMode = developmentMode;
268     }
269 
270     /**
271      * In development mode the style sheet is checked for modifications before use and if so, it is recompiled.
272      * 
273      * @return the developmentMode
274      */
275     public static boolean isDevelopmentMode() {
276         return developmentMode;
277     }
278 
279     /**
280      * A simple class to link modification times to Templates objects
281      */
282     private static class TemplateInfo {
283         /**
284          * Simple ctor
285          * 
286          * @param templates the compiled XSL
287          * @param modtime the time the XSL was last modified
288          */
289         public TemplateInfo(Templates templates, long modtime) {
290             this.templates = templates;
291             this.modtime = modtime;
292         }
293 
294         /**
295          * The transformer
296          * 
297          * @return the compiled XSL
298          */
299         Templates getTemplates() {
300             return templates;
301         }
302 
303         /**
304          * The time the xsl file was last modified
305          * 
306          * @return when the xsl was last modified.
307          */
308         long getModtime() {
309             return modtime;
310         }
311 
312         private Templates templates;
313         private long modtime;
314     }
315 
316     /**
317      * In development mode the style sheet is checked for modifications before use and if so, it is recompiled.
318      */
319     private static boolean developmentMode;
320 
321     /**
322      * The remembered ErrorListener because the transformer has not been created
323      */
324     private ErrorListener errors;
325 
326     /**
327      * The remembered URIResolver because the transformer has not been created
328      */
329     private URIResolver resolver;
330 
331     /**
332      * The remembered OutputProperties because the transformer has not been
333      * created
334      */
335     private Properties outputs;
336 
337     /**
338      * The remembered Parameters because the transformer has not been created
339      */
340     private Map<String, Object> params;
341 
342     /**
343      * The XSL stylesheet
344      */
345     private URI xsluri;
346 
347     /**
348      * The XML input source
349      */
350     private SAXEventProvider xmlsep;
351 
352     /**
353      * How we get the transformer objects
354      */
355     private TransformerFactory transfact;
356 
357     /**
358      * A cache of transformers
359      */
360     private static Map<URI, TemplateInfo> txers = new HashMap<URI, TemplateInfo>();
361 
362     /**
363      * The log stream
364      */
365     private static final Logger log = LoggerFactory.getLogger(TransformingSAXEventProvider.class);
366 }
367