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