org.apache.cocoon.transformation.TagTransformer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cocoon.transformation.TagTransformer.java

Source

/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed 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 org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentSelector;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceSelector;
import org.apache.avalon.framework.service.Serviceable;

import org.apache.cocoon.components.sax.XMLDeserializer;
import org.apache.cocoon.components.sax.XMLSerializer;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.taglib.IterationTag;
import org.apache.cocoon.taglib.Tag;
import org.apache.cocoon.taglib.BodyTag;
import org.apache.cocoon.taglib.BodyContent;
import org.apache.cocoon.xml.AbstractXMLProducer;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLProducer;
import org.apache.cocoon.xml.SaxBuffer;

import org.apache.commons.collections.ArrayStack;
import org.apache.commons.collections.map.StaticBucketMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Transformer which implements the taglib functionalty.
 *
 * <p>Transformer processes incoming SAX events and for each element it tries to
 * find {@link Tag} component with matching namespace and tag name.
 *
 * @author <a href="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a>
 * @version CVS $Id: TagTransformer.java 46206 2004-09-16 20:06:28Z vgritsenko $
 */
public class TagTransformer extends AbstractXMLProducer
        implements Transformer, Serviceable, Configurable, Disposable, Recyclable {

    private int recordingLevel;
    private int skipLevel;

    private String transformerHint;
    private ServiceSelector transformerSelector;

    private final ArrayStack tagStack = new ArrayStack();
    private final ArrayStack tagSelectorStack = new ArrayStack();
    private final ArrayStack tagTransformerStack = new ArrayStack();

    private ServiceSelector tagNamespaceSelector;
    private Tag currentTag;

    /** current SAX Event Consumer  */
    private XMLConsumer currentConsumer;

    /** backup of currentConsumer while recording */
    private XMLConsumer currentConsumerBackup;

    private XMLSerializer xmlSerializer;

    /** The SourceResolver for this request */
    private SourceResolver resolver;

    /** The current objectModel of the environment */
    private Map objectModel;

    /** The parameters specified in the sitemap */
    private Parameters parameters;

    /** The Avalon ServiceManager */
    private ServiceManager manager;

    /** Array for dynamic calling of Tag set property methods */
    private final String[] paramArray = new String[1];

    /** Map for caching Tag Introspection */
    private static Map TAG_PROPERTIES_MAP = new StaticBucketMap();

    //
    // Component Lifecycle Methods
    //

    /**
     * Avalon Serviceable Interface
     * @param manager The Avalon Service Manager
     */
    public void service(ServiceManager manager) throws ServiceException {
        this.manager = manager;
        this.tagNamespaceSelector = (ServiceSelector) manager.lookup(Tag.ROLE + "Selector");
    }

    /**
     * Avalon Configurable Interface
     */
    public void configure(Configuration conf) throws ConfigurationException {
        this.transformerHint = conf.getChild("transformer-hint").getValue(null);
        if (this.transformerHint != null) {
            try {
                this.transformerSelector = (ServiceSelector) manager.lookup(Transformer.ROLE + "Selector");
            } catch (ServiceException e) {
                String message = "Can't lookup transformer selector";
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(message, e);
                }
                throw new ConfigurationException(message, e);
            }
        }
    }

    /**
     * Set the <code>EntityResolver</code>, objectModel <code>Map</code>,
     * the source and sitemap <code>Parameters</code> used to process the request.
     */
    public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters)
            throws IOException, SAXException {
        this.resolver = resolver;
        this.objectModel = objectModel;
        this.parameters = parameters;
    }

    /**
     *  Recycle this component.
     */
    public void recycle() {
        this.recordingLevel = 0;
        this.skipLevel = 0;
        this.resolver = null;
        this.objectModel = null;
        this.parameters = null;
        this.currentTag = null;
        this.currentConsumer = null;
        this.currentConsumerBackup = null;

        // can happen if there was a error in the pipeline
        if (xmlSerializer != null) {
            manager.release(xmlSerializer);
            xmlSerializer = null;
        }

        while (!tagStack.isEmpty()) {
            Tag tag = (Tag) tagStack.pop();
            if (tag == null)
                continue;
            ComponentSelector tagSelector = (ComponentSelector) tagSelectorStack.pop();
            tagSelector.release(tag);

            tagNamespaceSelector.release(tagSelector);
        }

        while (!tagTransformerStack.isEmpty()) {
            Transformer transformer = (Transformer) tagTransformerStack.pop();
            transformerSelector.release(transformer);
        }

        if (!tagSelectorStack.isEmpty()) {
            getLogger().fatalError("recycle: internal Error, tagSelectorStack not empty");
            tagSelectorStack.clear();
        }

        super.recycle();
    }

    /**
     *  Dispose this component.
     */
    public void dispose() {
        this.manager.release(tagNamespaceSelector);
        tagNamespaceSelector = null;
        if (transformerSelector != null) {
            this.manager.release(transformerSelector);
            transformerSelector = null;
        }
    }

    /*
     * @see XMLProducer#setConsumer(XMLConsumer)
     */
    public void setConsumer(XMLConsumer consumer) {
        this.currentConsumer = consumer;
        super.setConsumer(consumer);
    }

    //
    // SAX Events Methods
    //

    public void setDocumentLocator(org.xml.sax.Locator locator) {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.setDocumentLocator(locator);
    }

    public void startDocument() throws SAXException {
        this.currentConsumer.startDocument();
    }

    public void endDocument() throws SAXException {
        this.currentConsumer.endDocument();
    }

    public void processingInstruction(String target, String data) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.processingInstruction(target, data);
    }

    public void startDTD(String name, String publicId, String systemId) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.startDTD(name, publicId, systemId);
    }

    public void endDTD() throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.endDTD();
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.startPrefixMapping(prefix, uri);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.endPrefixMapping(prefix);
    }

    public void startCDATA() throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.startCDATA();
    }

    public void endCDATA() throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.endCDATA();
    }

    public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
            throws SAXException {
        // Are we recording for iteration ?
        if (this.recordingLevel > 0) {
            this.recordingLevel++;
            this.currentConsumer.startElement(namespaceURI, localName, qName, atts);
            return;
        }

        // If we are skipping the body of a Tag
        if (this.skipLevel > 0) {
            // Remember to skip one more end element
            this.skipLevel++;
            // and ignore this start element
            return;
        }

        Tag tag = null;
        if (namespaceURI != null && namespaceURI.length() > 0) {
            // Try to find Tag corresponding to this element
            ComponentSelector tagSelector = null;
            try {
                tagSelector = (ComponentSelector) tagNamespaceSelector.select(namespaceURI);
                tagSelectorStack.push(tagSelector);

                // namespace matches tag library, lookup tag now.
                tag = (Tag) tagSelector.select(localName);
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("startElement: Got tag " + qName);
                }

                setupTag(tag, qName, atts);
            } catch (SAXException e) {
                throw e;
            } catch (Exception ignore) {
                // No namespace or tag found, process it as normal element (tag == null)
            }
        }

        tagStack.push(tag);
        if (tag == null) {
            currentConsumer.startElement(namespaceURI, localName, qName, atts);
            return;
        }

        // Execute Tag
        int eval = tag.doStartTag(namespaceURI, localName, qName, atts);
        switch (eval) {
        case Tag.EVAL_BODY:
            skipLevel = 0;
            if (tag instanceof IterationTag) {
                // start recording for IterationTag
                startRecording();
            }
            break;

        case Tag.SKIP_BODY:
            skipLevel = 1;
            break;

        default:
            String tagName = tag.getClass().getName();
            getLogger().warn("Bad return value from doStartTag(" + tagName + "): " + eval);
            break;
        }
    }

    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        Object saxFragment = null;

        // Are we recording?
        if (recordingLevel > 0) {
            if (--recordingLevel > 0) {
                currentConsumer.endElement(namespaceURI, localName, qName);
                return;
            }
            // Recording finished
            saxFragment = endRecording();
        }

        if (skipLevel > 0) {
            if (--skipLevel > 0) {
                return;
            }
        }

        Tag tag = (Tag) tagStack.pop();
        if (tag != null) {
            ComponentSelector tagSelector = (ComponentSelector) tagSelectorStack.pop();
            try {
                if (saxFragment != null) {
                    // Start Iteration
                    IterationTag iterTag = (IterationTag) tag;
                    XMLDeserializer xmlDeserializer = null;
                    try {
                        xmlDeserializer = (XMLDeserializer) manager.lookup(XMLDeserializer.ROLE);
                        xmlDeserializer.setConsumer(this);

                        // BodyTag Support
                        XMLConsumer backup = this.currentConsumer;
                        if (tag instanceof BodyTag) {
                            SaxBuffer content = new SaxBuffer();
                            this.currentConsumer = content;
                            ((BodyTag) tag).setBodyContent(new BodyContent(content, backup));
                            ((BodyTag) tag).doInitBody();
                        }

                        do {
                            xmlDeserializer.deserialize(saxFragment);
                        } while (iterTag.doAfterBody() != Tag.SKIP_BODY);

                        // BodyTag Support
                        if (tag instanceof BodyTag) {
                            this.currentConsumer = backup;
                        }

                    } catch (ServiceException e) {
                        throw new SAXException("Can't obtain XMLDeserializer", e);
                    } finally {
                        if (xmlDeserializer != null) {
                            manager.release(xmlDeserializer);
                        }
                    }
                }
                tag.doEndTag(namespaceURI, localName, qName);
                currentTag = tag.getParent();

                if (tag == this.currentConsumer) {
                    popConsumer();
                }
            } finally {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("endElement: Release tag " + qName);
                }

                tagSelector.release(tag);
                tagNamespaceSelector.release(tagSelector);

                if (transformerSelector != null && tag instanceof XMLProducer) {
                    getLogger().debug("endElement: Release transformer");
                    Transformer transformer = (Transformer) tagTransformerStack.pop();
                    transformerSelector.release(transformer);
                }
            }
        } else {
            this.currentConsumer.endElement(namespaceURI, localName, qName);
        }
    }

    public void startEntity(String name) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.startEntity(name);
    }

    public void endEntity(String name) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.endEntity(name);
    }

    public void skippedEntity(String name) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.skippedEntity(name);
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.characters(ch, start, length);
    }

    public void comment(char[] ch, int start, int length) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.comment(ch, start, length);
    }

    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
        // If we are skipping the body of a tag, ignore this...
        if (this.skipLevel > 0) {
            return;
        }

        this.currentConsumer.ignorableWhitespace(ch, start, length);
    }

    //
    // Internal Implementation Methods
    //

    private void setupTag(Tag tag, String name, Attributes atts) throws SAXException {
        // Set Tag Parent
        tag.setParent(this.currentTag);

        // Set Tag XML Consumer
        if (tag instanceof XMLProducer) {
            XMLConsumer tagConsumer;
            if (transformerSelector != null) {
                Transformer tagTransformer = null;
                try {
                    // Add additional (Tag)Transformer to the output of the Tag
                    tagTransformer = (Transformer) transformerSelector.select(transformerHint);
                    tagTransformerStack.push(tagTransformer);
                    tagTransformer.setConsumer(currentConsumer);
                    tagTransformer.setup(this.resolver, this.objectModel, null, this.parameters);
                } catch (SAXException e) {
                    throw e;
                } catch (Exception e) {
                    throw new SAXException("Failed to setup tag transformer " + transformerHint, e);
                }
                tagConsumer = tagTransformer;
            } else {
                tagConsumer = this.currentConsumer;
            }

            ((XMLProducer) tag).setConsumer(tagConsumer);
        }

        // Setup Tag
        try {
            tag.setup(this.resolver, this.objectModel, this.parameters);
        } catch (IOException e) {
            throw new SAXException("Could not set up tag " + name, e);
        }

        if (tag instanceof XMLConsumer) {
            this.currentConsumer = (XMLConsumer) tag;
        }
        this.currentTag = tag;

        // Set Tag-Attributes, Attributes are mapped to the coresponding Tag method
        for (int i = 0; i < atts.getLength(); i++) {
            String attributeName = atts.getLocalName(i);
            String attributeValue = atts.getValue(i);
            this.paramArray[0] = attributeValue;
            try {
                Method method = getWriteMethod(tag.getClass(), attributeName);
                method.invoke(tag, this.paramArray);
            } catch (Throwable e) {
                if (getLogger().isInfoEnabled()) {
                    getLogger().info("Tag " + name + " attribute " + attributeName + " not set", e);
                }
            }
        }
    }

    /**
     * Start recording for the iterator tag.
     */
    private void startRecording() throws SAXException {
        try {
            this.xmlSerializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE);
        } catch (ServiceException e) {
            throw new SAXException("Can't lookup XMLSerializer", e);
        }

        this.currentConsumerBackup = this.currentConsumer;
        this.currentConsumer = this.xmlSerializer;
        this.recordingLevel = 1;
    }

    /**
     * End recording for the iterator tag and returns recorded XML fragment.
     */
    private Object endRecording() {
        // Restore XML Consumer
        this.currentConsumer = this.currentConsumerBackup;
        this.currentConsumerBackup = null;

        // Get XML Fragment
        Object saxFragment = this.xmlSerializer.getSAXFragment();

        // Release Serializer
        this.manager.release(this.xmlSerializer);
        this.xmlSerializer = null;

        return saxFragment;
    }

    /**
     * Find previous XML consumer when processing of current consumer
     * is complete.
     */
    private void popConsumer() {
        Tag loop = this.currentTag;
        for (; loop != null; loop = loop.getParent()) {
            if (loop instanceof XMLConsumer) {
                this.currentConsumer = (XMLConsumer) loop;
                return;
            }
        }

        this.currentConsumer = this.xmlConsumer;
    }

    private static Method getWriteMethod(Class type, String propertyName) throws IntrospectionException {
        Map map = getWriteMethodMap(type);
        Method method = (Method) map.get(propertyName);
        if (method == null) {
            throw new IntrospectionException("No such property: " + propertyName);
        }
        return method;
    }

    private static Map getWriteMethodMap(Class beanClass) throws IntrospectionException {
        Map map = (Map) TAG_PROPERTIES_MAP.get(beanClass);
        if (map != null) {
            return map;
        }

        BeanInfo info = Introspector.getBeanInfo(beanClass);
        if (info != null) {
            PropertyDescriptor pds[] = info.getPropertyDescriptors();
            map = new HashMap(pds.length * 4 / 3, 1);
            for (int i = 0; i < pds.length; i++) {
                PropertyDescriptor pd = pds[i];
                String name = pd.getName();
                Method method = pd.getWriteMethod();
                Class type = pd.getPropertyType();
                if (type != String.class) // only String properties
                    continue;
                map.put(name, method);
            }
        }
        TAG_PROPERTIES_MAP.put(beanClass, map);
        return map;
    }
}