org.apache.cocoon.generation.ServerPagesGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cocoon.generation.ServerPagesGenerator.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.generation;

import org.apache.commons.collections.ArrayStack;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
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.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.language.generator.ProgramGenerator;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceValidity;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.Serializable;
import java.util.Map;

/**
 * This class acts as a proxy to a dynamically loaded<code>Generator</code>
 * delegating actual SAX event generation.
 * <p>
 * It has a single configuration item :
 * <code>&lt;autocomplete-documents&gttrue|false&lt;autocomplete-documents&gt;</code>
 * (default is <code>false</code>).
 * <p>
 * This tells the generator to automatically close all elements that weren't properly closed
 * by the XSP, such as when a <code>return</code> statement is used to prematurely end
 * processing. Activating this feature <em>sensibly increases CPU-usage</em> and should
 * therefore be used only if really needed (it's better to have clean XSP pages that don't
 * break abruptly generation flow).
 *
 * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
 * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
 * @version CVS $Id: ServerPagesGenerator.java 30932 2004-07-29 17:35:38Z vgritsenko $
 */
public class ServerPagesGenerator extends ServletGenerator
        implements Disposable, CacheableProcessingComponent, Configurable {
    /**
     * The sitemap-defined server pages program generator
     */
    protected ProgramGenerator programGenerator = null;

    protected AbstractServerPage generator = null;

    /** The source */
    private Source inputSource;

    private CompletionPipe completionPipe;

    /**
     * Set the global component manager. This method sets the sitemap-defined
     * program generator
     *
     * @param manager The global component manager
     */
    public void compose(ComponentManager manager) throws ComponentException {
        super.compose(manager);

        if (programGenerator == null) {
            this.programGenerator = (ProgramGenerator) manager.lookup(ProgramGenerator.ROLE);
        }
    }

    public void configure(Configuration config) throws ConfigurationException {
        boolean autoComplete = config.getChild("autocomplete-documents").getValueAsBoolean(false);

        if (autoComplete) {
            this.completionPipe = new CompletionPipe();
            this.completionPipe.enableLogging(getLogger());
        }

        this.markupLanguage = config.getChild("markup-language").getValue(DEFAULT_MARKUP_LANGUAGE);
        this.programmingLanguage = config.getChild("programming-language").getValue(DEFAULT_PROGRAMMING_LANGUAGE);
    }

    /**
     * Generate the unique key.
     * This key must be unique inside the space of this component.
     * This method must be invoked before the generateValidity() method.
     *
     * @return The generated key or <code>null</code> if the component
     *         is currently not cacheable.
     */
    public Serializable getKey() {
        Object key = generator.getKey();
        if (key == null) {
            return this.inputSource.getURI();
        }
        return this.inputSource.getURI() + '-' + key;
    }

    /**
     * Generate the validity object.
     * Before this method can be invoked the generateKey() method
     * must be invoked.
     *
     * @return The generated validity object or <code>null</code> if the
     *         component is currently not cacheable.
     */
    public SourceValidity getValidity() {
        // VG: Input source's systemID is part of the key,
        // and need not be included into the validity.
        return generator.getValidity();
    }

    /**
     * The loaded generator's <code>MarkupLanguage</code>
     */
    protected String markupLanguage;

    /**
     * The loaded generator's <code>ProgrammingLanguage</code>
     */
    protected String programmingLanguage;

    /**
     * The default <code>MarkupLanguage</code>
     */
    public final static String DEFAULT_MARKUP_LANGUAGE = "xsp";

    /**
     * The default <code>ProgrammingLanguage</code>
     */
    public final static String DEFAULT_PROGRAMMING_LANGUAGE = "java";

    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
            throws ProcessingException, SAXException, IOException {

        super.setup(resolver, objectModel, src, par);

        String markupLanguage = this.parameters.getParameter("markup-language", this.markupLanguage);
        String programmingLanguage = this.parameters.getParameter("programming-language", this.programmingLanguage);

        try {
            this.inputSource = this.resolver.resolveURI(src);
        } catch (SourceException se) {
            throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
            //throw SourceUtil.handle(se);
        }

        try {
            this.generator = (AbstractServerPage) programGenerator.load(this.manager, this.inputSource,
                    markupLanguage, programmingLanguage, this.resolver);
        } catch (ProcessingException e) {
            throw e;
        } catch (Exception e) {
            getLogger().warn("setup()", e);
            throw new ProcessingException(e.getMessage(), e);
        } catch (NoClassDefFoundError e) {
            // VG: Usually indicates that page invoked with the wrong case.
            // I.e., it was compiled as "my.xsp" and inoked as "My.xsp",
            // results in different class name and an error.
            getLogger().warn("Failed to load class: " + e);
            throw new ResourceNotFoundException(e.getMessage());
        }

        // Give our own logger to the generator so that logs go in the correct category
        generator.enableLogging(getLogger());

        generator.setup(super.resolver, super.objectModel, super.source, super.parameters);
    }

    /**
     * Generate XML data. This method loads a server pages generator associated
     * with its (file) input source and delegates SAX event generator to it
     * taking care of "closing" any event left open by the loaded generator as a
     * result of its possible "premature" return (a common situation in server
     * pages)
     *
     * @exception IOException IO Error
     * @exception SAXException SAX event generation error
     * @exception ProcessingException Error during load/execution
     */
    public void generate() throws IOException, SAXException, ProcessingException {

        if (this.completionPipe != null) {
            generator.setConsumer(this.completionPipe);
            if (this.xmlConsumer != null) {
                this.completionPipe.setConsumer(this.xmlConsumer);
            } else {
                this.completionPipe.setContentHandler(this.contentHandler);
                this.completionPipe.setLexicalHandler(this.lexicalHandler);
            }
        } else {
            if (this.xmlConsumer != null) {
                generator.setConsumer(this.xmlConsumer);
            } else {
                generator.setContentHandler(this.contentHandler);
                generator.setLexicalHandler(this.lexicalHandler);
            }
        }

        // Fixes BUG#4062: Set document locator which is used by XIncludeTransformer
        org.xml.sax.helpers.LocatorImpl locator = new org.xml.sax.helpers.LocatorImpl();
        locator.setSystemId(this.inputSource.getURI());
        this.contentHandler.setDocumentLocator(locator);

        // Log exception and ensure that generator is released.
        try {
            generator.generate();
        } catch (IOException e) {
            getLogger().debug("IOException in generate()", e);
            throw e;
        } catch (SAXException e) {
            getLogger().debug("SAXException in generate()", e);
            throw e;
        } catch (ProcessingException e) {
            getLogger().debug("ProcessingException in generate()", e);
            throw e;
        } catch (Exception e) {
            getLogger().debug("Exception in generate()", e);
            throw new ProcessingException("Exception in ServerPagesGenerator.generate()", e);
        } finally {
            if (generator != null) {
                programGenerator.release(generator);
            }
            generator = null;
        }

        if (this.completionPipe != null) {
            this.completionPipe.flushEvents();
        }
    }

    /**
     * Recycle the generator by removing references
     */
    public void recycle() {
        if (this.generator != null) {
            this.programGenerator.release(this.generator);
            this.generator = null;
        }
        if (this.inputSource != null) {
            this.resolver.release(this.inputSource);
            this.inputSource = null;
        }
        if (this.completionPipe != null) {
            this.completionPipe.recycle();
            this.completionPipe = null;
        }
        super.recycle();
    }

    /**
     * dispose
     */
    public void dispose() {
        this.manager.release(this.programGenerator);
        this.programGenerator = null;
        this.manager = null;
    }

    /* Completion pipe */

    // int values for event types
    private final static int DOCUMENT = 0;
    private final static int ELEMENT = 1;
    private final static int PREFIX_MAPPING = 2;
    private final static int CDATA = 3;
    private final static int DTD = 4;
    private final static int ENTITY = 5;

    // Integer equivalents to push on the stack
    private final static Integer DOCUMENT_OBJ = new Integer(DOCUMENT);
    private final static Integer ELEMENT_OBJ = new Integer(ELEMENT);
    private final static Integer PREFIX_MAPPING_OBJ = new Integer(PREFIX_MAPPING);
    private final static Integer CDATA_OBJ = new Integer(CDATA);
    private final static Integer DTD_OBJ = new Integer(DTD);
    private final static Integer ENTITY_OBJ = new Integer(ENTITY);

    public class CompletionPipe extends AbstractXMLPipe {

        /**
         * The SAX event stack. Used for "completing" pendind SAX events left "open"
         * by prematurely returning server pages generators
         */
        protected ArrayStack eventStack = new ArrayStack();

        /**
         * Receive notification of the beginning of a document.
         */
        public void startDocument() throws SAXException {
            super.startDocument();
            this.eventStack.push(DOCUMENT_OBJ);
        }

        /**
         * Receive notification of the end of a document.
         */
        public void endDocument() throws SAXException {
            this.eventStack.pop();
            super.endDocument();
        }

        /**
         * Receive notification of the beginning of an element.
         */
        public void startElement(String namespaceURI, String localName, String rawName, Attributes atts)
                throws SAXException {
            super.startElement(namespaceURI, localName, rawName, atts);
            this.eventStack.push(rawName);
            this.eventStack.push(localName);
            this.eventStack.push(namespaceURI);
            this.eventStack.push(ELEMENT_OBJ);
        }

        /**
         * Receive notification of the end of an element.
         */
        public void endElement(String namespaceURI, String localName, String rawName) throws SAXException {
            this.eventStack.pop(); // ELEMENT_OBJ
            this.eventStack.pop(); // namespaceURI
            this.eventStack.pop(); // localName
            this.eventStack.pop(); // rawName
            super.endElement(namespaceURI, localName, rawName);
        }

        /**
         * Begin the scope of a prefix-URI Namespace mapping.
         */
        public void startPrefixMapping(String prefix, String uri) throws SAXException {
            super.startPrefixMapping(prefix, uri);
            this.eventStack.push(prefix);
            this.eventStack.push(PREFIX_MAPPING_OBJ);
        }

        /**
         * End the scope of a prefix-URI mapping.
         */
        public void endPrefixMapping(String prefix) throws SAXException {
            this.eventStack.pop(); // PREFIX_MAPPING_OBJ
            this.eventStack.pop(); // prefix
            super.endPrefixMapping(prefix);
        }

        public void startCDATA() throws SAXException {
            super.startCDATA();
            this.eventStack.push(CDATA_OBJ);
        }

        public void endCDATA() throws SAXException {
            this.eventStack.pop();
            super.endCDATA();
        }

        public void startDTD(String name, String publicId, String systemId) throws SAXException {
            super.startDTD(name, publicId, systemId);
            this.eventStack.push(DTD_OBJ);
        }

        public void endDTD() throws SAXException {
            this.eventStack.pop();
            super.endDTD();
        }

        public void startEntity(String name) throws SAXException {
            super.startEntity(name);
            this.eventStack.push(name);
            this.eventStack.push(ENTITY_OBJ);
        }

        public void endEntity(String name) throws SAXException {
            this.eventStack.pop(); // ENTITY_OBJ
            this.eventStack.pop(); // name
            super.endEntity(name);
        }

        public void flushEvents() throws SAXException {

            if (this.getLogger().isWarnEnabled()) {
                if (this.eventStack.size() > 0) {
                    this.getLogger().warn("Premature end of document generated by " + inputSource.getURI());
                }
            }

            // End any started events in case of premature return
            while (this.eventStack.size() != 0) {
                int event = ((Integer) eventStack.pop()).intValue();

                switch (event) {
                case DOCUMENT:
                    super.endDocument();
                    break;

                case ELEMENT:
                    String namespaceURI = (String) eventStack.pop();
                    String localName = (String) eventStack.pop();
                    String rawName = (String) eventStack.pop();
                    super.endElement(namespaceURI, localName, rawName);
                    break;

                case PREFIX_MAPPING:
                    super.endPrefixMapping((String) eventStack.pop());
                    break;

                case CDATA:
                    super.endCDATA();
                    break;

                case DTD:
                    super.endDTD();
                    break;

                case ENTITY:
                    super.endEntity((String) eventStack.pop());
                    break;
                }
            }
        }

        public void recycle() {
            this.eventStack.clear();
            super.recycle();
        }
    }
}