Package org.apache.cocoon.transformation

Source Code of org.apache.cocoon.transformation.XIncludeTransformer

/*

============================================================================
                   The Apache Software License, Version 1.1
============================================================================

Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:

1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
(INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software  consists of voluntary contributions made  by many individuals
on  behalf of the Apache Software  Foundation and was  originally created by
Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.cocoon.transformation;

import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.CascadingRuntimeException;
import org.apache.avalon.framework.CascadingException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.util.NetUtils;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.components.xpointer.XPointer;
import org.apache.cocoon.components.xpointer.parser.XPointerFrameworkParser;
import org.apache.cocoon.components.xpointer.parser.ParseException;
import org.apache.cocoon.components.xpointer.XPointerContext;
import org.apache.cocoon.xml.IncludeXMLConsumer;
import org.apache.cocoon.xml.XMLBaseSupport;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.xml.sax.*;
import org.xml.sax.ext.LexicalHandler;

import java.io.*;
import java.net.MalformedURLException;
import java.util.Map;

/**
* Implementation of an XInclude transformer. It supports xml:base attributes,
* xpointer fragment identifiers (see the xpointer package to see what exactly is supported),
* fallback elements, and does xinclude processing on the included content and on the content
* of fallback elements (with loop inclusion detection).
*
* @author <a href="mailto:balld@webslingerZ.com">Donald Ball</a> (wrote the original version)
* @version CVS $Id: XIncludeTransformer.java,v 1.13 2004/02/08 11:30:38 bruno Exp $
*/
public class XIncludeTransformer extends AbstractTransformer implements Serviceable {
    protected SourceResolver resolver;
    protected ServiceManager manager;
    private XIncludePipe xIncludePipe;

    public static final String XMLBASE_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
    public static final String XMLBASE_ATTRIBUTE = "base";

    public static final String XINCLUDE_NAMESPACE_URI = "http://www.w3.org/2001/XInclude";
    public static final String XINCLUDE_INCLUDE_ELEMENT = "include";
    public static final String XINCLUDE_FALLBACK_ELEMENT = "fallback";
    public static final String XINCLUDE_INCLUDE_ELEMENT_HREF_ATTRIBUTE = "href";
    public static final String XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE = "parse";


    public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters)
            throws ProcessingException, SAXException, IOException {
        this.resolver = resolver;
        this.xIncludePipe = new XIncludePipe();
        this.xIncludePipe.enableLogging(getLogger());
        this.xIncludePipe.init(null);
        super.setConsumer(xIncludePipe);
    }

    public void setConsumer(XMLConsumer consumer) {
        xIncludePipe.setConsumer(consumer);
    }

    public void setContentHandler(ContentHandler handler) {
        xIncludePipe.setContentHandler(handler);
    }

    public void setLexicalHandler(LexicalHandler handler) {
        xIncludePipe.setLexicalHandler(handler);
    }

    public void service(ServiceManager manager) {
        this.manager = manager;
    }

    public void recycle()
    {
        // Reset all variables to initial state.
        this.resolver = null;
        this.xIncludePipe = null;
        super.recycle();
    }

    /**
     * XMLPipe that processes XInclude elements. To perform XInclude processing on included content,
     * this class is instantiated recursively.
     */
    private class XIncludePipe extends AbstractXMLPipe {
        /** Helper class to keep track of xml:base attributes */
        private XMLBaseSupport xmlBaseSupport;
        /** Element nesting level when inside an xi:include element. */
        private int xIncludeLevel = 0;
        /** Should the content of the fallback element be inserted when it is encountered? */
        private boolean useFallback = false;
        /** Element nesting level when inside the fallback element. */
        private int fallbackLevel;
        /** In case {@link #useFallback} = true, then this should contain the exception that caused fallback to be needed. */
        private Exception fallBackException;
        /**
         * Locator of the current stream, stored here so that it can be restored after
         * another document send its content to the consumer.
         */
        private Locator locator;
        /**
         * Value of the href attribute of the xi:include element that caused the creation of the this
         * XIncludePipe. Used to detect loop inclusions.
         * */
        private String href;
        private XIncludePipe parent;

        public void init(String uri) {
            this.href = uri;
            this.xmlBaseSupport = new XMLBaseSupport(resolver, getLogger());
        }

        public void setParent(XIncludePipe parent) {
            this.parent = parent;
        }

        public XIncludePipe getParent() {
            return parent;
        }

        public String getHref() {
            return href;
        }

        public void startElement(String uri, String name, String raw, Attributes attr) throws SAXException {
            if (xIncludeLevel == 1 && useFallback && name.equals(XINCLUDE_FALLBACK_ELEMENT) && uri.equals(XINCLUDE_NAMESPACE_URI)) {
                fallbackLevel++;

                // don't need these anymore
                useFallback = false;
                fallBackException = null;

                return;
            } else if (xIncludeLevel > 0 && fallbackLevel < 1) {
                xIncludeLevel++;
                return;
            }

            xmlBaseSupport.startElement(uri, name, raw, attr);
            if (uri.equals(XINCLUDE_NAMESPACE_URI)) {
                if (XINCLUDE_INCLUDE_ELEMENT.equals(name)) {
                    String href = attr.getValue("",XINCLUDE_INCLUDE_ELEMENT_HREF_ATTRIBUTE);
                    if (href == null) {
                        throw new SAXException(raw + " must have a 'href' attribute at " + getLocation());
                    }

                    String parse = attr.getValue("",XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE);

                    if (null == parse) parse="xml";
                    xIncludeLevel++;

                    try {
                        processXIncludeElement(href, parse);
                    } catch (ProcessingException e) {
                        getLogger().debug("Rethrowing exception", e);
                        throw new SAXException(e);
                    } catch (IOException e) {
                        getLogger().debug("Rethrowing exception", e);
                        throw new SAXException(e);
                    }
                    return;
                }

                throw new SAXException("Unknown XInclude element " + raw + " at " + getLocation());

            } else {
                super.startElement(uri,name,raw,attr);
            }
        }

        public void endElement(String uri, String name, String raw) throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1) {
                xIncludeLevel--;
                if (xIncludeLevel == 0)
                    xmlBaseSupport.endElement(uri, name, raw);
                if (xIncludeLevel == 0 && useFallback) {
                    // an error was encountered but a fallback element was not found: throw the error now
                    useFallback = false;
                    Exception localFallBackException = fallBackException;
                    fallBackException = null;
                    fallbackLevel = 0;
                    getLogger().error("Exception occured during xinclude processing, and did not find a fallback element.", localFallBackException);
                    throw new SAXException("Exception occured during xinclude processing, and did not find a fallback element: " + localFallBackException.getMessage());
                }
                return;
            }

            if (fallbackLevel > 0) {
                fallbackLevel--;
                if (fallbackLevel == 0)
                    return;
            }

            xmlBaseSupport.endElement(uri, name, raw);
            super.endElement(uri,name,raw);
        }

        public void startPrefixMapping(String prefix, String uri)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.startPrefixMapping(prefix, uri);
        }

        public void endPrefixMapping(String prefix)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.endPrefixMapping(prefix);
        }

        public void characters(char c[], int start, int len)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.characters(c, start, len);
        }

        public void ignorableWhitespace(char c[], int start, int len)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.ignorableWhitespace(c, start, len);
        }

        public void processingInstruction(String target, String data)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.processingInstruction(target, data);
        }

        public void skippedEntity(String name)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.skippedEntity(name);
        }

        public void startEntity(String name)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.startEntity(name);
        }

        public void endEntity(String name)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.endEntity(name);
        }

        public void startCDATA()
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.startCDATA();
        }

        public void endCDATA()
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.endCDATA();
        }

        public void comment(char ch[], int start, int len)
                throws SAXException {
            if (xIncludeLevel > 0 && fallbackLevel < 1)
                return;
            super.comment(ch, start, len);
        }

        public void setDocumentLocator(Locator locator) {
            try {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("setDocumentLocator called " + locator.getSystemId());
                }

                // When using SAXON to serialize a DOM tree to SAX, a locator is passed with a "null" system id
                if (locator.getSystemId() != null) {
                    Source source = resolver.resolveURI(locator.getSystemId());
                    try {
                        xmlBaseSupport.setDocumentLocation(source.getURI());
                        // only for the "root" XIncludePipe, we'll have to set the href here, in the other cases
                        // the href is taken from the xi:include href attribute
                        if (href == null)
                            href = source.getURI();
                    } finally {
                        resolver.release(source);
                    }
                }
            } catch (Exception e) {
                throw new CascadingRuntimeException("Error in XIncludeTransformer while trying to resolve base URL for document", e);
            }
            this.locator = locator;
            super.setDocumentLocator(locator);
        }

        protected void processXIncludeElement(String href, String parse)
        throws SAXException,ProcessingException,IOException {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Processing XInclude element: href="+href+", parse="+parse);
            }

            Source url = null;
            String suffix = "";
            try {
                int fragmentIdentifierPos = href.indexOf('#');
                if (fragmentIdentifierPos != -1) {
                    suffix = href.substring(fragmentIdentifierPos + 1);
                    href = href.substring(0, fragmentIdentifierPos);
                }

                // an empty href is a reference to the current document -- this can be different than the current base
                if (href.equals("")) {
                    if (this.href == null)
                        throw new SAXException("XIncludeTransformer: encountered empty href (= href pointing to the current document) but the location of the current document is unknown.");
                    int fragmentIdentifierPos2 = this.href.indexOf('#');
                    if (fragmentIdentifierPos2 != -1)
                        href = this.href.substring(0, fragmentIdentifierPos2);
                    else
                        href = this.href;
                }

                url = xmlBaseSupport.makeAbsolute(href);
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("URL: " + url.getURI() + "\nSuffix: " + suffix);
                }

                // check loop inclusion
                String canonicURI = url.getURI() + (suffix.length() > 0 ? "#" + suffix: "");
                if (isLoopInclusion(canonicURI))
                    throw new ProcessingException("Detected loop inclusion of " + canonicURI);

                if (parse.equals("text")) {
                    getLogger().debug("Parse type is text");
                    InputStream input = url.getInputStream();
                    Reader reader = new BufferedReader(new InputStreamReader(input));
                    int read;
                    char ary[] = new char[1024];
                    if (reader != null) {
                        while ((read = reader.read(ary)) != -1) {
                            super.characters(ary,0,read);
                        }
                        reader.close();
                    }
                } else if (parse.equals("xml")) {
                    XIncludePipe subPipe = new XIncludePipe();
                    subPipe.enableLogging(getLogger());
                    subPipe.init(canonicURI);
                    subPipe.setConsumer(xmlConsumer);
                    subPipe.setParent(this);

                    getLogger().debug("Parse type is XML");
                    try {
                        if (suffix.length() > 0) {
                            XPointer xpointer;
                            xpointer = XPointerFrameworkParser.parse(NetUtils.decodePath(suffix));
                            XPointerContext context = new XPointerContext(suffix, url, subPipe, getLogger(), manager);
                            xpointer.process(context);
                        } else {
                            SourceUtil.toSAX(url, new IncludeXMLConsumer(subPipe));
                        }
                        // restore locator on the consumer
                        if (locator != null)
                            xmlConsumer.setDocumentLocator(locator);
                    } catch (ResourceNotFoundException e) {
                        useFallback = true;
                        fallBackException = new CascadingException("Resouce not found: " + url.getURI());
                        getLogger().error("xIncluded resource not found: " + url.getURI(), e);
                    } catch (ParseException e) {
                        // this exception is thrown in case of an invalid xpointer expression
                        useFallback = true;
                        fallBackException = new CascadingException("Error parsing xPointer expression", e);
                        fallBackException.fillInStackTrace();
                        getLogger().error("Error parsing XPointer expression, will try to use fallback.", e);
                    } catch(SAXException e) {
                        getLogger().error("Error in processXIncludeElement", e);
                        throw e;
                    } catch(ProcessingException e) {
                        getLogger().error("Error in processXIncludeElement", e);
                        throw e;
                    } catch(MalformedURLException e) {
                        useFallback = true;
                        fallBackException = e;
                        getLogger().error("Error processing an xInclude, will try to use fallback.", e);
                    } catch(IOException e) {
                        useFallback = true;
                        fallBackException = e;
                        getLogger().error("Error processing an xInclude, will try to use fallback.", e);
                    }
                }
            } catch (SourceException se) {
                throw SourceUtil.handle(se);
            } finally {
                if (url != null) {
                    resolver.release(url);
                }
            }
        }

        public boolean isLoopInclusion(String uri) {
            if (uri.equals(this.href)) {
                return true;
            }

            XIncludePipe parent = getParent();
            while (parent != null) {
                if (uri.equals(parent.getHref())) {
                    return true;
                }
                parent = parent.getParent();
            }
            return false;
        }
       
        private String getLocation() {
            if (this.locator == null) {
                return "unknown location";
            } else {
                return this.locator.getSystemId() + ":" + this.locator.getColumnNumber() + ":" + this.locator.getLineNumber();
            }
        }
    }
}
TOP

Related Classes of org.apache.cocoon.transformation.XIncludeTransformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.