KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > servlet > view > xslt > XsltView


1 /*
2  * Copyright 2002-2006 the original author or authors.
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.springframework.web.servlet.view.xslt;
18
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.Reader JavaDoc;
22 import java.util.Enumeration JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Properties JavaDoc;
26
27 import javax.servlet.http.HttpServletRequest JavaDoc;
28 import javax.servlet.http.HttpServletResponse JavaDoc;
29 import javax.xml.transform.ErrorListener JavaDoc;
30 import javax.xml.transform.OutputKeys JavaDoc;
31 import javax.xml.transform.Result JavaDoc;
32 import javax.xml.transform.Source JavaDoc;
33 import javax.xml.transform.Templates JavaDoc;
34 import javax.xml.transform.Transformer JavaDoc;
35 import javax.xml.transform.TransformerConfigurationException JavaDoc;
36 import javax.xml.transform.TransformerFactory JavaDoc;
37 import javax.xml.transform.URIResolver JavaDoc;
38 import javax.xml.transform.dom.DOMSource JavaDoc;
39 import javax.xml.transform.stream.StreamResult JavaDoc;
40 import javax.xml.transform.stream.StreamSource JavaDoc;
41
42 import org.w3c.dom.Document JavaDoc;
43 import org.w3c.dom.Node JavaDoc;
44
45 import org.springframework.beans.BeansException;
46 import org.springframework.context.ApplicationContextException;
47 import org.springframework.core.io.Resource;
48 import org.springframework.util.Assert;
49 import org.springframework.util.CollectionUtils;
50 import org.springframework.util.ObjectUtils;
51 import org.springframework.util.xml.SimpleTransformErrorListener;
52 import org.springframework.web.servlet.view.AbstractUrlBasedView;
53
54 /**
55  * XSLT-driven View that allows for response context to be rendered as the
56  * result of an XSLT transformation.
57  *
58  * <p>The XSLT Source object is supplied as a parameter in the model and then
59  * {@link #locateSource detected} during response rendering. Users can either specify
60  * a specific entry in the model via the {@link #setSourceKey sourceKey} property or
61  * have Spring locate the Source object. This class also provides basic conversion
62  * of objects into Source implementations. See {@link #getSourceTypes() here}
63  * for more details.
64  *
65  * <p>All model parameters are passed to the XSLT Transformer as parameters.
66  * In addition the user can configure {@link #setOutputProperties output properties}
67  * to be passed to the Transformer.
68  *
69  * @author Rob Harrop
70  * @since 2.0
71  */

72 public class XsltView extends AbstractUrlBasedView {
73
74     private TransformerFactory JavaDoc transformerFactory = TransformerFactory.newInstance();
75
76     private ErrorListener JavaDoc errorListener = new SimpleTransformErrorListener(logger);
77
78     private URIResolver JavaDoc uriResolver;
79
80     private boolean cacheTemplates = true;
81
82     private Templates JavaDoc cachedTemplates;
83
84     private String JavaDoc sourceKey;
85
86     private Properties JavaDoc outputProperties;
87
88     private boolean indent = true;
89
90
91     /**
92      * Turns on/off the caching of the XSLT {@link Templates} instance. The default
93      * value is <code>true</code>. Only set this to <code>false</code> in development
94      * as not caching seriously impacts performance.
95      */

96     public void setCacheTemplates(boolean cacheTemplates) {
97         this.cacheTemplates = cacheTemplates;
98     }
99
100     /**
101      * Sets a custom {@link URIResolver} to use for processing the transformation
102      * and loading the
103      * @param uriResolver
104      */

105     public void setUriResolver(URIResolver JavaDoc uriResolver) {
106         this.uriResolver = uriResolver;
107     }
108
109     public void setErrorListener(ErrorListener JavaDoc errorListener) {
110         Assert.notNull(errorListener, "'errorListener' cannot be null.");
111         this.errorListener = errorListener;
112     }
113
114     public void setSourceKey(String JavaDoc sourceKey) {
115         this.sourceKey = sourceKey;
116     }
117
118     public void setOutputProperties(Properties JavaDoc outputProperties) {
119         this.outputProperties = outputProperties;
120     }
121
122     public void setIndent(boolean indent) {
123         this.indent = indent;
124     }
125
126     protected final TransformerFactory JavaDoc getTransformerFactory() {
127         return this.transformerFactory;
128     }
129
130
131     protected void initApplicationContext() throws BeansException {
132         this.getTransformerFactory().setErrorListener(this.errorListener);
133
134         if (this.uriResolver != null) {
135             if (logger.isInfoEnabled()) {
136                 logger.info("Using custom URIResolver '" + this.uriResolver
137                                 + "' in XSLT view with URL '" + getUrl() + "'");
138             }
139             this.getTransformerFactory().setURIResolver(this.uriResolver);
140         }
141
142         if (logger.isDebugEnabled()) {
143             logger.debug("URL in view is '" + getUrl() + "'");
144         }
145
146         if (this.cacheTemplates) {
147             this.cachedTemplates = loadTemplates();
148         }
149     }
150
151     protected void renderMergedOutputModel(Map JavaDoc model, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws Exception JavaDoc {
152         Templates JavaDoc templates = this.cachedTemplates;
153         if (templates == null) {
154             logger.warn("DEBUG SETTING: WILL IMPAIR PERFORMANCE: template will be refreshed");
155             templates = loadTemplates();
156         }
157
158         Transformer JavaDoc transformer = createTransformer(templates);
159         configureTransformer(model, response, transformer);
160         configureResponse(model, response, transformer);
161         Source JavaDoc source = null;
162         try {
163             source = locateSource(model);
164             if(source == null) {
165                 throw new IllegalArgumentException JavaDoc("Unable to locate Source object in model.");
166             }
167             transformer.transform(source, createResult(response));
168         }
169         finally {
170             if (source != null) {
171                 closeSourceIfNecessary(source);
172             }
173         }
174     }
175
176     /**
177      * Creates the XSLT {@link Result} used to render the result of the transformation.
178      * Default implementation creates a {@link StreamResult} wrapping the supplied
179      * {@link HttpServletResponse}.
180      */

181     protected Result JavaDoc createResult(HttpServletResponse JavaDoc response) throws Exception JavaDoc {
182         return new StreamResult JavaDoc(response.getOutputStream());
183     }
184
185     /**
186      * Locates the {@link Source} object in the supplied model, converting objects as required.
187      * Default implementation first attempts to look under the configured {@link #setSourceKey source key},
188      * if any, before attempting to locate an object of {@link #getSourceTypes() supported type}.
189      */

190     protected Source JavaDoc locateSource(Map JavaDoc model) throws Exception JavaDoc {
191         if (this.sourceKey != null) {
192             return convertSource(model.get(this.sourceKey));
193         }
194
195         Object JavaDoc source = CollectionUtils.findValueOfType(model.values(), getSourceTypes());
196         return (source != null ? convertSource(source) : null);
197     }
198
199     /**
200      * Returns the array of {@link Class Classes} that are supported when converting to an
201      * XSLT {@link Source}. Current supports {@link Source}, {@link Document}, {@link Node},
202      * {@link InputStream}, {@link Reader} and {@link Resource}.
203      */

204     protected Class JavaDoc[] getSourceTypes() {
205         return new Class JavaDoc[]{Source JavaDoc.class, Document JavaDoc.class, Node JavaDoc.class, InputStream JavaDoc.class, Reader JavaDoc.class, Resource.class};
206     }
207
208     /**
209      * Converts the supplied {@link Object} into an XSLT {@link Source} if the
210      * {@link Object} type is {@link #getSourceTypes() supported}.
211      *
212      * @throws IllegalArgumentException if the {@link Object} cannot if not of a supported type.
213      */

214     protected Source JavaDoc convertSource(Object JavaDoc source) throws Exception JavaDoc {
215         if (source instanceof Source JavaDoc) {
216             return (Source JavaDoc) source;
217         }
218         else if (source instanceof Document JavaDoc) {
219             return new DOMSource JavaDoc(((Document JavaDoc) source).getDocumentElement());
220         }
221         else if (source instanceof Node JavaDoc) {
222             return new DOMSource JavaDoc((Node JavaDoc) source);
223         }
224         else if (source instanceof InputStream JavaDoc) {
225             return new StreamSource JavaDoc((InputStream JavaDoc) source);
226         }
227         else if (source instanceof Reader JavaDoc) {
228             return new StreamSource JavaDoc((Reader JavaDoc) source);
229         }
230         else if (source instanceof Resource) {
231             return new StreamSource JavaDoc(((Resource) source).getInputStream());
232         }
233         else {
234             throw new IllegalArgumentException JavaDoc("Value '" + source + "' cannot be converted to Source.");
235         }
236     }
237
238     /**
239      * Configures the supplied {@link Transformer} instance. Default implementation copies parameters from
240      * the model into the {@link Transformer} {@link Transformer#setParameter parameter set}.
241      * This implementation also copies the {@link #setOutputProperties output properties}
242      * into the {@link Transformer} {@link Transformer#setOutputProperty output properties}.
243      * Indentation properties are also set by this implementation.
244      *
245      * @see #copyModelParameters(Map, Transformer)
246      * @see #copyOutputProperties(Transformer)
247      * @see #configureIndentation(Transformer)
248      */

249     protected void configureTransformer(Map JavaDoc model, HttpServletResponse JavaDoc response, Transformer JavaDoc transformer) {
250         copyModelParameters(model, transformer);
251         copyOutputProperties(transformer);
252         configureIndentation(transformer);
253     }
254
255     /**
256      * Configures the indentation settings for the supplied {@link Transformer}.
257      * @param transformer the target transformer
258      * @throws IllegalArgumentException if the supplied {@link Transformer} is <code>null</code>
259      * @see TransformerUtils#enableIndenting(javax.xml.transform.Transformer)
260      * @see TransformerUtils#disableIndenting(javax.xml.transform.Transformer)
261      */

262     protected final void configureIndentation(Transformer JavaDoc transformer) {
263         if (this.indent) {
264             TransformerUtils.enableIndenting(transformer);
265         }
266         else {
267             TransformerUtils.disableIndenting(transformer);
268         }
269     }
270
271     /**
272      * Copies the configured output {@link Properties}, if any, into the
273      * {@link Transformer#setOutputProperty output property set} of the supplied
274      * {@link Transformer}.
275      */

276     protected final void copyOutputProperties(Transformer JavaDoc transformer) {
277         if (this.outputProperties != null) {
278             Enumeration JavaDoc en = this.outputProperties.propertyNames();
279             while (en.hasMoreElements()) {
280                 String JavaDoc name = (String JavaDoc) en.nextElement();
281                 transformer.setOutputProperty(name, this.outputProperties.getProperty(name));
282             }
283         }
284     }
285
286     /**
287      * Copies all entries from the supplied Map into the
288      * {@link Transformer#setParameter(String, Object) parameter set}
289      * of the supplied {@link Transformer}.
290      */

291     protected final void copyModelParameters(Map JavaDoc model, Transformer JavaDoc transformer) {
292         copyMapEntriesToTransformerParameters(model, transformer);
293     }
294
295     /**
296      * Configures the supplied {@link HttpServletResponse}. The default implementation of this
297      * method sets the {@link HttpServletResponse#setContentType content type} and
298      * {@link HttpServletResponse#setCharacterEncoding encoding} from properties specified
299      * in the {@link Transformer}.
300      */

301     protected void configureResponse(Map JavaDoc model, HttpServletResponse JavaDoc response, Transformer JavaDoc transformer) {
302         response.setContentType(transformer.getOutputProperty(OutputKeys.MEDIA_TYPE));
303         response.setCharacterEncoding(transformer.getOutputProperty(OutputKeys.ENCODING));
304     }
305
306     /**
307      * Load the {@link Templates} instance for the stylesheet at the configured location.
308      */

309     private Templates JavaDoc loadTemplates() throws ApplicationContextException {
310         Source JavaDoc stylesheetSource = getStylesheetSource();
311         try {
312             Templates JavaDoc templates = getTransformerFactory().newTemplates(stylesheetSource);
313             if (logger.isDebugEnabled()) {
314                 logger.debug("Loading templates '" + templates + "'");
315             }
316             return templates;
317         }
318         catch (TransformerConfigurationException JavaDoc ex) {
319             throw new ApplicationContextException("Can't load stylesheet from '" + getUrl() + "'", ex);
320         }
321         finally {
322             closeSourceIfNecessary(stylesheetSource);
323         }
324     }
325
326     /**
327      * Creates the {@link Transformer} instance used to prefer the XSLT transformation. Default implementation
328      * simply calls {@link Templates#newTransformer()}. Configures the {@link Transformer} with the custom
329      * {@link URIResolver} if specified.
330      */

331     protected Transformer JavaDoc createTransformer(Templates JavaDoc templates) throws TransformerConfigurationException JavaDoc {
332         Transformer JavaDoc transformer = templates.newTransformer();
333         if (this.uriResolver != null) {
334             transformer.setURIResolver(this.uriResolver);
335         }
336         return transformer;
337     }
338
339     /**
340      * Gets the XSLT {@link Source} for the XSLT template under the {@link #setUrl configured URL}.
341      */

342     protected Source JavaDoc getStylesheetSource() {
343         String JavaDoc url = getUrl();
344         if (logger.isDebugEnabled()) {
345             logger.debug("Loading XSLT stylesheet from '" + url + "'");
346         }
347         try {
348             Resource stylesheetResource = getApplicationContext().getResource(url);
349             String JavaDoc systemId = url.substring(0, url.lastIndexOf('/') + 1);
350             return new StreamSource JavaDoc(stylesheetResource.getInputStream(), systemId);
351         }
352         catch (IOException JavaDoc ex) {
353             throw new ApplicationContextException("Can't load XSLT stylesheet from '" + url + "'", ex);
354         }
355     }
356
357     /**
358      * Copies all {@link Map.Entry entries} from the supplied {@link Map} into the
359      * {@link Transformer#setParameter(String, Object) parameter set} of the supplied
360      * {@link Transformer}.
361      */

362     private void copyMapEntriesToTransformerParameters(Map JavaDoc map, Transformer JavaDoc transformer) {
363         for (Iterator JavaDoc iterator = map.entrySet().iterator(); iterator.hasNext();) {
364             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iterator.next();
365             transformer.setParameter(ObjectUtils.nullSafeToString(entry.getKey()), entry.getValue());
366         }
367     }
368
369     /**
370      * Closes the underlying resource managed by the supplied {@link Source} if applicable.
371      * Only works for {@link StreamSource StreamSources}.
372      */

373     private void closeSourceIfNecessary(Source JavaDoc source) {
374         if (source instanceof StreamSource JavaDoc) {
375             StreamSource JavaDoc streamSource = (StreamSource JavaDoc) source;
376
377             if (streamSource.getInputStream() != null) {
378                 try {
379                     streamSource.getInputStream().close();
380                 }
381                 catch (IOException JavaDoc e) {
382                     logger.warn("Unable to close InputStream '" + streamSource.getInputStream() + "'");
383                 }
384             }
385             else if (streamSource.getReader() != null) {
386                 try {
387                     streamSource.getReader().close();
388                 }
389                 catch (IOException JavaDoc e) {
390                     logger.warn("Unable to close Reader '" + streamSource.getReader() + "'");
391                 }
392             }
393         }
394     }
395
396 }
397
Popular Tags