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 it's 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 for license details.<br>
55   *      The copyright to this program is held by it's authors.
56   * @author Joe Walker [joe at eireneh dot com]
57   */
58  public class TransformingSAXEventProvider extends Transformer implements SAXEventProvider {
59      /**
60       * Simple ctor
61       */
62      public TransformingSAXEventProvider(URI xsluri, SAXEventProvider xmlsep) {
63          this.xsluri = xsluri;
64          this.xmlsep = xmlsep;
65          this.outputs = new Properties();
66          this.params = new HashMap<String, Object>();
67      }
68  
69      /**
70       * Compile the XSL or retrieve it from the cache
71       * 
72       * @throws IOException
73       */
74      private TemplateInfo getTemplateInfo() throws TransformerConfigurationException, IOException {
75          // we may have one cached
76          TemplateInfo tinfo = txers.get(xsluri);
77  
78          long modtime = -1;
79          if (TransformingSAXEventProvider.developmentMode) {
80              if (tinfo != null) {
81                  modtime = NetUtil.getLastModified(xsluri);
82  
83                  // But check it is up to date
84                  if (modtime > tinfo.getModtime()) {
85                      txers.remove(xsluri);
86                      tinfo = null;
87                      log.debug("updated style, re-caching. xsl={}", xsluri);
88                  }
89              }
90          }
91  
92          if (tinfo == null) {
93              log.debug("generating templates for {}", xsluri);
94  
95              InputStream xslStream = null;
96              try {
97                  xslStream = NetUtil.getInputStream(xsluri);
98                  if (transfact == null) {
99                      transfact = TransformerFactory.newInstance();
100                 }
101                 Templates templates = transfact.newTemplates(new StreamSource(xslStream));
102 
103                 if (modtime == -1) {
104                     modtime = NetUtil.getLastModified(xsluri);
105                 }
106 
107                 tinfo = new TemplateInfo(templates, modtime);
108 
109                 txers.put(xsluri, tinfo);
110             } finally {
111                 IOUtil.close(xslStream);
112             }
113         }
114 
115         return tinfo;
116     }
117 
118     /*
119      * (non-Javadoc)
120      * 
121      * @see
122      * javax.xml.transform.Transformer#transform(javax.xml.transform.Source,
123      * 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     /*
159      * (non-Javadoc)
160      * 
161      * @see
162      * org.crosswire.common.xml.SAXEventProvider#provideSAXEvents(org.xml.sax
163      * .ContentHandler)
164      */
165     public void provideSAXEvents(ContentHandler handler) throws SAXException {
166         try {
167             Source xmlSource = new SAXSource(new SAXEventProviderXMLReader(xmlsep), new SAXEventProviderInputSource());
168 
169             SAXResult outputTarget = new SAXResult(handler);
170 
171             transform(xmlSource, outputTarget);
172         } catch (TransformerException ex) {
173             throw new SAXException(ex);
174         }
175     }
176 
177     /**
178      * @see Transformer#getErrorListener()
179      */
180     @Override
181     public ErrorListener getErrorListener() {
182         return errors;
183     }
184 
185     /**
186      * @see Transformer#setErrorListener(javax.xml.transform.ErrorListener)
187      */
188     @Override
189     public void setErrorListener(ErrorListener errors) throws IllegalArgumentException {
190         this.errors = errors;
191     }
192 
193     /**
194      * @see Transformer#getURIResolver()
195      */
196     @Override
197     public URIResolver getURIResolver() {
198         return resolver;
199     }
200 
201     /**
202      * @see Transformer#setURIResolver(javax.xml.transform.URIResolver)
203      */
204     @Override
205     public void setURIResolver(URIResolver resolver) {
206         this.resolver = resolver;
207     }
208 
209     /**
210      * @see Transformer#getOutputProperties()
211      */
212     @Override
213     public Properties getOutputProperties() {
214         return outputs;
215     }
216 
217     /**
218      * @see Transformer#setOutputProperties(java.util.Properties)
219      */
220     @Override
221     public void setOutputProperties(Properties outputs) throws IllegalArgumentException {
222         this.outputs = outputs;
223     }
224 
225     /**
226      * @see Transformer#getOutputProperty(java.lang.String)
227      */
228     @Override
229     public String getOutputProperty(String name) throws IllegalArgumentException {
230         return outputs.getProperty(name);
231     }
232 
233     /**
234      * @see Transformer#setOutputProperty(java.lang.String, java.lang.String)
235      */
236     @Override
237     public void setOutputProperty(String name, String value) throws IllegalArgumentException {
238         outputs.setProperty(name, value);
239     }
240 
241     /**
242      * @see Transformer#clearParameters()
243      */
244     @Override
245     public void clearParameters() {
246         params.clear();
247     }
248 
249     /**
250      * @see Transformer#getParameter(java.lang.String)
251      */
252     @Override
253     public Object getParameter(String name) {
254         return params.get(name);
255     }
256 
257     /**
258      * @see Transformer#setParameter(java.lang.String, java.lang.Object)
259      */
260     @Override
261     public void setParameter(String name, Object value) {
262         params.put(name, value);
263     }
264 
265     /**
266      * @param developmentMode the developmentMode to set
267      */
268     public static void setDevelopmentMode(boolean developmentMode) {
269         TransformingSAXEventProvider.developmentMode = developmentMode;
270     }
271 
272     /**
273      * In development mode the style sheet is checked for modifications before use and if so, it is recompiled.
274      * @return the developmentMode
275      */
276     public static boolean isDevelopmentMode() {
277         return developmentMode;
278     }
279 
280     /**
281      * A simple struct to link modification times to Templates objects
282      */
283     private static class TemplateInfo {
284         /**
285          * Simple ctor
286          */
287         public TemplateInfo(Templates templates, long modtime) {
288             this.templates = templates;
289             this.modtime = modtime;
290         }
291 
292         /**
293          * The transformer
294          */
295         Templates getTemplates() {
296             return templates;
297         }
298 
299         /**
300          * The modtime of the xsl file
301          */
302         long getModtime() {
303             return modtime;
304         }
305 
306         private Templates templates;
307         private long modtime;
308     }
309 
310     /**
311      * In development mode the style sheet is checked for modifications before use and if so, it is recompiled.
312      */
313     private static boolean developmentMode;
314 
315     /**
316      * The remembered ErrorListener because the transformer has not been created
317      */
318     private ErrorListener errors;
319 
320     /**
321      * The remembered URIResolver because the transformer has not been created
322      */
323     private URIResolver resolver;
324 
325     /**
326      * The remembered OutputProperties because the transformer has not been
327      * created
328      */
329     private Properties outputs;
330 
331     /**
332      * The remembered Parameters because the transformer has not been created
333      */
334     private Map<String, Object> params;
335 
336     /**
337      * The XSL stylesheet
338      */
339     private URI xsluri;
340 
341     /**
342      * The XML input source
343      */
344     private SAXEventProvider xmlsep;
345 
346     /**
347      * How we get the transformer objects
348      */
349     private TransformerFactory transfact;
350 
351     /**
352      * A cache of transformers
353      */
354     private static Map<URI, TemplateInfo> txers = new HashMap<URI, TemplateInfo>();
355 
356     /**
357      * The log stream
358      */
359     private static final Logger log = LoggerFactory.getLogger(TransformingSAXEventProvider.class);
360 }
361