Package org.apache.cocoon.components.source.impl

Source Code of org.apache.cocoon.components.source.impl.CachingSource$TraversableSourceMeta

/*
* 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.components.source.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;

import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
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.Serviceable;
import org.apache.cocoon.CascadingIOException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.Cache;
import org.apache.cocoon.caching.IdentifierCacheKey;
import org.apache.cocoon.components.sax.XMLDeserializer;
import org.apache.cocoon.components.sax.XMLSerializer;
import org.apache.cocoon.xml.ContentHandlerWrapper;
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.SourceResolver;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.TraversableSource;
import org.apache.excalibur.source.impl.validity.ExpiresValidity;
import org.apache.excalibur.source.impl.validity.TimeStampValidity;
import org.apache.excalibur.xml.sax.XMLizable;
import org.apache.excalibur.xmlizer.XMLizer;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
* This class implements a proxy like source that uses another source
* to get the content. This implementation can cache the content for
* a given period of time
*
* <h2>Syntax for Protocol</h2>
* <p>
* cached:http://www.apache.org/[?cocoon:cache-expires=60&cocoon:cache-name=main]
* </p>
* <p>
* The above examples show how the real source <code>http://www.apache.org</code>
* is wrapped and the cached contents is used for <code>60</code> seconds.
* The second querystring parameter instructs that the cache key be extended with the string
* <code>main</code>. This allows the use of multiple cache entries for the same source.
* </p>
* <p>
* The value of the expires parameter holds some additional semantics.
* Specifying <code>-1</code> will yield the cached response to be considered valid
* always. <code>0</code> can be used to achieve the exact opposite. That is to say,
* the cached contents will be thrown out and updated immediately and unconditionally.
* <p>
* @version CVS $Id: CachingSource.java,v 1.11 2004/04/15 08:05:56 cziegeler Exp $
*/
public class CachingSource extends AbstractLogEnabled
implements Source, Serviceable, Initializable, XMLizable {
   
    /** The ServiceManager */
    protected ServiceManager manager;
   
    /** The SourceResolver to resolve the wrapped Source */
    protected SourceResolver resolver;
   
    /** The current cache */
    protected Cache cache;
   
    /** The refresher for asynchronous updates */
    protected Refresher refresher;
   
    /** The source object for the real content */
    protected Source source;
   
    /** The cached response (if any) */
    protected CachedSourceResponse response;
   
    /** Did we just update meta info? */
    protected boolean freshMeta;
   
    /** The full location string */
    final protected String uri;
   
    /** The used protocol */
    final protected String protocol;
   
    /** The key used in the store */
    final protected IdentifierCacheKey cacheKey;
   
    /** number of seconds before cached object becomes invalid */
    final protected int expires;
   
    /** Parameters */
    final protected Parameters parameters;
   
    /** asynchronic refresh strategy ? */
    final protected boolean async;
   
    /**
     * Construct a new object.
     */
    public CachingSource(final String protocol,
                         final String uri,
                         final Source source,
                         final Parameters parameters,
                         final int expires,
                         final boolean async) {
        this.protocol = protocol;
        this.uri = uri;
        this.source = source;
        this.expires = expires;
        this.async = async;
        this.parameters = parameters;
       
        String key = "source:" + source.getURI();
        String cacheName = parameters.getParameter("cache-name", null);
        if (cacheName != null) {
            key += ":" + cacheName;
        }
        this.cacheKey = new IdentifierCacheKey(key, false);
    }
       
    /**
     * Set the ServiceManager.
     */
    public void service(final ServiceManager manager) throws ServiceException {
        this.manager = manager;
    }
   
    /**
     * Initialize the Source.
     */
    public void initialize() throws Exception {
       
        boolean checkValidity = true;
        if (this.expires == -1) {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Using cached response if available.");
            }
            checkValidity = false;
        }
       
        if (this.async && this.expires != 0) {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Not invalidating cached response " +
                    "for asynch source " + getSourceURI());
            }
            checkValidity = false;
        }

        this.response = (CachedSourceResponse) this.cache.get(this.cacheKey);
        if (this.response == null) {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("No cached response found " +
                    "for source " + getSourceURI());
            }
            checkValidity = false;
        }
       
        if (checkValidity) {
           
            final ExpiresValidity cacheValidity = (ExpiresValidity) this.response.getValidityObjects()[0];
            final SourceValidity sourceValidity = this.response.getValidityObjects()[1];
           
            boolean remove = false;
            if (this.expires == 0) {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Force invalidation of cached response" +
                        " of source " + getSourceURI());
                }
                remove = true;
            }
            else {
                boolean expired = cacheValidity.isValid() != SourceValidity.VALID;
                if (expired) {
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug("Cached response of source "
                            + getSourceURI() + " is expired.");
                    }
                    boolean invalid = !isValid(sourceValidity, this.source);
                    if (invalid) {
                        if (getLogger().isDebugEnabled()) {
                            getLogger().debug("Cached response of source "
                                + getSourceURI() + " is invalid.");
                        }
                        remove = true;
                    }
                    else {
                        if (getLogger().isDebugEnabled()) {
                            getLogger().debug("Cached response of source "
                                + getSourceURI() + " is still valid.");
                        }
                        // set new expiration period
                        this.response.getValidityObjects()[0] = new ExpiresValidity(getExpiration());
                    }
                }
            }
           
            if (remove) {
                this.response = null;
                // remove it if it no longer exists
                if (!exists()) {
                    this.cache.remove(this.cacheKey);
                }
            }
        }
        if (this.async && this.expires > 0) {
            // schedule it with the refresher
            this.refresher.refresh(this.cacheKey,
                                   getSourceURI(),
                                   this.parameters.getParameter("cache-role", null),
                                   this.parameters);
        }
    }
   
    /**
     * Cleanup.
     */
    public void dispose() {
        if (this.source != null) {
            this.resolver.release(this.source);
            this.source = null;
        }
        this.manager = null;
        this.resolver = null;
        this.cache = null;
    }
   
    /**
     * Initialize the cached response with meta info.
     *
     * @throws IOException  if an the binary response could not be initialized
     */
    protected void initMetaResponse() throws IOException {
        boolean storeResponse = false;
        CachedSourceResponse response = this.response;
        if (response == null) {
            if (this.expires != 0) {
                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
                final SourceValidity sourceValidity = source.getValidity();
                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
                storeResponse = true;
            }
            else {
                response = new CachedSourceResponse(null);
            }
        }
        if (response.getExtra() == null) {
            response.setExtra(readMeta(this.source));
            this.freshMeta = true;
        }
        if (storeResponse) {
            this.response = response;
            try {
                this.cache.store(this.cacheKey, this.response);
            }
            catch(ProcessingException e) {
                throw new CascadingIOException("Failure storing response.", e);
            }
        }
    }
   
    /**
     * Initialize the cached response with binary contents.
     *
     * @throws IOException  if an the binary response could not be initialized
     */
    protected void initBinaryResponse() throws IOException {
        boolean storeResponse = false;
        /* delay caching the response until we have a valid new one */
        CachedSourceResponse response = this.response;
        if (response == null) {
            if (this.expires != 0) {
                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
                final SourceValidity sourceValidity = source.getValidity();
                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
                storeResponse = true;
            }
            else {
                response = new CachedSourceResponse(null);
            }
        }
        if (response.getBinaryResponse() == null) {
            response.setBinaryResponse(readBinaryResponse(this.source));
            if (!this.freshMeta) {
                /* always refresh meta in this case */
                response.setExtra(readMeta(this.source));
            }
        }
        if (storeResponse) {
            this.response = response;
            try {
                this.cache.store(this.cacheKey, this.response);
            }
            catch(ProcessingException e) {
                throw new CascadingIOException("Failure storing response.", e);
            }
        }
    }
   
    /**
     * Initialize the cached response with XML contents.
     *
     * @param refresh  whether to force refresh.
     * @throws SAXException  if something happened during xml processing
     * @throws IOException  if an IO level error occured
     * @throws CascadingIOException  wraps all other exception types
     */
    protected void initXMLResponse(boolean refresh) throws SAXException, IOException, CascadingIOException {
        boolean storeResponse = false;
        /* delay caching the response until we have a valid new one */
        CachedSourceResponse response = this.response;
        if (response == null) {
            if (this.expires != 0) {
                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
                final SourceValidity sourceValidity = source.getValidity();
                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
                storeResponse = true;
            }
            else {
                response = new CachedSourceResponse(null);
            }
        }
        if (response.getXMLResponse() == null || refresh) {
            byte[] binary = response.getBinaryResponse();
            response.setXMLResponse(readXMLResponse(this.source, binary, this.manager));
            if (!this.freshMeta) {
                /* always refresh meta in this case */
                response.setExtra(readMeta(this.source));
            }
        }
        if (storeResponse) {
            this.response = response;
            try {
                this.cache.store(this.cacheKey, this.response);
            }
            catch(ProcessingException e) {
                throw new CascadingIOException("Failure storing response.", e);
            }
        }
    }
   
    // ---------------------------------------------------- Source implementation
   
    /**
     * Return the protocol identifier.
     */
    public String getScheme() {
        return this.protocol;
    }

    /**
     * Get the content length of the source or -1 if it
     * is not possible to determine the length.
     */
    public long getContentLength() {
        return -1;
    }

    /**
     * Get the last modification date.
     * @return The last modification in milliseconds since January 1, 1970 GMT
     *         or 0 if it is unknown
     */
    public long getLastModified() {
        try {
            initMetaResponse();
        } catch (IOException io) {
            return 0;
        }
        return ((SourceMeta) this.response.getExtra()).getLastModified();
    }

    /**
     * The mime-type of the content described by this object.
     * If the source is not able to determine the mime-type by itself
     * this can be null.
     */
    public String getMimeType() {
        try {
            initMetaResponse();
        } catch (IOException io) {
            return null;
        }
        return ((SourceMeta) this.response.getExtra()).getMimeType();
    }
   
    /**
     * Return an <code>InputStream</code> object to read from the source.
     */
    public InputStream getInputStream() throws IOException, SourceException {
        try {
            initBinaryResponse();
        } catch (IOException se) {
            throw new SourceException("Failure getting input stream", se);
        }
        return new ByteArrayInputStream(this.response.getBinaryResponse());
    }

    /**
     * Return the unique identifer for this source
     */
    public String getURI() {
        return this.uri;
    }

    /**
     * @see org.apache.excalibur.source.Source#exists()
     */
    public boolean exists() {
    return this.source.exists();
    }
   
    /**
     *  Get the Validity object. This can either wrap the last modification
     *  date or the expires information or...
     *  If it is currently not possible to calculate such an information
     *  <code>null</code> is returned.
     */
    public SourceValidity getValidity() {
        long lastModified = getLastModified();
        if (lastModified > 0) {
            return new TimeStampValidity(lastModified);
        }
        return null;
    }
    
    /**
     * Refresh this object and update the last modified date
     * and content length.
     */
    public void refresh() {
        this.response = null;
        this.source.refresh();
    }
   
    // ---------------------------------------------------- XMLizable implementation
   
    /**
     * Generates SAX events representing the object's state.
     */
    public void toSAX(ContentHandler contentHandler) throws SAXException {
        XMLDeserializer deserializer = null;
        try {
            initXMLResponse(false);
            deserializer = (XMLDeserializer) this.manager.lookup(XMLDeserializer.ROLE);
            if (contentHandler instanceof XMLConsumer) {
                deserializer.setConsumer((XMLConsumer) contentHandler);
            } else {
                deserializer.setConsumer(new ContentHandlerWrapper(contentHandler));
            }
            deserializer.deserialize(this.response.getXMLResponse());
        } catch(CascadingIOException e) {
            throw new SAXException(e.getMessage(), (Exception) e.getCause());
        } catch(IOException e) {
            throw new SAXException("Failure reading SAX response.", e);
        } catch (ServiceException se ) {
            throw new SAXException("Missing service dependency: XMLdeserializer.", se);
        } finally {
            this.manager.release(deserializer);
        }
    }
   
    // ---------------------------------------------------- CachingSource specific accessors
   
    /**
     * Return the uri of the cached source.
     */
    protected String getSourceURI() {
        return this.source.getURI();
    }
   
    /**
     * Return the used key.
     */
    protected IdentifierCacheKey getCacheKey() {
        return this.cacheKey;
    }
   
    /**
     * Expires (in milli-seconds)
     */
    protected long getExpiration() {
        return this.expires * 1000;
    }
   
    /**
     * Read XML content from source.
     *
   * @return content from source
   * @throws SAXException
   * @throws IOException
   * @throws CascadingIOException
   */
  protected static byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager)
    throws SAXException, IOException, CascadingIOException {
        XMLSerializer serializer = null;
        XMLizer xmlizer = null;
        byte[] result = null;
    try {
        serializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE);
           
            if (source instanceof XMLizable) {
                ((XMLizable) source).toSAX(serializer);
            }
            else {
                if (binary == null) {
                    binary = readBinaryResponse(source);
                }
                final String mimeType = source.getMimeType();
                if (mimeType != null) {
                    xmlizer = (XMLizer) manager.lookup(XMLizer.ROLE);
                    xmlizer.toSAX(new ByteArrayInputStream(binary),
                                  mimeType,
                                  source.getURI(),
                                  serializer);
                }
            }
        result = (byte[]) serializer.getSAXFragment();
    } catch (ServiceException se) {
        throw new CascadingIOException("Missing service dependency.", se);
    } finally {
            manager.release(xmlizer);
        manager.release(serializer);
    }
    return result;
  }

  /**
     * Read binary content from source.
     *
   * @return content from source
   * @throws IOException
   * @throws SourceNotFoundException
   */
  protected static byte[] readBinaryResponse(Source source)
    throws IOException, SourceNotFoundException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final byte[] buffer = new byte[2048];
    final InputStream inputStream = source.getInputStream();
    int length;
    while ((length = inputStream.read(buffer)) > -1) {
        baos.write(buffer, 0, length);
    }
    baos.flush();
    inputStream.close();
    return baos.toByteArray();
  }
   
    /**
     * Read meta data from source.
     *
     * @return source meta data
     * @throws IOException
     */
    protected static SourceMeta readMeta(Source source) throws IOException {
        SourceMeta meta;
       
        if (source instanceof TraversableSource) {
           
            final TraversableSourceMeta tmeta = new TraversableSourceMeta();
            final TraversableSource tsource = (TraversableSource) source;
           
            tmeta.setName(tsource.getName());
            tmeta.setIsCollection(tsource.isCollection());
           
            if (tmeta.isCollection()) {
                final Collection children = tsource.getChildren();
                if (children != null) {
                    final String[] names = new String[children.size()];
                    final Iterator iter = children.iterator();
                    int count = 0;
                    while(iter.hasNext()) {
                        TraversableSource child = (TraversableSource) iter.next();
                        names[count] = child.getName();
                        count++;
                    }
                    tmeta.setChildren(names);
                }
            }
           
            meta = tmeta;
        }
        else {
            meta = new SourceMeta();
        }
       
        final long lastModified = source.getLastModified();
        if (lastModified > 0) {
            meta.setLastModified(lastModified);
        }
        else {
            meta.setLastModified(System.currentTimeMillis());
        }
        meta.setMimeType(source.getMimeType());
       
        return meta;
    }
   
    protected static boolean isValid(SourceValidity validity, Source source) {
        if (validity == null) return false;
        return validity.isValid() == SourceValidity.VALID ||
              (validity.isValid() == SourceValidity.UNKNOWN &&
               validity.isValid(source.getValidity()) == SourceValidity.VALID);
    }
   
    /**
     * Data holder for caching Source meta info.
     */
    protected static class SourceMeta implements Serializable {
       
        private String m_mimeType;
        private long m_lastModified;
        private boolean m_exists;
       
        protected String getMimeType() {
            return m_mimeType;
        }
       
        protected void setMimeType(String mimeType) {
            m_mimeType = mimeType;
        }
       
        protected long getLastModified() {
            return m_lastModified;
        }
       
        protected void setLastModified(long lastModified) {
            m_lastModified = lastModified;
        }
       
        protected boolean exists() {
            return m_exists;
        }
       
        protected void setExists(boolean exists) {
            m_exists = exists;
        }
       
    }
   
    protected static class TraversableSourceMeta extends SourceMeta {
        private String   m_name;
        private boolean  m_isCollection;
        private String[] m_children;
       
        protected String getName() {
            return m_name;
        }
       
        protected void setName(String name) {
            m_name = name;
        }
       
        protected boolean isCollection() {
            return m_isCollection;
        }
       
        protected void setIsCollection(boolean isCollection) {
            m_isCollection = isCollection;
        }
       
        protected String[] getChildren() {
            return m_children;
        }
       
        protected void setChildren(String[] children) {
            m_children = children;
        }
    }

}
TOP

Related Classes of org.apache.cocoon.components.source.impl.CachingSource$TraversableSourceMeta

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.