1 /*
2 * Scope: a generic MVC framework.
3 * Copyright (c) 2000-2002, The Scope team
4 * All rights reserved.
5 *
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * Neither the name "Scope" nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 *
36 * $Id: AbstractXSLPage.java,v 1.9 2002/09/05 15:41:45 ludovicc Exp $
37 */
38 package org.scopemvc.view.servlet.xml;
39
40
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.io.Writer;
47 import java.net.URL;
48 import java.util.HashMap;
49 import java.util.Properties;
50 import javax.xml.transform.OutputKeys;
51 import javax.xml.transform.Result;
52 import javax.xml.transform.Templates;
53 import javax.xml.transform.Transformer;
54 import javax.xml.transform.TransformerConfigurationException;
55 import javax.xml.transform.TransformerFactory;
56 import javax.xml.transform.sax.SAXResult;
57 import javax.xml.transform.sax.SAXSource;
58 import javax.xml.transform.sax.SAXTransformerFactory;
59 import javax.xml.transform.sax.TransformerHandler;
60 import javax.xml.transform.stream.StreamResult;
61 import javax.xml.transform.stream.StreamSource;
62 import org.apache.commons.logging.Log;
63 import org.apache.commons.logging.LogFactory;
64 import org.scopemvc.util.Debug;
65 import org.scopemvc.util.ScopeConfig;
66 import org.xml.sax.ContentHandler;
67 import org.scopemvc.view.servlet.Page;
68
69 /***
70 * <P>
71 *
72 * A ServletView that references an XSLT URI used to transform an XML
73 * representation of the View's bound model objects. </P> <P>
74 *
75 * The XSLT is assumed to describe the entire view, not just a part of the
76 * overall page, even when this view is a subview or a parent of subviews. </P>
77 * <P>
78 *
79 * Model objects are turned to a SAX source in a concrete subclass, which is
80 * transformed with the XSLT to a SAX stream that gets fed to a SAX
81 * ContentHandler (eg an HTML serializer) that writes to an OutputStream (eg the
82 * HTTPResponse's output stream). </P> <P>
83 *
84 * This abstract base class does some generic XSLT handling, including caching
85 * compiled stylesheets. The concrete Scope impl is in {@link XSLPage}. </P>
86 *
87 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A>
88 * @created 05 September 2002
89 * @version $Revision: 1.9 $ $Date: 2002/09/05 15:41:45 $
90 */
91 public abstract class AbstractXSLPage extends Page {
92
93 /***
94 * A cache for compiled stylesheets.
95 */
96 protected static HashMap templateCache = new HashMap();
97
98 private final static Log LOG = LogFactory.getLog(AbstractXSLPage.class);
99
100 /***
101 * Base URI to reference XSL URIs from.
102 */
103 private static String systemID;
104
105 /***
106 * Do we want to cache the stylesheets? Yes for production, no for
107 * development because you want to see changes to the XSLTs as soon as you
108 * make them rather than after restarting the web application.
109 */
110 protected boolean shouldCacheTemplates;
111
112 /***
113 * URI of the XSLT for this View relative to the {@link #setSystemID}. The
114 * XSLT is assumed to be for the whole page, even if this view is a subview
115 * of a parent.
116 */
117 protected String xslURI;
118
119 /***
120 * Filesystem directory to dump all XML before transforming. Useful for
121 * debug and development but disable in production!
122 */
123 protected String debugXMLDirectory;
124
125
126 /***
127 * Specify the XSLT to use when showing this View. The XSLT is assumed to be
128 * for the whole page, even if this view is a subview or a parent of some
129 * subviews.
130 *
131 * @param inXslURI a URI to the XSLT relative to what has been set in {@link
132 * #setSystemID}
133 * @param inViewID TODO: Describe the Parameter
134 */
135 public AbstractXSLPage(String inViewID, String inXslURI) {
136 super(inViewID);
137
138 if (LOG.isDebugEnabled()) {
139 LOG.debug("XslURI: " + inXslURI, new Throwable());
140 }
141 // this is for information only
142 setXslURI(inXslURI);
143
144 if (ScopeConfig.getString("org.scopemvc.view.servlet.xml.AbstractXSLPage.shouldCacheTemplates").equals("1")) {
145 shouldCacheTemplates = true;
146 } else {
147 shouldCacheTemplates = false;
148 }
149
150 debugXMLDirectory = ScopeConfig.getString("org.scopemvc.view.servlet.xml.AbstractXSLPage.debugXMLDirectory");
151 if (debugXMLDirectory.length() < 1) {
152 debugXMLDirectory = null;
153 }
154 }
155
156
157 /***
158 * Where XSLT URIs are referenced relative to. See
159 * javax.xml.transform.sax.TransformerHandler
160 *
161 * @return The systemID value
162 */
163 public static String getSystemID() {
164 if (systemID == null) {
165 return "";
166 }
167 return systemID;
168 }
169
170
171 // --------------------- XML/XSL handling --------------------------
172
173 /***
174 * Where XSLT URIs are referenced relative to. See
175 * javax.xml.transform.sax.TransformerHandler
176 *
177 * @param inSystemID The new systemID value
178 */
179 public static void setSystemID(String inSystemID) {
180 systemID = inSystemID;
181 }
182
183
184 /***
185 * The XSLT that will be shown for this view. (If null then the default
186 * "pass-through" XSL is used.)
187 *
188 * @return The xslURI value
189 */
190 public String getXslURI() {
191 return xslURI;
192 }
193
194
195 /***
196 * Allow subclasses to deliver different content types. Here "text/html"
197 * unless no stylesheet in which case "text/xml".
198 *
199 * @return The contentType value
200 */
201 public String getContentType() {
202 if (getXslURI() == null) {
203 return "text/xml";
204 } else {
205 return "text/html";
206 }
207 }
208
209
210 /***
211 * The XSLT that will be shown for this view. (If null then the default
212 * "pass-through" XSL is used.)
213 *
214 * @param inURI The new xslURI value
215 */
216 public void setXslURI(String inURI) {
217 xslURI = inURI;
218 }
219
220
221 /***
222 * For debug.
223 *
224 * @return TODO: Describe the Return Value
225 */
226 public String toString() {
227 String result = "(" + getClass().getName() + ":" + getID() + "," + getSystemID() + "," + getXslURI();
228 result += ")";
229 return result;
230 }
231
232
233 // /***
234 // * <P>
235 // * Implement this to return a SAX2 ContentHandler that will
236 // * be used to convert from the SAX output of the XSL processor
237 // * into the final stream for sending via the servlet.
238 // * </P>
239 // * <P>
240 // * To alter the generated XML format override this, eg to change
241 // * escaping for characters, elements to be treated as CDATA,
242 // * the doctype, etc. See <code>javax.xml.transform.OutputKeys</code>.
243 // * </P>
244 // */
245 // protected ContentHandler getContentHandler(Writer inWriter) throws Exception {
246 // if (Debug.ON) Debug.assertTrue(inWriter != null);
247 //
248 // if (getXslURI() == null) {
249 // OutputFormat format = new OutputFormat(Method.XML, null, true);
250 // format.setLineWidth(0);
251 // return new XMLSerializer(inWriter, format);
252 // } else {
253 // OutputFormat format = new OutputFormat(Method.HTML, null, true);
254 // format.setLineWidth(0);
255 // return new HTMLSerializer(inWriter, format);
256 // }
257 // }
258
259
260 // /***
261 // * <P>
262 // * Implement this to return a SAX2 ContentHandler that will
263 // * be used to convert from the SAX output of the XSL processor
264 // * into the final stream for sending via the servlet.
265 // * </P>
266 // * <P>
267 // * To alter the generated XML format override this, eg to change
268 // * escaping for characters, elements to be treated as CDATA,
269 // * the doctype, etc. See <code>javax.xml.transform.OutputKeys</code>.
270 // * </P>
271 // */
272 // protected Transformer getSerializer() throws Exception {
273 // TransformerFactory tfactory = TransformerFactory.newInstance();
274 // Transformer serializer = tfactory.newTransformer();
275 // Properties oprops = new Properties();
276 //
277 // if (getXslURI() == null) {
278 // oprops.put(OutputKeys.METHOD, "xml");
279 // oprops.put(OutputKeys.INDENT, "2");
280 // } else {
281 // oprops.put(OutputKeys.METHOD, "html");
282 // oprops.put(OutputKeys.INDENT, "2");
283 // }
284 // serializer.setOutputProperties(oprops);
285 //
286 // return serializer;
287 // }
288
289
290 /***
291 * <P>
292 *
293 * Stream the view by calling {@link #generateXMLDocument} and processing
294 * the result with the XSLT. </P>
295 *
296 * @param inOutputStream Stream the result of the XSLT processing into here.
297 * @throws Exception TODO: Describe the Exception
298 */
299 public void streamView(OutputStream inOutputStream) throws Exception {
300
301 // For debug/development, write XML to file before XSL processing
302 if (debugXMLDirectory != null) {
303 try {
304 File file = new File(debugXMLDirectory + getXslURI() + ".xml");
305 File directory = file.getParentFile();
306 directory.mkdirs();
307 FileOutputStream fileOutputStream = new FileOutputStream(file);
308
309 generateXMLDocument(makeSerializer(fileOutputStream, getXMLOutputProperties()));
310 fileOutputStream.flush();
311 fileOutputStream.close();
312
313 // OutputFormat format = new OutputFormat(Method.XML, null, true);
314 // format.setIndent(2);
315 // format.setLineWidth(0);
316 // format.setPreserveSpace(true);
317 // ContentHandler fileContentHandler =
318 // SerializerFactory.getSerializerFactory(Method.XML).
319 // makeSerializer(fileOutputStream, format).asContentHandler();
320
321 // generateXMLDocument(handler);
322
323 } catch (IOException e) {
324 LOG.warn("streamView: can't write XML to file: ", e);
325 }
326 }
327
328 // Make the XSL processor
329 TransformerHandler transformerHandler = getTransformerHandler(inOutputStream);
330
331 // debug
332 long time0 = System.currentTimeMillis();
333
334 // Transform the XML
335 generateXMLDocument(transformerHandler);
336
337 // debug
338 if (LOG.isInfoEnabled()) {
339 LOG.info("streamView: process XML: time: " + (System.currentTimeMillis() - time0));
340 }
341 }
342
343
344 /***
345 * Make Properties for <code>Transformer.setOutputProperties</code> suitable
346 * for debug XML output.
347 *
348 * @return The xMLOutputProperties value
349 * @see javax.xml.transform.OutputKeys
350 */
351 protected Properties getXMLOutputProperties() {
352 Properties oprops = new Properties();
353 oprops.put(OutputKeys.METHOD, "xml");
354 oprops.put(OutputKeys.INDENT, "2");
355 return oprops;
356 }
357
358
359 /***
360 * Make Properties for <code>Transformer.setOutputProperties</code> suitable
361 * for final HTML output.
362 *
363 * @return The hTMLOutputProperties value
364 * @see javax.xml.transform.OutputKeys
365 */
366 protected Properties getHTMLOutputProperties() {
367 Properties oprops = new Properties();
368 oprops.put(OutputKeys.METHOD, "html");
369 oprops.put(OutputKeys.INDENT, "2");
370 return oprops;
371 }
372
373
374 /***
375 * Thread-safe Templates are cached for reuse to avoid parsing and compiling
376 * stylesheets repeatedly.
377 *
378 * @param inOutputStream TODO: Describe the Parameter
379 * @return The transformerHandler value
380 * @throws Exception TODO: Describe the Exception
381 */
382 protected TransformerHandler getTransformerHandler(OutputStream inOutputStream) throws Exception {
383 // debug
384 long time0 = System.currentTimeMillis();
385
386 // Make a transformer factory...
387 TransformerFactory transformerFactory = TransformerFactory.newInstance();
388 // ... make sure that SAX input and output is supported (true for Xalan)
389 if (!transformerFactory.getFeature(javax.xml.transform.sax.SAXResult.FEATURE)) {
390 throw new UnsupportedOperationException("Can't use AbstractXSLPage with an XSL Processor that can't output to SAX.");
391 }
392 if (!transformerFactory.getFeature(javax.xml.transform.sax.SAXSource.FEATURE)) {
393 throw new UnsupportedOperationException("Can't use AbstractXSLPage with an XSL Processor that can't take SAX input.");
394 }
395 // ... then cast the factory to a SAX factory
396 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) transformerFactory;
397
398 // If no XSLT URI for this view then return a "pass-through" TransformerHandler
399 TransformerHandler transformerHandler = null;
400 if (getXslURI() == null) {
401 transformerHandler = saxTransformerFactory.newTransformerHandler();
402
403 } else {
404 // Got a cached copy of the XSL Template?
405 Object o;
406 synchronized (templateCache) {
407 o = templateCache.get(getXslURI());
408 }
409
410 Templates template = null;
411 if (o != null) {
412 if (Debug.ON) {
413 Debug.assertTrue(o instanceof Templates);
414 }
415 template = (Templates) o;
416
417 } else {
418 // Parse the XSL and get a threadsafe templates object that can drive a TransformerHandler
419 String urlPath = getSystemID() + getXslURI();
420 URL url = new URL(urlPath);
421 if (LOG.isInfoEnabled()) {
422 LOG.info("XSL URL: " + url);
423 }
424 InputStream stream = url.openStream();
425 int fileIndex = urlPath.lastIndexOf('/') + 1;
426 String baseURI = "";
427 if (fileIndex >= 0) {
428 baseURI = urlPath.substring(0, fileIndex);
429 }
430 if (LOG.isDebugEnabled()) {
431 LOG.debug("getTransformerHandler: baseURI: " + baseURI + "urlPath: " + urlPath);
432 }
433 StreamSource source = new StreamSource(stream, baseURI);
434 // set system id for xsl includes
435 template = saxTransformerFactory.newTemplates(source);
436
437 if (shouldCacheTemplates) {
438 synchronized (templateCache) {
439 templateCache.put(getXslURI(), template);
440 }
441 }
442 }
443
444 if (LOG.isInfoEnabled()) {
445 LOG.info("getTransformerHandler: get compiled XSL: time: " + (System.currentTimeMillis() - time0));
446 }
447
448 // Now get a SAX handler that will take SAX input, transform using the template, and produce SAX output
449 if (Debug.ON) {
450 Debug.assertTrue(template != null, "null template");
451 }
452 transformerHandler = saxTransformerFactory.newTransformerHandler(template);
453 }
454
455 // Make from the transformer go through the final content handler...
456 if (Debug.ON) {
457 Debug.assertTrue(inOutputStream != null, "null output stream");
458 }
459 ContentHandler contentHandler;
460 if (getXslURI() == null) {
461 contentHandler = makeSerializer(inOutputStream, getXMLOutputProperties());
462 } else {
463 contentHandler = makeSerializer(inOutputStream, getHTMLOutputProperties());
464 }
465
466 Result result = new SAXResult(contentHandler);
467 transformerHandler.setResult(result);
468
469 return transformerHandler;
470 }
471
472
473 /***
474 * Make a null transformer to take SAX input and stream it to an
475 * OutputStream using the passed OutputProperties.
476 *
477 * @param inStream TODO: Describe the Parameter
478 * @param inOutputProperties TODO: Describe the Parameter
479 * @return ContentHandler around a null Transformer
480 * @see #getXMLOutputProperties()
481 * @see #getHTMLOutputProperties()
482 */
483 protected ContentHandler makeSerializer(OutputStream inStream, Properties inOutputProperties) {
484 try {
485 TransformerFactory tfactory = TransformerFactory.newInstance();
486 if (tfactory.getFeature(SAXSource.FEATURE)) {
487 SAXTransformerFactory stfactory = ((SAXTransformerFactory) tfactory);
488 TransformerHandler handler = stfactory.newTransformerHandler();
489
490 Transformer serializer = handler.getTransformer();
491 serializer.setOutputProperties(inOutputProperties);
492
493 Result result = new StreamResult(inStream);
494 handler.setResult(result);
495 return handler;
496 } else {
497 LOG.fatal("TransformerFactory doesn't support SAXSource");
498 throw new UnsupportedOperationException("TransformerFactory doesn't support SAXSource");
499 }
500 } catch (TransformerConfigurationException e) {
501 LOG.fatal("TransformerConfigurationException", e);
502 throw new UnsupportedOperationException("TransformerConfigurationException: " + e);
503 }
504 }
505
506
507 /***
508 * Override to implement model to SAX conversion into the passed
509 * ContentHandler.
510 *
511 * @param inContentHandler TODO: Describe the Parameter
512 * @throws Exception TODO: Describe the Exception
513 */
514 protected abstract void generateXMLDocument(ContentHandler inContentHandler)
515 throws Exception;
516 }
This page was automatically generated by Maven