Coverage report

  %line %branch
org.apache.commons.jelly.tags.xml.TransformTag$TagBodyInputSource
100% 
100% 

 1  
 /*
 2  
  * Copyright 2002,2004 The Apache Software Foundation.
 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  
 package org.apache.commons.jelly.tags.xml;
 17  
 
 18  
 import java.io.File;
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.io.Reader;
 22  
 import java.io.StringReader;
 23  
 import java.io.StringWriter;
 24  
 import java.net.MalformedURLException;
 25  
 import java.net.URL;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 
 29  
 import javax.xml.transform.Result;
 30  
 import javax.xml.transform.Source;
 31  
 import javax.xml.transform.TransformerConfigurationException;
 32  
 import javax.xml.transform.TransformerException;
 33  
 import javax.xml.transform.TransformerFactory;
 34  
 import javax.xml.transform.URIResolver;
 35  
 import javax.xml.transform.sax.SAXResult;
 36  
 import javax.xml.transform.sax.SAXSource;
 37  
 import javax.xml.transform.sax.SAXTransformerFactory;
 38  
 import javax.xml.transform.sax.TransformerHandler;
 39  
 import javax.xml.transform.stream.StreamSource;
 40  
 
 41  
 import org.apache.commons.jelly.JellyContext;
 42  
 import org.apache.commons.jelly.JellyException;
 43  
 import org.apache.commons.jelly.JellyTagException;
 44  
 import org.apache.commons.jelly.MissingAttributeException;
 45  
 import org.apache.commons.jelly.Script;
 46  
 import org.apache.commons.jelly.Tag;
 47  
 import org.apache.commons.jelly.XMLOutput;
 48  
 import org.apache.commons.jelly.impl.ScriptBlock;
 49  
 import org.apache.commons.jelly.impl.StaticTagScript;
 50  
 import org.apache.commons.jelly.impl.TagScript;
 51  
 import org.apache.commons.logging.Log;
 52  
 import org.apache.commons.logging.LogFactory;
 53  
 import org.dom4j.Document;
 54  
 import org.dom4j.io.DocumentResult;
 55  
 import org.dom4j.io.DocumentSource;
 56  
 import org.xml.sax.ContentHandler;
 57  
 import org.xml.sax.DTDHandler;
 58  
 import org.xml.sax.EntityResolver;
 59  
 import org.xml.sax.ErrorHandler;
 60  
 import org.xml.sax.InputSource;
 61  
 import org.xml.sax.SAXException;
 62  
 import org.xml.sax.SAXNotRecognizedException;
 63  
 import org.xml.sax.SAXNotSupportedException;
 64  
 import org.xml.sax.XMLReader;
 65  
 import org.xml.sax.ext.LexicalHandler;
 66  
 import org.xml.sax.helpers.XMLReaderFactory;
 67  
 
 68  
 /** A tag which parses some XML, applies an xslt transform to it
 69  
   * and defines a variable with the transformed Document.
 70  
   * The XML can either be specified as its body or can be passed in via the
 71  
   * xml property which can be a Reader, InputStream, URL or String URI.
 72  
   *
 73  
   * The XSL can be passed in via the
 74  
   * xslt property which can be a Reader, InputStream, URL or String URI.
 75  
   *
 76  
   * @author Robert Leftwich
 77  
   * @version $Revision: 155420 $
 78  
   */
 79  
 public class TransformTag extends ParseTag {
 80  
 
 81  
     /** The Log to which logging calls will be made. */
 82  
     private static final Log log = LogFactory.getLog(TransformTag.class);
 83  
 
 84  
     /** Propert name for lexical handler */
 85  
     private static final String LEXICAL_HANDLER_PROPERTY =
 86  
         "http://xml.org/sax/properties/lexical-handler";
 87  
 
 88  
     /** The xslt to parse, either a String URI, a Reader or InputStream */
 89  
     private Object xslt;
 90  
 
 91  
     /** The xsl transformer factory */
 92  
     private SAXTransformerFactory tf;
 93  
 
 94  
     /** the transformer handler, doing the real work */
 95  
     private TransformerHandler transformerHandler;
 96  
 
 97  
     /**
 98  
      * Constructor for TransformTag.
 99  
      */
 100  
     public TransformTag() {
 101  
         super();
 102  
         this.tf = (SAXTransformerFactory) TransformerFactory.newInstance();
 103  
     }
 104  
 
 105  
     // Tag interface
 106  
     //-------------------------------------------------------------------------
 107  
 
 108  
     /**
 109  
      * Process this tag instance
 110  
      *
 111  
      * @param output The pipeline for xml events
 112  
      * @throws Exception - when required attributes are missing
 113  
      */
 114  
     public void doTag(XMLOutput output) throws MissingAttributeException, JellyTagException {
 115  
 
 116  
         if (null == this.getXslt()) {
 117  
             throw new MissingAttributeException("The xslt attribute cannot be null");
 118  
         }
 119  
 
 120  
         // set a resolver to locate uri
 121  
         this.tf.setURIResolver(createURIResolver());
 122  
 
 123  
         try {
 124  
             this.transformerHandler =
 125  
                 this.tf.newTransformerHandler(class="keyword">this.getObjAsSAXSource(class="keyword">this.getXslt()));
 126  
         }
 127  
         catch (TransformerConfigurationException e) {
 128  
             throw new JellyTagException(e);
 129  
         }
 130  
 
 131  
         // run any nested param tags
 132  
         this.doNestedParamTag(output);
 133  
 
 134  
         try {
 135  
             // get a reader to provide SAX events to transformer
 136  
             XMLReader xmlReader = this.createXMLReader();
 137  
             xmlReader.setContentHandler(this.transformerHandler);
 138  
             xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, this.transformerHandler);
 139  
 
 140  
             // handle result differently, depending on if var is specified
 141  
             String varName = this.getVar();
 142  
             if (null == varName) {
 143  
                 // pass the result of the transform out as SAX events
 144  
                 this.transformerHandler.setResult(class="keyword">this.createSAXResult(output));
 145  
                 xmlReader.parse(this.getXMLInputSource());
 146  
             }
 147  
             else {
 148  
                 // pass the result of the transform out as a document
 149  
                 DocumentResult result = new DocumentResult();
 150  
                 this.transformerHandler.setResult(result);
 151  
                 xmlReader.parse(this.getXMLInputSource());
 152  
 
 153  
                 // output the result as a variable
 154  
                 Document transformedDoc = result.getDocument();
 155  
                 this.context.setVariable(varName, transformedDoc);
 156  
             }
 157  
         }
 158  
         catch (SAXException e) {
 159  
             throw new JellyTagException(e);
 160  
         }
 161  
         catch (IOException e) {
 162  
             throw new JellyTagException(e);
 163  
         }
 164  
 
 165  
     }
 166  
 
 167  
     // Properties
 168  
     //-------------------------------------------------------------------------
 169  
 
 170  
     /**
 171  
      * Gets the source of the XSL which is either a String URI, Reader or
 172  
      * InputStream
 173  
      *
 174  
      * @returns xslt    The source of the xslt
 175  
      */
 176  
     public Object getXslt() {
 177  
         return this.xslt;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Sets the source of the XSL which is either a String URI, Reader or
 182  
      * InputStream
 183  
      *
 184  
      * @param xslt    The source of the xslt
 185  
      */
 186  
     public void setXslt(Object xslt) {
 187  
         this.xslt = xslt;
 188  
     }
 189  
 
 190  
     public void setParameterValue(String name, Object value) {
 191  
         this.transformerHandler.getTransformer().setParameter(name, value);
 192  
     }
 193  
 
 194  
     // Implementation methods
 195  
     //-------------------------------------------------------------------------
 196  
 
 197  
     /**
 198  
      * Creates a new URI Resolver so that URIs inside the XSLT document can be
 199  
      * resolved using the JellyContext
 200  
      *
 201  
      * @return a URI Resolver for the JellyContext
 202  
      */
 203  
     protected URIResolver createURIResolver() {
 204  
         return new URIResolver() {
 205  
             public Source resolve(String href, String base)
 206  
                 throws TransformerException {
 207  
 
 208  
                 if (log.isDebugEnabled() ) {
 209  
                     log.debug( "base: " + base + " href: " + href );
 210  
                 }
 211  
 
 212  
                 // pass if we don't have a systemId
 213  
                 if (null == href)
 214  
                     return null;
 215  
 
 216  
                 // @todo
 217  
                 // #### this is a pretty simplistic implementation.
 218  
                 // #### we should really handle this better such that if
 219  
                 // #### base is specified as an absolute URL
 220  
                 // #### we trim the end off it and append href
 221  
                 return new StreamSource(context.getResourceAsStream(href));
 222  
             }
 223  
         };
 224  
     }
 225  
 
 226  
     /**
 227  
      * Factory method to create a new SAXResult for the given
 228  
      * XMLOutput so that the output of an XSLT transform will go
 229  
      * directly into the XMLOutput that we are given.
 230  
      *
 231  
      * @param output The destination of the transform output
 232  
      * @return A SAXResult for the transfrom output
 233  
      */
 234  
     protected Result createSAXResult(XMLOutput output) {
 235  
         SAXResult result = new SAXResult(output);
 236  
         result.setLexicalHandler(output);
 237  
         return result;
 238  
     }
 239  
 
 240  
     /**
 241  
      * Factory method to create a new XMLReader for this tag
 242  
      * so that the input of the XSLT transform comes from
 243  
      * either the xml var, the nested tag or the tag body.
 244  
      *
 245  
      * @return XMLReader for the transform input
 246  
      * @throws SAXException
 247  
      *             If the value of the "org.xml.sax.driver" system property
 248  
      *             is null, or if the class cannot be loaded and instantiated.
 249  
      */
 250  
     protected XMLReader createXMLReader() throws SAXException {
 251  
         XMLReader xmlReader = null;
 252  
         Object xmlReaderSourceObj = this.getXml();
 253  
         // if no xml source specified then get from body
 254  
         // otherwise convert it to a SAX source
 255  
         if (null == xmlReaderSourceObj) {
 256  
             xmlReader = new TagBodyXMLReader(this);
 257  
         }
 258  
         else {
 259  
             xmlReader = XMLReaderFactory.createXMLReader();
 260  
         }
 261  
 
 262  
         return xmlReader;
 263  
     }
 264  
 
 265  
     /**
 266  
      * Helper method to get the appropriate xml input source
 267  
      * so that the input of the XSLT transform comes from
 268  
      * either the xml var, the nested tag or the tag body.
 269  
      *
 270  
      * @return InputSource for the transform input
 271  
      */
 272  
     protected InputSource getXMLInputSource() {
 273  
         InputSource xmlInputSource = null;
 274  
         Object xmlInputSourceObj = this.getXml();
 275  
         // if no xml source specified then get from tag body
 276  
         // otherwise convert it to an input source
 277  
         if (null == xmlInputSourceObj) {
 278  
             xmlInputSource = new TagBodyInputSource();
 279  
         } else {
 280  
             xmlInputSource = this.getInputSourceFromObj(xmlInputSourceObj);
 281  
         }
 282  
         return xmlInputSource;
 283  
     }
 284  
 
 285  
     /**
 286  
      * Helper method to convert the specified object to a SAX source
 287  
      *
 288  
      * @return SAXSource from the source object or null
 289  
      */
 290  
     protected SAXSource getObjAsSAXSource(Object saxSourceObj) {
 291  
         SAXSource saxSource = null;
 292  
         if (null != saxSourceObj) {
 293  
             if (saxSourceObj instanceof Document) {
 294  
                 saxSource =  new DocumentSource((Document) saxSourceObj);
 295  
             } else {
 296  
                 InputSource xmlInputSource =
 297  
                     this.getInputSourceFromObj(saxSourceObj);
 298  
                 saxSource = new SAXSource(xmlInputSource);
 299  
             }
 300  
         }
 301  
 
 302  
         return saxSource;
 303  
     }
 304  
 
 305  
     /**
 306  
      * Helper method to get an xml input source for the supplied object
 307  
      *
 308  
      * @return InputSource for the object or null
 309  
      */
 310  
     protected InputSource getInputSourceFromObj(Object sourceObj ) {
 311  
         InputSource xmlInputSource = null;
 312  
         if (sourceObj instanceof Document) {
 313  
             SAXSource saxSource = new DocumentSource((Document) sourceObj);
 314  
             xmlInputSource = saxSource.getInputSource();
 315  
         } else {
 316  
             if (sourceObj instanceof String) {
 317  
                 String uri = (String) sourceObj;
 318  
                 xmlInputSource = new InputSource(context.getResourceAsStream(uri));
 319  
             }
 320  
             else if (sourceObj instanceof Reader) {
 321  
                 xmlInputSource = new InputSource((Reader) sourceObj);
 322  
             }
 323  
             else if (sourceObj instanceof InputStream) {
 324  
                 xmlInputSource = new InputSource((InputStream) sourceObj);
 325  
             }
 326  
             else if (sourceObj instanceof URL) {
 327  
                 String uri = ((URL) sourceObj).toString();
 328  
                 xmlInputSource = new InputSource(context.getResourceAsStream(uri));
 329  
             }
 330  
             else if (sourceObj instanceof File) {
 331  
                 try {
 332  
                     String uri = ((File) sourceObj).toURL().toString();
 333  
                     xmlInputSource = new InputSource(context.getResourceAsStream(uri));
 334  
                 }
 335  
                 catch (MalformedURLException e) {
 336  
                     throw new IllegalArgumentException(
 337  
                         "This should never occur. We should always be able to convert a File to a URL" + e );
 338  
                 }
 339  
             }
 340  
             else {
 341  
                 throw new IllegalArgumentException(
 342  
                     "Invalid source argument. Must be a String, Reader, InputStream or URL."
 343  
                         + " Was type; "
 344  
                         + sourceObj.getClass().getName()
 345  
                         + " with value: "
 346  
                         + sourceObj);
 347  
             }
 348  
         }
 349  
 
 350  
         return xmlInputSource;
 351  
     }
 352  
 
 353  
     /**
 354  
      * Helper method to run any nested param tags
 355  
      *
 356  
     * @param output The destination for any SAX output (not actually used)
 357  
      */
 358  
     private void doNestedParamTag(XMLOutput output) throws JellyTagException {
 359  
         // find any nested param tags and run them
 360  
         Script bodyScript = this.getBody();
 361  
         
 362  
         if (bodyScript instanceof ScriptBlock) {
 363  
             ScriptBlock scriptBlock = (ScriptBlock) bodyScript;
 364  
             List scriptList = scriptBlock.getScriptList();
 365  
             for (Iterator iter = scriptList.iterator(); iter.hasNext(); ) {
 366  
                 Script script = (Script) iter.next();
 367  
                 if (script instanceof TagScript) {
 368  
 
 369  
                     Tag tag = null;
 370  
                     try {
 371  
                         tag = ((TagScript) script).getTag(getContext());
 372  
                     } catch (JellyException e) {
 373  
                         throw new JellyTagException(e);
 374  
                     }
 375  
 
 376  
                     if (tag instanceof ParamTag) {
 377  
                         script.run(context, output);
 378  
                     }
 379  
 
 380  
 
 381  
                 }
 382  
             }
 383  
         }
 384  
     }
 385  
 
 386  
     
 387  
     /** A helper class that converts a transform tag body to an XMLReader
 388  
       * to hide the details of where the input for the transform is obtained
 389  
       *
 390  
       * @author <a href="mailto:robert@leftwich.info">Robert Leftwich</a>
 391  
       * @version $Revision: 155420 $
 392  
       */
 393  
     private class TagBodyXMLReader implements XMLReader {
 394  
 
 395  
         /** The tag whose body is to be read. */
 396  
         private Tag tag;
 397  
 
 398  
         /** The destination for the sax events generated by the reader. */
 399  
         private XMLOutput xmlOutput;
 400  
 
 401  
         /** Storage for a DTDHandler if set by the user of the reader. */
 402  
         private DTDHandler dtdHandler;
 403  
 
 404  
         /** Storage for a ErrorHandler if set by the user of the reader. */
 405  
         private ErrorHandler errorHandler;
 406  
 
 407  
         /** Storage for a EntityResolver if set by the user of the reader. */
 408  
         private EntityResolver entityResolver;
 409  
 
 410  
         /**
 411  
          * Construct an XMLReader for the specified Tag
 412  
          *
 413  
          * @param tag    The Tag to convert to an XMLReader
 414  
          */
 415  
         public TagBodyXMLReader(Tag tag)
 416  
         {
 417  
             this.tag = tag;
 418  
             this.xmlOutput = new XMLOutput();
 419  
         }
 420  
 
 421  
         // Methods
 422  
         //-------------------------------------------------------------------------
 423  
 
 424  
         /**
 425  
          * Parse an XML source.
 426  
          *
 427  
          * @param input  The source of the xml
 428  
          * @throws SAXException -
 429  
          *             Any SAX exception, possibly wrapping another exception.
 430  
          * @throws IOException -
 431  
          *             An IO exception from the parser, possibly from a byte
 432  
                        stream or character stream supplied by the application.
 433  
          */
 434  
         public void parse(InputSource input)
 435  
         throws IOException, SAXException
 436  
         {
 437  
             // safety check that we are being used correctly
 438  
             if (input instanceof TagBodyInputSource) {
 439  
                 this.doInvokeBody();
 440  
             } else {
 441  
                 throw new SAXException("Invalid input source");
 442  
             }
 443  
         }
 444  
 
 445  
         /**
 446  
          * Parse an XML source specified by a system id
 447  
          *
 448  
          * @param input  The system identifier (URI)
 449  
          * @throws SAXException -
 450  
          *             Any SAX exception, possibly wrapping another exception.
 451  
          * @throws IOException -
 452  
          *             An IO exception from the parser, possibly from a byte
 453  
                        stream or character stream supplied by the application.
 454  
          */
 455  
         public void parse(String systemId)
 456  
         throws IOException, SAXException
 457  
         {
 458  
             this.doInvokeBody();
 459  
         }
 460  
 
 461  
         // Helper methods
 462  
         //-------------------------------------------------------------------------
 463  
 
 464  
         /**
 465  
          * Actually invoke the tag body to generate the SAX events
 466  
          *
 467  
          * @throws SAXException -
 468  
          *             Any SAX exception, possibly wrapping another exception.
 469  
          */
 470  
         private void doInvokeBody() throws SAXException {
 471  
             try {
 472  
                 if (this.shouldParseBody()) {
 473  
                     XMLReader anXMLReader = XMLReaderFactory.createXMLReader();
 474  
                     anXMLReader.setContentHandler(this.xmlOutput);
 475  
                     anXMLReader.setProperty(LEXICAL_HANDLER_PROPERTY,this.xmlOutput);
 476  
                     StringWriter writer = new StringWriter();
 477  
                     this.tag.invokeBody(XMLOutput.createXMLOutput(writer));
 478  
                     Reader reader = new StringReader(writer.toString());
 479  
                     anXMLReader.parse(new InputSource(reader));
 480  
                 } else {
 481  
                     this.tag.invokeBody(class="keyword">this.xmlOutput);
 482  
                 }
 483  
             } catch (Exception ex) {
 484  
                 throw new SAXException(ex);
 485  
             }
 486  
         }
 487  
 
 488  
         /**
 489  
          * Helper method to determin if nested body needs to be parsed by (an
 490  
          * xml parser, i.e. its only text) to generate SAX events or not
 491  
          *
 492  
          * @return True if tag body should be parsed or false if invoked only
 493  
          * @throws JellyTagException
 494  
          */
 495  
         private boolean shouldParseBody() throws JellyTagException {
 496  
             boolean result = false;
 497  
             // check to see if we need to parse the body or just invoke it
 498  
             Script bodyScript = this.tag.getBody();
 499  
             
 500  
             if (bodyScript instanceof ScriptBlock) {
 501  
                 ScriptBlock scriptBlock = (ScriptBlock) bodyScript;
 502  
                 List scriptList = scriptBlock.getScriptList();
 503  
                 for (Iterator iter = scriptList.iterator(); iter.hasNext(); ) {
 504  
                     Script script = (Script) iter.next();
 505  
                     if (script instanceof StaticTagScript) {
 506  
                         result = true;
 507  
                          break;
 508  
                     }
 509  
                 }
 510  
             }
 511  
             return result;
 512  
         }
 513  
 
 514  
         // Properties
 515  
         //-------------------------------------------------------------------------
 516  
 
 517  
         /**
 518  
          * Gets the SAX ContentHandler to feed SAX events into
 519  
          *
 520  
          * @return the SAX ContentHandler to use to feed SAX events into
 521  
          */
 522  
         public ContentHandler getContentHandler() {
 523  
             return this.xmlOutput.getContentHandler();
 524  
         }
 525  
 
 526  
         /**
 527  
          * Sets the SAX ContentHandler to feed SAX events into
 528  
          *
 529  
          * @param contentHandler is the ContentHandler to use.
 530  
          *      This value cannot be null.
 531  
          */
 532  
         public void setContentHandler(ContentHandler contentHandler) {
 533  
             this.xmlOutput.setContentHandler(contentHandler);
 534  
             // often classes will implement LexicalHandler as well
 535  
             if (contentHandler instanceof LexicalHandler) {
 536  
                 this.xmlOutput.setLexicalHandler((LexicalHandler) contentHandler);
 537  
             }
 538  
         }
 539  
 
 540  
         /**
 541  
          * Gets the DTD Handler to feed SAX events into
 542  
          *
 543  
          * @return the DTD Handler to use to feed SAX events into
 544  
          */
 545  
         public DTDHandler getDTDHandler() {
 546  
             return this.dtdHandler;
 547  
         }
 548  
 
 549  
         /**
 550  
          * Sets the DTD Handler to feed SAX events into
 551  
          *
 552  
          * @param the DTD Handler to use to feed SAX events into
 553  
          */
 554  
         public void setDTDHandler(DTDHandler dtdHandler) {
 555  
             this.dtdHandler = dtdHandler;
 556  
         }
 557  
 
 558  
         /**
 559  
          * Gets the Error Handler to feed SAX events into
 560  
          *
 561  
          * @return the Error Handler to use to feed SAX events into
 562  
          */
 563  
         public ErrorHandler getErrorHandler() {
 564  
             return this.errorHandler;
 565  
         }
 566  
 
 567  
         /**
 568  
          * Sets the Error Handler to feed SAX events into
 569  
          *
 570  
          * @param the Error Handler to use to feed SAX events into
 571  
          */
 572  
         public void setErrorHandler(ErrorHandler errorHandler) {
 573  
             // save the error handler
 574  
             this.errorHandler = errorHandler;
 575  
         }
 576  
 
 577  
         /**
 578  
          * Gets the Entity Resolver to feed SAX events into
 579  
          *
 580  
          * @return the Entity Resolver to use to feed SAX events into
 581  
          */
 582  
         public EntityResolver getEntityResolver() {
 583  
             return this.entityResolver;
 584  
         }
 585  
 
 586  
         /**
 587  
          * Sets the Entity Resolver to feed SAX events into
 588  
          *
 589  
          * @param the Entity Resolver to use to feed SAX events into
 590  
          */
 591  
         public void setEntityResolver(EntityResolver entityResolver) {
 592  
             this.entityResolver = entityResolver;
 593  
         }
 594  
 
 595  
         /**
 596  
          * Lookup the value of a property
 597  
          *
 598  
          * @param name - The property name, which is a fully-qualified URI.
 599  
          * @return - The current value of the property.
 600  
          * @throws SAXNotRecognizedException -
 601  
          *            When the XMLReader does not recognize the property name.
 602  
          * @throws SAXNotSupportedException -
 603  
          *            When the XMLReader re