Package org.apache.cocoon.transformation

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

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.transformation;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.Map;

import org.apache.avalon.framework.CascadingException;
import org.apache.avalon.framework.CascadingRuntimeException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.components.source.impl.MultiSourceValidity;
import org.apache.cocoon.components.xpointer.XPointer;
import org.apache.cocoon.components.xpointer.XPointerContext;
import org.apache.cocoon.components.xpointer.parser.ParseException;
import org.apache.cocoon.components.xpointer.parser.XPointerFrameworkParser;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.util.NetUtils;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.xml.IncludeXMLConsumer;
import org.apache.cocoon.xml.XMLBaseSupport;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceNotFoundException;
import org.apache.excalibur.source.SourceValidity;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;

/**
* @cocoon.sitemap.component.documentation
* Implementation of an XInclude transformer.
*
* @cocoon.sitemap.component.name   xinclude
* @cocoon.sitemap.component.logger sitemap.transformer.xinclude
*
* @cocoon.sitemap.component.pooling.max  16
*
* 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 SVN $Id: XIncludeTransformer.java 433543 2006-08-22 06:22:54Z crossley $
*/
public class XIncludeTransformer extends AbstractTransformer implements Serviceable, CacheableProcessingComponent {
    protected SourceResolver resolver;
    protected ServiceManager manager;
    private XIncludePipe xIncludePipe;

    /**
     * @deprecated Should be removed in cocoon 2.2. Use javax.xml.XMLConstants.XML_NS_URI instead.
     */
    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_XPOINTER_ATTRIBUTE = "xpointer";
    public static final String XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE = "parse";

    private static final String XINCLUDE_CACHE_KEY = "XInclude";

    /** The {@link SourceValidity} instance associated with this request. */
    protected MultiSourceValidity validity;

    public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters)
            throws ProcessingException, SAXException, IOException {
        this.resolver = resolver;
        this.validity = new MultiSourceValidity(resolver, MultiSourceValidity.CHECK_ALWAYS);
        this.xIncludePipe = new XIncludePipe();
        this.xIncludePipe.enableLogging(getLogger());
        this.xIncludePipe.init(null, null);
        super.setContentHandler(xIncludePipe);
        super.setLexicalHandler(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;
    }

    /** Key to be used for caching */
    public Serializable getKey() {
        return XINCLUDE_CACHE_KEY;
    }

    /** Get the validity for this transform */
    public SourceValidity getValidity() {
        return this.validity;
    }

    public void recycle()
    {
        // Reset all variables to initial state.
        this.resolver = null;
        this.validity = 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;
        /** The nesting level of xi:include elements that have been encountered. */
        private int xIncludeElementLevel = 0;

        /** The nesting level of fallback that should be used */
        private int useFallbackLevel = 0;

        /** The nesting level of xi:fallback elements that have been encountered. */
        private int fallbackElementLevel;

        /**
         * In case {@link #useFallbackLevel} > 0, then this should contain the
         * exception that caused fallback to be needed. In the case of nested
         * include elements it will contain only the deepest exception.
         */
        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;

        /**
         * Value of the xpointer attribute of the xi:include element that caused the creation of this
         * XIncludePipe. Used to detect loop inclusions.
         */
        private String xpointer;

        private XIncludePipe parent;

        public void init(String uri, String xpointer) {
            this.href = uri;
            this.xpointer = xpointer;
            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 String getXpointer() {
            return xpointer;
        }

        /**
         * Determine whether the pipe is currently in a state where contents
         * should be evaluated, i.e. xi:include elements should be resolved
         * and elements in other namespaces should be copied through. Will
         * return false for fallback contents within a successful xi:include,
         * and true for contents outside any xi:include or within an xi:fallback
         * for an unsuccessful xi:include.
         */
        private boolean isEvaluatingContent() {
            return xIncludeElementLevel == 0 || (fallbackElementLevel > 0 && fallbackElementLevel == useFallbackLevel);
        }

        public void endDocument() throws SAXException {
            // We won't be getting any more sources so mark the MultiSourceValidity as finished.
            validity.close();
            super.endDocument();
        }

        public void startElement(String uri, String name, String raw, Attributes attr) throws SAXException {
            // Track xml:base context:
            xmlBaseSupport.startElement(uri, name, raw, attr);
            // Handle elements in xinclude namespace:
            if (XINCLUDE_NAMESPACE_URI.equals(uri)) {
                // Handle xi:include:
                if (XINCLUDE_INCLUDE_ELEMENT.equals(name)) {
                    // Process the include, unless in an ignored fallback:
                    if (isEvaluatingContent()) {
                        String href = attr.getValue("", XINCLUDE_INCLUDE_ELEMENT_HREF_ATTRIBUTE);
                        String parse = attr.getValue("", XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE);
                        String xpointer = attr.getValue("", XINCLUDE_INCLUDE_ELEMENT_XPOINTER_ATTRIBUTE);

                        try {
                            processXIncludeElement(href, parse, xpointer);
                        } catch (ProcessingException e) {
                            getLogger().debug("Rethrowing exception", e);
                            throw new SAXException(e);
                        } catch (IOException e) {
                            getLogger().debug("Rethrowing exception", e);
                            throw new SAXException(e);
                        }
                    }
                    xIncludeElementLevel++;
                } else if (XINCLUDE_FALLBACK_ELEMENT.equals(name)) {
                    // Handle xi:fallback
                    fallbackElementLevel++;
                } else {
                    // Unknown element:
                    throw new SAXException("Unknown XInclude element " + raw + " at " + getLocation());
                }
            } else if (isEvaluatingContent()) {
                // Copy other elements through when appropriate:
                super.startElement(uri, name, raw, attr);
            }
        }

        public void endElement(String uri, String name, String raw) throws SAXException {
            // Track xml:base context:
            xmlBaseSupport.endElement(uri, name, raw);

            // Handle elements in xinclude namespace:
            if (XINCLUDE_NAMESPACE_URI.equals(uri)) {
                // Handle xi:include:
                if (XINCLUDE_INCLUDE_ELEMENT.equals(name)) {
                    xIncludeElementLevel--;
                    if (useFallbackLevel > xIncludeElementLevel) {
                        useFallbackLevel = xIncludeElementLevel;
                    }
                } else if (XINCLUDE_FALLBACK_ELEMENT.equals(name)) {
                    // Handle xi:fallback:
                    fallbackElementLevel--;
                }
            } else if (isEvaluatingContent()) {
                // Copy other elements through when appropriate:
                super.endElement(uri, name, raw);
            }
        }

        public void startPrefixMapping(String prefix, String uri) throws SAXException {
            if (isEvaluatingContent()) {
                super.startPrefixMapping(prefix, uri);
            }
        }

        public void endPrefixMapping(String prefix) throws SAXException {
            if (isEvaluatingContent()) {
                super.endPrefixMapping(prefix);
            }
        }

        public void characters(char c[], int start, int len) throws SAXException {
            if (isEvaluatingContent()) {
                super.characters(c, start, len);
            }
        }

        public void ignorableWhitespace(char c[], int start, int len) throws SAXException {
            if (isEvaluatingContent()) {
                super.ignorableWhitespace(c, start, len);
            }
        }

        public void processingInstruction(String target, String data) throws SAXException {
            if (isEvaluatingContent()) {
                super.processingInstruction(target, data);
            }
        }

        public void skippedEntity(String name) throws SAXException {
            if (isEvaluatingContent()) {
                super.skippedEntity(name);
            }
        }

        public void startEntity(String name) throws SAXException {
            if (isEvaluatingContent()) {
                super.startEntity(name);
            }
        }

        public void endEntity(String name) throws SAXException {
            if (isEvaluatingContent()) {
                super.endEntity(name);
            }
        }

        public void startCDATA() throws SAXException {
            if (isEvaluatingContent()) {
                super.startCDATA();
            }
        }

        public void endCDATA() throws SAXException {
            if (isEvaluatingContent()) {
                super.endCDATA();
            }
        }

        public void comment(char ch[], int start, int len) throws SAXException {
            if (isEvaluatingContent()) {
                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, String xpointer)
        throws SAXException,ProcessingException,IOException {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Processing XInclude element: href="+href+", parse="+parse+", xpointer="+xpointer);
            }

            // Default for @parse is "xml"
            if (parse == null) {
                parse = "xml";
            }
            Source url = null;

            try {
                int fragmentIdentifierPos = href.indexOf('#');
                if (fragmentIdentifierPos != -1) {
                    getLogger().warn("Fragment identifer found in 'href' attribute: " + href +
                            "\nFragment identifiers are forbidden by the XInclude specification. " +
                            "They are still handled by XIncludeTransformer for backward " +
                            "compatibility, but their use is deprecated and will be prohibited " +
                            "in a future release.  Use the 'xpointer' attribute instead.");
                    if (xpointer == null) {
                        xpointer = href.substring(fragmentIdentifierPos + 1);
                    }
                    href = href.substring(0, fragmentIdentifierPos);
                }

                // An empty or absent href is a reference to the current document -- this can be different than the current base
                if (href == null || href.length() == 0) {
                    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.");
                    }
                    // The following can be simplified once fragment identifiers are prohibited
                    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() + "\nXPointer: " + xpointer);
                }

                // add the source to the SourceValidity
                validity.addSource(url);

                if (parse.equals("text")) {
                    getLogger().debug("Parse type is text");
                    if (xpointer != null) {
                        throw new SAXException("xpointer attribute must not be present when parse='text': " + getLocation());
                    }
                    InputStream is = null;
                    InputStreamReader isr = null;
                    Reader reader = null;
                    try {
                        is = url.getInputStream();
                        isr = new InputStreamReader(is);
                        reader = new BufferedReader(isr);
                        int read;
                        char ary[] = new char[1024 * 4];
                        while ((read = reader.read(ary)) != -1) {
                            super.characters(ary,0,read);
                        }
                    } catch (SourceNotFoundException e) {
                        useFallbackLevel++;
                        fallBackException = new CascadingException("Resource not found: " + url.getURI());
                        getLogger().error("xIncluded resource not found: " + url.getURI(), e);
                    } finally {
                        if (reader != null) reader.close();
                        if (isr != null) isr.close();
                        if (is != null) is.close();
                    }
                } else if (parse.equals("xml")) {
                    getLogger().debug("Parse type is XML");

                    // Check loop inclusion
                    if (isLoopInclusion(url.getURI(), xpointer)) {
                        throw new ProcessingException("Detected loop inclusion of href=" + url.getURI() + ", xpointer=" + xpointer);
                    }

                    XIncludePipe subPipe = new XIncludePipe();
                    subPipe.enableLogging(getLogger());
                    subPipe.init(url.getURI(), xpointer);
                    subPipe.setConsumer(xmlConsumer);
                    subPipe.setParent(this);

                    try {
                        if (xpointer != null && xpointer.length() > 0) {
                            XPointer xptr;
                            xptr = XPointerFrameworkParser.parse(NetUtils.decodePath(xpointer));
                            XPointerContext context = new XPointerContext(xpointer, url, subPipe, getLogger(), manager);
                            xptr.process(context);
                        } else {
                            SourceUtil.toSAX(url, new IncludeXMLConsumer(subPipe));
                        }
                        // restore locator on the consumer
                        if (locator != null)
                            xmlConsumer.setDocumentLocator(locator);
                    } catch (ResourceNotFoundException e) {
                        useFallbackLevel++;
                        fallBackException = new CascadingException("Resource 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
                        useFallbackLevel++;
                        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) {
                        useFallbackLevel++;
                        fallBackException = e;
                        getLogger().error("Error processing an xInclude, will try to use fallback.", e);
                    } catch(IOException e) {
                        useFallbackLevel++;
                        fallBackException = e;
                        getLogger().error("Error processing an xInclude, will try to use fallback.", e);
                    }
                } else {
                    throw new SAXException("Found 'parse' attribute with unknown value " + parse + " at " + getLocation());
                }
            } catch (SourceException se) {
                throw SourceUtil.handle(se);
            } finally {
                if (url != null) {
                    resolver.release(url);
                }
            }
        }

        public boolean isLoopInclusion(String uri, String xpointer) {
            if (xpointer == null) {
                xpointer = "";
            }

            if (uri.equals(this.href) && xpointer.equals(this.xpointer == null ? "" : this.xpointer)) {
                return true;
            }

            XIncludePipe parent = getParent();
            while (parent != null) {
                if (uri.equals(parent.getHref()) && xpointer.equals(parent.getXpointer() == null ? "" : parent.getXpointer())) {
                    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.
nalytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');