FileDocCategorySizeDatePackage
TransformSupport.javaAPI DocGlassfish v2 API14001Sat May 05 19:17:54 BST 2007org.apache.taglibs.standard.tag.common.xml

TransformSupport.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * Portions Copyright Apache Software Foundation.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.apache.taglibs.standard.tag.common.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.taglibs.standard.resources.Resources;
import org.apache.taglibs.standard.tag.common.core.ImportSupport;
import org.apache.taglibs.standard.tag.common.core.Util;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * <p>Support for tag handlers for <transform>, the XML transformation
 * tag.</p>
 *
 * @author Shawn Bayern
 */
public abstract class TransformSupport extends BodyTagSupport {

    //*********************************************************************
    // Protected state

    protected Object xml;                       // attribute
    protected String xmlSystemId;		// attribute
    protected Object xslt;			// attribute
    protected String xsltSystemId;		// attribute
    protected Result result;			// attribute

    //*********************************************************************
    // Private state

    private String var;                            // 'var' attribute
    private int scope;				   // processed 'scope' attr
    private Transformer t;			   // actual Transformer
    private TransformerFactory tf;		   // reusable factory
    private DocumentBuilder db;			   // reusable factory
    private DocumentBuilderFactory dbf;		   // reusable factory


    //*********************************************************************
    // Constructor and initialization

    public TransformSupport() {
	super();
	init();
    }

    private void init() {
	xml = xslt = null;
	xmlSystemId = xsltSystemId = null;
	var = null;
	result = null;
	tf = null;
        scope = PageContext.PAGE_SCOPE;
    }


    //*********************************************************************
    // Tag logic

    public int doStartTag() throws JspException {
      /*
       * We can set up our Transformer here, so we do so, and we let
       * it receive parameters directly from subtags (instead of
       * caching them.
       */
      try {

	//************************************
	// Initialize

	// set up our DocumentBuilderFactory if necessary
	if (dbf == null) {
	    dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            dbf.setValidating(false);
	}
        if (db == null)
	    db = dbf.newDocumentBuilder();

	// set up the TransformerFactory if necessary
        if (tf == null)
            tf = TransformerFactory.newInstance();

	//************************************
	// Produce transformer

	Source s;
	if (xslt != null) {
	    if (!(xslt instanceof String) && !(xslt instanceof Reader)
                    && !(xslt instanceof javax.xml.transform.Source))
		throw new JspTagException(
		    Resources.getMessage("TRANSFORM_XSLT_UNRECOGNIZED"));
	    s = getSource(xslt, xsltSystemId);
	} else {
	    throw new JspTagException(
	        Resources.getMessage("TRANSFORM_NO_TRANSFORMER"));
        }
	tf.setURIResolver(new JstlUriResolver(pageContext));
        t = tf.newTransformer(s);

	return EVAL_BODY_BUFFERED;

      } catch (SAXException ex) {
	throw new JspException(ex);
      } catch (ParserConfigurationException ex) {
	throw new JspException(ex);
      } catch (IOException ex) {
	throw new JspException(ex);
      } catch (TransformerConfigurationException ex) {
	throw new JspException(ex);
      }
    }

    // parse 'xml' or body, transform via our Transformer,
    // and store as 'var' or through 'result'
    public int doEndTag() throws JspException {
      try {

	//************************************
	// Determine source XML

	// if we haven't gotten a source, use the body (which may be empty)
	Object xml = this.xml;
	if (xml == null)				// still equal
	    if (bodyContent != null && bodyContent.getString() != null)
	        xml = bodyContent.getString().trim();
	    else
		xml = "";

	// let the Source be with you
	Source source = getSource(xml, xmlSystemId);

	//************************************
	// Conduct the transformation

	// we can assume at most one of 'var' or 'result' is specified
	if (result != null)
	    // we can write directly to the Result
	    t.transform(source, result);
	else if (var != null) {
	    // we need a Document
	    Document d = db.newDocument();
	    Result doc = new DOMResult(d);
	    t.transform(source, doc);
	    pageContext.setAttribute(var, d, scope);
	} else {
	    Result page =
		new StreamResult(new SafeWriter(pageContext.getOut()));
	    t.transform(source, page);
	}

	return EVAL_PAGE;
      } catch (SAXException ex) {
	throw new JspException(ex);
      } catch (ParserConfigurationException ex) {
	throw new JspException(ex);
      } catch (IOException ex) {
	throw new JspException(ex);
      } catch (TransformerException ex) {
	throw new JspException(ex);
      }
    }

    // Releases any resources we may have (or inherit)
    public void release() {
	init();
    }


    //*********************************************************************
    // Public methods for subtags

    /** Sets (adds) a transformation parameter on our transformer. */
    public void addParameter(String name, Object value) {
	t.setParameter(name, value);
    }


    //*********************************************************************
    // Utility methods

    /**
     * Wraps systemId with a "jstl:" prefix to prevent the parser from
     * thinking that the URI is truly relative and resolving it against
     * the current directory in the filesystem.
     */
    private static String wrapSystemId(String systemId) {
      if (systemId == null)
          return "jstl:";
      else if (ImportSupport.isAbsoluteUrl(systemId))
          return systemId;
      else
          return ("jstl:" + systemId);
    }

    /**
     * Retrieves a Source from the given Object, whether it be a String,
     * Reader, Node, or other supported types (even a Source already).
     * If 'url' is true, then we must be passed a String and will interpret
     * it as a URL.  A null input always results in a null output.
     */
    private Source getSource(Object o, String systemId)
	    throws SAXException, ParserConfigurationException, IOException {
	if (o == null)
	    return null;
        else if (o instanceof Source) {
	    return (Source) o;
        } else if (o instanceof String) {
	    // if we've got a string, chain to Reader below
	    return getSource(new StringReader((String) o), systemId);
        } else if (o instanceof Reader) {
	    // explicitly go through SAX to maintain control
	    // over how relative external entities resolve
            XMLReader xr = XMLReaderFactory.createXMLReader();
            xr.setEntityResolver(
                new ParseSupport.JstlEntityResolver(pageContext));
            InputSource s = new InputSource((Reader) o);
            s.setSystemId(wrapSystemId(systemId));
            Source result = new SAXSource(xr, s);
            result.setSystemId(wrapSystemId(systemId));
	    return result;
        } else if (o instanceof Node) {
	    return new DOMSource((Node) o);
        } else if (o instanceof List) {
	    // support 1-item List because our XPath processor outputs them	
	    List l = (List) o;
	    if (l.size() == 1) {
	        return getSource(l.get(0), systemId);		// unwrap List
	    } else {
	        throw new IllegalArgumentException(
                  Resources.getMessage("TRANSFORM_SOURCE_INVALID_LIST"));
	    }
        } else {
	    throw new IllegalArgumentException(
	       Resources.getMessage("TRANSFORM_SOURCE_UNRECOGNIZED")
	         + o.getClass());
	}
    }


    //*********************************************************************
    // Tag attributes

    public void setVar(String var) {
	this.var = var;
    }

    public void setScope(String scope) {
        this.scope = Util.getScope(scope);
    }


    //*********************************************************************
    // Private utility classes

    /**
     * A Writer based on a wrapped Writer but ignoring requests to
     * close() and flush() it.  (Someone must have wrapped the
     * toilet in my office similarly...)
     */
    private static class SafeWriter extends Writer {
	private Writer w;
	public SafeWriter(Writer w) { this.w = w; }
	public void close() { }
	public void flush() { }
	public void write(char[] cbuf, int off, int len) throws IOException {
	    w.write(cbuf, off, len);
	}
    }	

    //*********************************************************************
    // JSTL-specific URIResolver class

    /** Lets us resolve relative external entities. */
    private static class JstlUriResolver implements URIResolver {
        private final PageContext ctx;
        public JstlUriResolver(PageContext ctx) {
            this.ctx = ctx;
        }
        public Source resolve(String href, String base)
	        throws TransformerException {

            // pass if we don't have a systemId
            if (href == null)
                return null;

	    // remove "jstl" marker from 'base'
            // NOTE: how 'base' is determined varies among different Xalan
            // xsltc implementations
            int index;
            if (base != null && (index = base.indexOf("jstl:")) != -1) {
                base = base.substring(index + 5);
            }  

            // we're only concerned with relative URLs
            if (ImportSupport.isAbsoluteUrl(href)
		    || (base != null && ImportSupport.isAbsoluteUrl(base)))
                return null;

	    // base is relative; remove everything after trailing '/'
	    if (base == null || base.lastIndexOf("/") == -1)
		base = "";
	    else
		base = base.substring(0, base.lastIndexOf("/") + 1);

	    // concatenate to produce the real URL we're interested in
	    String target = base + href;	    

            // for relative URLs, load and wrap the resource.
            // don't bother checking for 'null' since we specifically want
            // the parser to fail if the resource doesn't exist
            InputStream s;
            if (target.startsWith("/")) {
                s = ctx.getServletContext().getResourceAsStream(target);
                if (s == null)
                    throw new TransformerException(
                        Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY",
                         href));
            } else {
                String pagePath =
                    ((HttpServletRequest) ctx.getRequest()).getServletPath();
                String basePath =
                    pagePath.substring(0, pagePath.lastIndexOf("/"));
                s = ctx.getServletContext().getResourceAsStream(
                      basePath + "/" + target);
		if (s == null)
		    throw new TransformerException(
                        Resources.getMessage("UNABLE_TO_RESOLVE_ENTITY",
                         href));
            }
            return new StreamSource(s);
        }
    }

}