| TransformingSAXEventProvider.java |
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