Package org.apache.cocoon.serialization

Source Code of org.apache.cocoon.serialization.ZipArchiveSerializer

/*
* 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.serialization;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.avalon.framework.activity.Disposable;
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.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.NamespaceSupport;

/**
* A serializer that builds Zip archives by aggregating several sources.
*
* <p>
* The input document should describe entries of the archive by means of
* their name (which can be a path) and their content either as URLs or
* inline data :
* <ul>
* <li>URLs, given by the "src" attribute, are Cocoon sources and as such
*     can use any of the protocols handled by Cocoon, including "cocoon:" to
*     include dynamically generated content in the archive.</li>
* <li>inline data is represented by an XML document that is serialized to the
*     zip entry using the serializer identified by the "serializer" attribute.</li>
* </ul>
* <p>
* Example :
* <pre>
*   &lt;zip:archive xmlns:zip="http://apache.org/cocoon/zip-archive/1.0"&gt;
*     &lt;zip:entry name="foo.html" src="cocoon://dynFoo.html"/&gt;
*     &lt;zip:entry name="images/bar.jpeg" src="bar.jpeg"/&gt;
*     &lt;zip:entry name="index.html" serializer="html"&gt;
*       &lt;html&gt;
*         &lt;head&gt;
*           &lt;title&gt;Index page&lt;/title&gt;
*         &lt;/head&gt;
*         &lt;body&gt;
*           Please go &lt;a href="foo.html"&gt;there&lt;/a&gt;
*         &lt;/body&lt;
*       &lt;/html&gt;
*     &lt;/zip:entry&gt;
*   &lt;/zip:archive:zip&gt;
* </pre>
*
* @cocoon.sitemap.component.documentation
* A serializer that builds Zip archives by aggregating several sources.
* @cocoon.sitemap.component.documentation.caching Not Implemented
*
* @version $Id: ZipArchiveSerializer.java 607379 2007-12-29 05:36:49Z vgritsenko $
*/

// TODO (1) : handle more attributes on <archive> for properties of ZipOutputStream
//            such as comment or default compression method and level

// TODO (2) : handle more attributes on <entry> for properties of ZipEntry
//            (compression method and level, time, comment, etc.)

public class ZipArchiveSerializer extends AbstractSerializer
                                  implements Disposable, Serviceable {

    /**
     * The namespace for elements handled by this serializer,
     * "http://apache.org/cocoon/zip-archive/1.0".
     */
    public static final String ZIP_NAMESPACE = "http://apache.org/cocoon/zip-archive/1.0";

    private static final int START_STATE = 0;
    private static final int IN_ZIP_STATE = 1;
    private static final int IN_CONTENT_STATE = 2;

    /** The component manager */
    protected ServiceManager manager;

    /** The serializer component selector */
    protected ServiceSelector selector;

    /** The Zip stream where entries will be written */
    protected ZipOutputStream zipOutput;

    /** The current state */
    protected int state = START_STATE;

    /** The resolver to get sources */
    protected SourceResolver resolver;

    /** Temporary byte buffer to read source data */
    protected byte[] buffer;

    /** Serializer used when in IN_CONTENT state */
    protected Serializer serializer;

    /** Current depth of the serialized content */
    protected int contentDepth;

    /** Used to collect namespaces */
    private NamespaceSupport nsSupport = new NamespaceSupport();

    /**
     * Store exception
     */
    private SAXException exception;


    /**
     * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
     */
    public void service(ServiceManager manager) throws ServiceException {
        this.manager = manager;
        this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
    }

    /**
     * Returns default mime type for zip archives, <code>application/zip</code>.
     * Can be overridden in the sitemap.
     * @return application/zip
     */
    public String getMimeType() {
        return "application/zip";
    }

    /**
     * @see org.xml.sax.ContentHandler#startDocument()
     */
    public void startDocument() throws SAXException {
        this.state = START_STATE;
        this.zipOutput = new ZipOutputStream(this.output);
    }

    /**
     * Begin the scope of a prefix-URI Namespace mapping.
     *
     * @param prefix The Namespace prefix being declared.
     * @param uri The Namespace URI the prefix is mapped to.
     */
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        if (state == IN_CONTENT_STATE && this.contentDepth > 0) {
            // Pass to the serializer
            super.startPrefixMapping(prefix, uri);

        } else {
            // Register it if it's not our own namespace (useless to content)
            if (!uri.equals(ZIP_NAMESPACE)) {
                this.nsSupport.declarePrefix(prefix, uri);
            }
        }
    }
   
    public void endPrefixMapping(String prefix) throws SAXException {
        if (state == IN_CONTENT_STATE && this.contentDepth > 0) {
            // Pass to the serializer
            super.endPrefixMapping(prefix);
        }
    }

    // Note : no need to implement endPrefixMapping() as we just need to pass it through if there
    // is a serializer, which is what the superclass does.

    /**
     * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
     */
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
        throws SAXException {

        // Damage control. Sometimes one exception is just not enough...
        if (this.exception != null) {
            throw this.exception;
        }

        switch (state) {
            case START_STATE:
                // expecting "zip" as the first element
                if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("archive")) {
                    this.nsSupport.pushContext();
                    this.state = IN_ZIP_STATE;
                } else {
                    throw this.exception =
                        new SAXException("Expecting 'archive' root element (got '" + localName + "')");
                }
                break;

            case IN_ZIP_STATE:
                // expecting "entry" element
                if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("entry")) {
                    this.nsSupport.pushContext();
                    // Get the source
                    addEntry(atts);
                } else {
                    throw this.exception =
                        new SAXException("Expecting 'entry' element (got '" + localName + "')");
                }
                break;

            case IN_CONTENT_STATE:
                if (this.contentDepth == 0) {
                    // Give it any namespaces already declared
                    Enumeration prefixes = this.nsSupport.getPrefixes();
                    while (prefixes.hasMoreElements()) {
                        String prefix = (String) prefixes.nextElement();
                        super.startPrefixMapping(prefix, this.nsSupport.getURI(prefix));
                    }
                }

                this.contentDepth++;
                super.startElement(namespaceURI, localName, qName, atts);
                break;
        }
    }

    /**
     * @see org.xml.sax.ContentHandler#characters(char[], int, int)
     */
    public void characters(char[] buffer, int offset, int length) throws SAXException {
        // Propagate text to the serializer only if we have encountered the content's top-level
        // element. Otherwhise, the serializer may be confused by some characters occuring between
        // startDocument() and the first startElement() (e.g. Batik fails hard in that case)
        if (this.state == IN_CONTENT_STATE && this.contentDepth > 0) {
            super.characters(buffer, offset, length);
        }
    }

    /**
     * Add an entry in the archive.
     * @param atts the attributes that describe the entry
     */
    protected void addEntry(Attributes atts) throws SAXException {
        String name = atts.getValue("name");
        if (name == null) {
            throw this.exception =
                new SAXException("No name given to the Zip entry");
        }

        String src = atts.getValue("src");
        String serializerType = atts.getValue("serializer");

        if (src == null && serializerType == null) {
            throw this.exception =
                new SAXException("No source nor serializer given for the Zip entry '" + name + "'");
        }

        if (src != null && serializerType != null) {
            throw this.exception =
                new SAXException("Cannot specify both 'src' and 'serializer' on a Zip entry '" + name + "'");
        }

        Source source = null;
        try {
            if (src != null) {
                // Get the source and its data
                source = resolver.resolveURI(src);
                InputStream sourceInput = source.getInputStream();

                // Create a new Zip entry with file modification time.
                ZipEntry entry = new ZipEntry(name);
                long lastModified = source.getLastModified();
                if (lastModified != 0)
                  entry.setTime(lastModified);
                this.zipOutput.putNextEntry(entry);
               
                // Buffer lazily allocated
                if (this.buffer == null)
                    this.buffer = new byte[8192];

                // Copy the source to the zip
                int len;
                while ((len = sourceInput.read(this.buffer)) > 0) {
                    this.zipOutput.write(this.buffer, 0, len);
                }

                // and close the entry
                this.zipOutput.closeEntry();
                // close input stream (to avoid "too many open files" problem)
                sourceInput.close();
            } else {
                // Create a new Zip entry with current time.
                ZipEntry entry = new ZipEntry(name);
                this.zipOutput.putNextEntry(entry);

                // Serialize content
                if (this.selector == null) {
                    this.selector =
                        (ServiceSelector) this.manager.lookup(Serializer.ROLE + "Selector");
                }

                // Get the serializer
                this.serializer = (Serializer) this.selector.select(serializerType);

                // Direct its output to the zip file, filtering calls to close()
                // (we don't want the archive to be closed by the serializer)
                this.serializer.setOutputStream(new FilterOutputStream(this.zipOutput) {
                    public void close() { /* nothing */ }
                });

                // Set it as the current XMLConsumer
                setConsumer(serializer);

                // start its document
                this.serializer.startDocument();

                this.state = IN_CONTENT_STATE;
                this.contentDepth = 0;
            }

        } catch (RuntimeException re) {
            throw re;
        } catch (SAXException se) {
            throw this.exception = se;
        } catch (Exception e) {
            throw this.exception = new SAXException(e);
        } finally {
            this.resolver.release( source );
        }
    }

    /**
     * @see org.xml.sax.ContentHandler#endElement(String, String, String)
     */
    public void endElement(String namespaceURI, String localName, String qName)
        throws SAXException {

        // Damage control. Sometimes one exception is just not enough...
        if (this.exception != null) {
            throw this.exception;
        }

        if (state == IN_CONTENT_STATE) {
            super.endElement(namespaceURI, localName, qName);
            this.contentDepth--;

            if (this.contentDepth == 0) {
                // End of this entry

                // close all declared namespaces.
                Enumeration prefixes = this.nsSupport.getPrefixes();
                while (prefixes.hasMoreElements()) {
                    String prefix = (String) prefixes.nextElement();
                    super.endPrefixMapping(prefix);
                }

                super.endDocument();

                try {
                    this.zipOutput.closeEntry();
                } catch (IOException ioe) {
                    throw this.exception = new SAXException(ioe);
                }

                super.setConsumer(null);
                this.selector.release(this.serializer);
                this.serializer = null;

                // Go back to listening for entries
                this.state = IN_ZIP_STATE;
            }
        } else {
            this.nsSupport.popContext();
        }
    }

    /**
     * @see org.xml.sax.ContentHandler#endDocument()
     */
    public void endDocument() throws SAXException {
        try {
            // Close the zip archive
            this.zipOutput.finish();

        } catch (IOException ioe) {
            throw new SAXException(ioe);
        }
    }

    /**
     * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
     */
    public void recycle() {
        this.exception = null;
        if (this.serializer != null) {
            this.selector.release(this.serializer);
        }
        if (this.selector != null) {
            this.manager.release(this.selector);
        }

        this.nsSupport.reset();
        super.recycle();
    }

    /* (non-Javadoc)
     * @see org.apache.avalon.framework.activity.Disposable#dispose()
     */
    public void dispose() {
        if (this.manager != null) {
            this.manager.release(this.resolver);
            this.resolver = null;
            this.manager = null;
        }
    }
}
TOP

Related Classes of org.apache.cocoon.serialization.ZipArchiveSerializer

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.