View Javadoc
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