KickJava   Java API By Example, From Geeks To Geeks.

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


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.BufferedOutputStream JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.net.URL 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.ServletException JavaDoc;
28 import javax.servlet.http.HttpServletRequest JavaDoc;
29 import javax.servlet.http.HttpServletResponse JavaDoc;
30 import javax.xml.transform.ErrorListener JavaDoc;
31 import javax.xml.transform.OutputKeys JavaDoc;
32 import javax.xml.transform.Result JavaDoc;
33 import javax.xml.transform.Source JavaDoc;
34 import javax.xml.transform.Templates JavaDoc;
35 import javax.xml.transform.Transformer JavaDoc;
36 import javax.xml.transform.TransformerConfigurationException JavaDoc;
37 import javax.xml.transform.TransformerException JavaDoc;
38 import javax.xml.transform.TransformerFactory JavaDoc;
39 import javax.xml.transform.URIResolver JavaDoc;
40 import javax.xml.transform.dom.DOMSource JavaDoc;
41 import javax.xml.transform.stream.StreamResult JavaDoc;
42 import javax.xml.transform.stream.StreamSource JavaDoc;
43
44 import org.w3c.dom.Node JavaDoc;
45
46 import org.springframework.context.ApplicationContextException;
47 import org.springframework.core.io.Resource;
48 import org.springframework.util.xml.SimpleTransformErrorListener;
49 import org.springframework.web.servlet.view.AbstractView;
50 import org.springframework.web.util.NestedServletException;
51
52 /**
53  * Convenient superclass for views rendered using an XSLT stylesheet.
54  *
55  * <p>Subclasses typically must provide the {@link Source} to transform
56  * by overriding {@link #createXsltSource}. Subclasses do not need to
57  * concern themselves with XSLT other than providing a valid stylesheet location.
58  *
59  * <p>Properties:
60  * <ul>
61  * <li>{@link #setStylesheetLocation(org.springframework.core.io.Resource) stylesheetLocation}:
62  * a {@link Resource} pointing to the XSLT stylesheet
63  * <li>{@link #setRoot(String) root}: the name of the root element; defaults to {@link #DEFAULT_ROOT "DocRoot"}
64  * <li>{@link #setUriResolver(javax.xml.transform.URIResolver) uriResolver}:
65  * the {@link URIResolver} to be used in the transform
66  * <li>{@link #setErrorListener(javax.xml.transform.ErrorListener) errorListener} (optional):
67  * the {@link ErrorListener} implementation instance for custom handling of warnings and errors during TransformerFactory operations
68  * <li>{@link #setIndent(boolean) indent} (optional): whether additional whitespace
69  * may be added when outputting the result; defaults to <code>true</code>
70  * <li>{@link #setCache(boolean) cache} (optional): are templates to be cached; debug setting only; defaults to <code>true</code>
71  * </ul>
72  *
73  * <p>Note that setting {@link #setCache(boolean) "cache"} to <code>false</code>
74  * will cause the template objects to be reloaded for each rendering. This is
75  * useful during development, but will seriously affect performance in production
76  * and is not thread-safe.
77  *
78  * @author Rod Johnson
79  * @author Darren Davison
80  * @author Juergen Hoeller
81  */

82 public abstract class AbstractXsltView extends AbstractView {
83
84     /**
85      * The default document root name.
86      */

87     public static final String JavaDoc DEFAULT_ROOT = "DocRoot";
88
89
90     private Resource stylesheetLocation;
91
92     private String JavaDoc root = DEFAULT_ROOT;
93
94     private boolean useSingleModelNameAsRoot = true;
95
96     private URIResolver JavaDoc uriResolver;
97
98     private ErrorListener JavaDoc errorListener = new SimpleTransformErrorListener(logger);
99
100     private boolean indent = true;
101
102     private Properties JavaDoc outputProperties;
103
104     private boolean cache = true;
105
106     private TransformerFactory JavaDoc transformerFactory;
107
108     private Templates JavaDoc templates;
109
110
111     /**
112      * Set the location of the XSLT stylesheet.
113      * <p>If the {@link TransformerFactory} used by this instance has already
114      * been initialized then invoking this setter will result in the
115      * {@link TransformerFactory#newTemplates(javax.xml.transform.Source) attendant templates}
116      * being re-cached.
117      * @param stylesheetLocation the location of the XSLT stylesheet
118      * @see org.springframework.context.ApplicationContext#getResource
119      */

120     public void setStylesheetLocation(Resource stylesheetLocation) {
121         this.stylesheetLocation = stylesheetLocation;
122         // Re-cache templates if transformer factory already initialized.
123
if (this.transformerFactory != null) {
124             cacheTemplates();
125         }
126     }
127
128     /**
129      * The document root element name. Default is {@link #DEFAULT_ROOT "DocRoot"}.
130      * <p>Only used if we're not passed a single {@link Node} as the model.
131      * @param root the document root element name
132      * @see #DEFAULT_ROOT
133      */

134     public void setRoot(String JavaDoc root) {
135         this.root = root;
136     }
137
138     /**
139      * Set whether to use the name of a given single model object as the
140      * document root element name.
141      * <p>Default is <code>true</code> : If you pass in a model with a single object
142      * named "myElement", then the document root will be named "myElement"
143      * as well. Set this flag to <code>false</code> if you want to pass in a single
144      * model object while still using the root element name configured
145      * through the {@link #setRoot(String) "root" property}.
146      * @param useSingleModelNameAsRoot <code>true</code> if the name of a given single
147      * model object is to be used as the document root element name
148      * @see #setRoot
149      */

150     public void setUseSingleModelNameAsRoot(boolean useSingleModelNameAsRoot) {
151         this.useSingleModelNameAsRoot = useSingleModelNameAsRoot;
152     }
153
154     /**
155      * Set the URIResolver used in the transform.
156      * <p>The URIResolver handles calls to the XSLT document() function.
157      * @param uriResolver URIResolver to set. No URIResolver
158      * will be set if this is <code>null</code> (this is the default).
159      */

160     public void setUriResolver(URIResolver JavaDoc uriResolver) {
161         this.uriResolver = uriResolver;
162     }
163
164     /**
165      * Set an implementation of the {@link javax.xml.transform.ErrorListener}
166      * interface for custom handling of transformation errors and warnings.
167      * <p>If not set, a default
168      * {@link org.springframework.util.xml.SimpleTransformErrorListener} is
169      * used that simply logs warnings using the logger instance of the view class,
170      * and rethrows errors to discontinue the XML transformation.
171      * @see org.springframework.util.xml.SimpleTransformErrorListener
172      */

173     public void setErrorListener(ErrorListener JavaDoc errorListener) {
174         this.errorListener = errorListener;
175     }
176
177     /**
178      * Set whether the XSLT transformer may add additional whitespace when
179      * outputting the result tree.
180      * <p>Default is <code>true</code> (on); set this to <code>false</code> (off)
181      * to not specify an "indent" key, leaving the choice up to the stylesheet.
182      * @see javax.xml.transform.OutputKeys#INDENT
183      */

184     public void setIndent(boolean indent) {
185         this.indent = indent;
186     }
187
188     /**
189      * Set arbitrary transformer output properties to be applied to the stylesheet.
190      * <p>Any values specified here will override defaults that this view sets
191      * programmatically.
192      * @see javax.xml.transform.Transformer#setOutputProperty
193      */

194     public void setOutputProperties(Properties JavaDoc outputProperties) {
195         this.outputProperties = outputProperties;
196     }
197
198     /**
199      * Set whether to activate the cache. Default is <code>true</code>.
200      */

201     public void setCache(boolean cache) {
202         this.cache = cache;
203     }
204
205
206     /**
207      * Here we load our template, as we need the
208      * {@link org.springframework.context.ApplicationContext} to do it.
209      */

210     protected final void initApplicationContext() throws ApplicationContextException {
211         this.transformerFactory = TransformerFactory.newInstance();
212         this.transformerFactory.setErrorListener(this.errorListener);
213         if (this.uriResolver != null) {
214             if (logger.isInfoEnabled()) {
215                 logger.info("Using custom URIResolver [" + this.uriResolver + "] in XSLT view with name '" +
216                         getBeanName() + "'");
217             }
218             this.transformerFactory.setURIResolver(this.uriResolver);
219         }
220         if (logger.isDebugEnabled()) {
221             logger.debug("URL in view is " + this.stylesheetLocation);
222         }
223         cacheTemplates();
224     }
225
226     private synchronized void cacheTemplates() throws ApplicationContextException {
227         if (this.stylesheetLocation != null) {
228             try {
229                 this.templates = this.transformerFactory.newTemplates(getStylesheetSource(this.stylesheetLocation));
230                 if (logger.isDebugEnabled()) {
231                     logger.debug("Loaded templates [" + this.templates + "] in XSLT view '" + getBeanName() + "'");
232                 }
233             }
234             catch (TransformerConfigurationException JavaDoc ex) {
235                 throw new ApplicationContextException("Can't load stylesheet from " + this.stylesheetLocation +
236                         " in XSLT view '" + getBeanName() + "'", ex);
237             }
238         }
239     }
240
241     /**
242      * Load the stylesheet.
243      * @param stylesheetLocation the stylesheet resource to be loaded
244      * @return the stylesheet source
245      * @throws ApplicationContextException if the stylesheet resource could not be loaded
246      */

247     protected Source JavaDoc getStylesheetSource(Resource stylesheetLocation) throws ApplicationContextException {
248         if (logger.isDebugEnabled()) {
249             logger.debug("Loading XSLT stylesheet from " + stylesheetLocation);
250         }
251         try {
252             URL JavaDoc url = stylesheetLocation.getURL();
253             String JavaDoc urlPath = url.toString();
254             String JavaDoc systemId = urlPath.substring(0, urlPath.lastIndexOf('/') + 1);
255             return new StreamSource JavaDoc(url.openStream(), systemId);
256         }
257         catch (IOException JavaDoc ex) {
258             throw new ApplicationContextException("Can't load XSLT stylesheet from " + stylesheetLocation, ex);
259         }
260     }
261
262
263     protected final void renderMergedOutputModel(
264             Map JavaDoc model, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws Exception JavaDoc {
265
266         if (!this.cache) {
267             logger.warn("DEBUG SETTING: NOT THREADSAFE AND WILL IMPAIR PERFORMANCE: template will be refreshed");
268             cacheTemplates();
269         }
270
271         if (this.templates == null) {
272             if (this.transformerFactory == null) {
273                 throw new ServletException JavaDoc("XLST view is incorrectly configured. Templates AND TransformerFactory are null");
274             }
275
276             logger.warn("XSLT view is not configured: will copy XML input");
277             response.setContentType("text/xml; charset=ISO-8859-1");
278         }
279         else {
280             // normal case
281
response.setContentType(getContentType());
282         }
283
284         Source JavaDoc source = null;
285         String JavaDoc docRoot = null;
286
287         // Value of a single element in the map, if there is one.
288
Object JavaDoc singleModel = null;
289
290         if (this.useSingleModelNameAsRoot && model.size() == 1) {
291             docRoot = (String JavaDoc) model.keySet().iterator().next();
292             if (logger.isDebugEnabled()) {
293                 logger.debug("Single model object received, key [" + docRoot + "] will be used as root tag");
294             }
295             singleModel = model.get(docRoot);
296         }
297
298         // Handle special case when we have a single node.
299
if (singleModel instanceof Node JavaDoc || singleModel instanceof Source JavaDoc) {
300             // Don't domify if the model is already an XML node/source.
301
// We don't need to worry about model name, either:
302
// we leave the Node alone.
303
logger.debug("No need to domify: was passed an XML Node or Source");
304             source = (singleModel instanceof Node JavaDoc ? new DOMSource JavaDoc((Node JavaDoc) singleModel) : (Source JavaDoc) singleModel);
305         }
306         else {
307             // docRoot local variable takes precedence
308
source = createXsltSource(model, (docRoot != null ? docRoot : this.root), request, response);
309         }
310
311         doTransform(model, source, request, response);
312     }
313
314     /**
315      * Return the XML {@link Source} to transform.
316      * @param model the model Map
317      * @param root name for root element. This can be supplied as a bean property
318      * to concrete subclasses within the view definition file, but will be overridden
319      * in the case of a single object in the model map to be the key for that object.
320      * If no root property is specified and multiple model objects exist, a default
321      * root tag name will be supplied.
322      * @param request HTTP request. Subclasses won't normally use this, as
323      * request processing should have been complete. However, we might want to
324      * create a RequestContext to expose as part of the model.
325      * @param response HTTP response. Subclasses won't normally use this,
326      * however there may sometimes be a need to set cookies.
327      * @return the XSLT Source to transform
328      * @throws Exception if an error occurs
329      */

330     protected Source JavaDoc createXsltSource(
331             Map JavaDoc model, String JavaDoc root, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
332             throws Exception JavaDoc {
333
334         return null;
335     }
336
337     /**
338      * Perform the actual transformation, writing to the HTTP response.
339      * <p>The default implementation delegates to the
340      * {@link #doTransform(javax.xml.transform.Source, java.util.Map, javax.xml.transform.Result, String)}
341      * method, building a StreamResult for the ServletResponse OutputStream
342      * or for the ServletResponse Writer (according to {@link #useWriter()}).
343      * @param model the model Map
344      * @param source the Source to transform
345      * @param request current HTTP request
346      * @param response current HTTP response
347      * @throws Exception if an error occurs
348      * @see javax.xml.transform.stream.StreamResult
349      * @see javax.servlet.ServletResponse#getOutputStream()
350      * @see javax.servlet.ServletResponse#getWriter()
351      * @see #useWriter()
352      */

353     protected void doTransform(
354             Map JavaDoc model, Source JavaDoc source, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
355             throws Exception JavaDoc {
356
357         Map JavaDoc parameters = getParameters(request);
358         Result JavaDoc result = (useWriter() ?
359                 new StreamResult JavaDoc(response.getWriter()) :
360                 new StreamResult JavaDoc(new BufferedOutputStream JavaDoc(response.getOutputStream())));
361         String JavaDoc encoding = response.getCharacterEncoding();
362         doTransform(source, parameters, result, encoding);
363     }
364
365     /**
366      * Perform the actual transformation, writing to the given result.
367      * @param source the Source to transform
368      * @param parameters a Map of parameters to be applied to the stylesheet
369      * @param result the result to write to
370      * @param encoding the preferred character encoding that the underlying Transformer should use
371      * @throws Exception if an error occurs
372      */

373     protected void doTransform(Source JavaDoc source, Map JavaDoc parameters, Result JavaDoc result, String JavaDoc encoding)
374             throws Exception JavaDoc {
375
376         try {
377             Transformer JavaDoc trans = (this.templates != null) ?
378                     this.templates.newTransformer() : // we have a stylesheet
379
this.transformerFactory.newTransformer(); // just a copy
380

381             // Explicitly apply URIResolver to every created Transformer.
382
if (this.uriResolver != null) {
383                 trans.setURIResolver(this.uriResolver);
384             }
385
386             // Apply any subclass supplied parameters to the transformer.
387
if (parameters != null) {
388                 for (Iterator JavaDoc it = parameters.entrySet().iterator(); it.hasNext();) {
389                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
390                     trans.setParameter(entry.getKey().toString(), entry.getValue());
391                 }
392                 if (logger.isDebugEnabled()) {
393                     logger.debug("Added parameters [" + parameters + "] to transformer object");
394                 }
395             }
396
397             // Specify default output properties.
398
trans.setOutputProperty(OutputKeys.ENCODING, encoding);
399             if (this.indent) {
400                 TransformerUtils.enableIndenting(trans);
401             }
402
403             // Apply any arbitrary output properties, if specified.
404
if (this.outputProperties != null) {
405                 Enumeration JavaDoc propsEnum = this.outputProperties.propertyNames();
406                 while (propsEnum.hasMoreElements()) {
407                     String JavaDoc propName = (String JavaDoc) propsEnum.nextElement();
408                     trans.setOutputProperty(propName, this.outputProperties.getProperty(propName));
409                 }
410             }
411
412             // Perform the actual XSLT transformation.
413
trans.transform(source, result);
414             if (logger.isDebugEnabled()) {
415                 logger.debug("XSLT transformed with stylesheet [" + this.stylesheetLocation + "]");
416             }
417         }
418         catch (TransformerConfigurationException JavaDoc ex) {
419             throw new NestedServletException("Couldn't create XSLT transformer for stylesheet [" +
420                     this.stylesheetLocation + "] in XSLT view with name [" + getBeanName() + "]", ex);
421         }
422         catch (TransformerException JavaDoc ex) {
423             throw new NestedServletException("Couldn't perform transform with stylesheet [" +
424                     this.stylesheetLocation + "] in XSLT view with name [" + getBeanName() + "]", ex);
425         }
426     }
427
428     /**
429      * Return a Map of transformer parameters to be applied to the stylesheet.
430      * <p>Subclasses can override this method in order to apply one or more
431      * parameters to the transformation process.
432      * <p>The default implementation delegates to the simple
433      * {@link #getParameters()} variant.
434      * @param request current HTTP request
435      * @return a Map of parameters to apply to the transformation process
436      * @see #getParameters()
437      * @see javax.xml.transform.Transformer#setParameter
438      */

439     protected Map JavaDoc getParameters(HttpServletRequest JavaDoc request) {
440         return getParameters();
441     }
442
443     /**
444      * Return a Map of transformer parameters to be applied to the stylesheet.
445      * <p>Subclasses can override this method in order to apply one or more
446      * parameters to the transformation process.
447      * <p>The default implementation simply returns <code>null</code>.
448      * @return a Map of parameters to apply to the transformation process
449      * @see #getParameters(HttpServletRequest)
450      * @see javax.xml.transform.Transformer#setParameter
451      */

452     protected Map JavaDoc getParameters() {
453         return null;
454     }
455
456     /**
457      * Return whether to use a <code>java.io.Writer</code> to write text content
458      * to the HTTP response. Else, a <code>java.io.OutputStream</code> will be used,
459      * to write binary content to the response.
460      * <p>The default implementation returns <code>false</code>, indicating a
461      * a <code>java.io.OutputStream</code>.
462      * @see javax.servlet.ServletResponse#getWriter()
463      * @see javax.servlet.ServletResponse#getOutputStream()
464      */

465     protected boolean useWriter() {
466         return false;
467     }
468
469 }
470
Popular Tags