CachingAttachmentProvider.java :  » Wiki-Engine » JSPWiki » com » ecyrd » jspwiki » providers » Java Open Source

Java Open Source » Wiki Engine » JSPWiki 
JSPWiki » com » ecyrd » jspwiki » providers » CachingAttachmentProvider.java
/* 
    JSPWiki - a JSP-based WikiWiki clone.

    Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.ecyrd.jspwiki.providers;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

import org.apache.log4j.Logger;

import com.ecyrd.jspwiki.*;
import com.ecyrd.jspwiki.attachment.Attachment;
import com.ecyrd.jspwiki.attachment.AttachmentManager;
import com.ecyrd.jspwiki.util.ClassUtil;
import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.base.NeedsRefreshException;

/**
 *  Provides a caching attachment provider.  This class rests on top of a
 *  real provider class and provides a cache to speed things up.  Only the
 *  Attachment objects are cached; the actual attachment contents are 
 *  fetched always from the provider.
 *
 *  @author Janne Jalkanen
 *  @since 2.1.64.
 */

// FIXME: Do we need to clear the cache entry if we get an NRE and the attachment is not there?
// FIXME: We probably clear the cache a bit too aggressively in places.
// FIXME: Does not yet react well to external cache changes.  Should really use custom
//        EntryRefreshPolicy for that.

public class CachingAttachmentProvider
    implements WikiAttachmentProvider
{
    private static final Logger log = Logger.getLogger(CachingAttachmentProvider.class);

    private WikiAttachmentProvider m_provider;

    /**
     *  The cache contains Collection objects which contain Attachment objects.
     *  The key is the parent wiki page name (String).
     */
    private Cache m_cache;

    private long m_cacheMisses = 0;
    private long m_cacheHits   = 0;

    /** The extension to append to directory names to denote an attachment directory. */
    public static final String DIR_EXTENSION   = "-att";
    
    /** Property that supplies the directory used to store attachments. */
    public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";

    // FIXME: Make settable.
    private int  m_refreshPeriod = 60*10; // 10 minutes at the moment

    /**
     * {@inheritDoc}
     */
    public void initialize( WikiEngine engine, Properties properties )
        throws NoRequiredPropertyException,
               IOException
    {
        log.debug("Initing CachingAttachmentProvider");

        //
        //  Construct an unlimited cache.
        //
        m_cache = new Cache( true, false, true );

        //
        //  Find and initialize real provider.
        //
        String classname = WikiEngine.getRequiredProperty( properties, 
                                                           AttachmentManager.PROP_PROVIDER );
        
        try
        {            
            Class providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
                                                       classname );

            m_provider = (WikiAttachmentProvider)providerclass.newInstance();

            log.debug("Initializing real provider class "+m_provider);
            m_provider.initialize( engine, properties );
        }
        catch( ClassNotFoundException e )
        {
            log.error("Unable to locate provider class "+classname,e);
            throw new IllegalArgumentException("no provider class");
        }
        catch( InstantiationException e )
        {
            log.error("Unable to create provider class "+classname,e);
            throw new IllegalArgumentException("faulty provider class");
        }
        catch( IllegalAccessException e )
        {
            log.error("Illegal access to provider class "+classname,e);
            throw new IllegalArgumentException("illegal provider class");
        }

    }

    /**
     * {@inheritDoc}
     */
    public void putAttachmentData( Attachment att, InputStream data )
        throws ProviderException,
               IOException
    {
        m_provider.putAttachmentData( att, data );

        m_cache.flushEntry( att.getParentName() );
    }

    /**
     * {@inheritDoc}
     */
    public InputStream getAttachmentData( Attachment att )
        throws ProviderException,
               IOException
    {
        return m_provider.getAttachmentData( att );
    }

    /**
     * {@inheritDoc}
     */
    public Collection listAttachments( WikiPage page )
        throws ProviderException
    {
        log.debug("Listing attachments for "+page);
        try
        {
            Collection c = (Collection)m_cache.getFromCache( page.getName(), m_refreshPeriod );

            if( c != null )
            {
                log.debug("LIST from cache, "+page.getName()+", size="+c.size());
                m_cacheHits++;
                return cloneCollection(c);
            }

            log.debug("list NOT in cache, "+page.getName());

            refresh( page );
        }
        catch( NeedsRefreshException nre )
        {
            try
            {
                Collection c = refresh( page );

                return cloneCollection(c);
            }
            catch( Exception ex )
            {
                // Is a catch-all, because cache will get confused if
                // we let this one go.
                log.warn("Provider failed, returning cached content",ex);

                m_cache.cancelUpdate(page.getName());
                
                return (Collection)nre.getCacheContent();
            }
        }

        return new ArrayList();
    }

    private Collection cloneCollection( Collection c )
    {
        ArrayList list = new ArrayList();
        
        list.addAll( c );
        
        return list;
    }
    
    /**
     * {@inheritDoc}
     */
    public Collection findAttachments( QueryItem[] query )
    {
        return m_provider.findAttachments( query );
    }

    /**
     * {@inheritDoc}
     */
    public List listAllChanged( Date timestamp )
        throws ProviderException
    {
        // FIXME: Should cache
        return m_provider.listAllChanged( timestamp );
    }

    /**
     *  Simply goes through the collection and attempts to locate the
     *  given attachment of that name.
     *
     *  @return null, if no such attachment was in this collection.
     */
    private Attachment findAttachmentFromCollection( Collection c, String name )
    {
        for( Iterator i = c.iterator(); i.hasNext(); )
        {
            Attachment att = (Attachment) i.next();

            if( name.equals( att.getFileName() ) )
            {
                return att;
            }
        }

        return null;
    }

    /**
     *  Refreshes the cache content and updates counters.
     *
     *  @return The newly fetched object from the provider.
     */
    private final Collection refresh( WikiPage page )
        throws ProviderException
    {
        m_cacheMisses++;
        Collection c = m_provider.listAttachments( page );
        m_cache.putInCache( page.getName(), c );

        return c;
    }

    /**
     * {@inheritDoc}
     */
    public Attachment getAttachmentInfo( WikiPage page, String name, int version )
        throws ProviderException
    {
        if( log.isDebugEnabled() )
        {
            log.debug("Getting attachments for "+page+", name="+name+", version="+version);
        }

        //
        //  We don't cache previous versions
        //
        if( version != WikiProvider.LATEST_VERSION )
        {       
            log.debug("...we don't cache old versions");
            return m_provider.getAttachmentInfo( page, name, version );
        }

        try
        {
            Collection c = (Collection)m_cache.getFromCache( page.getName(), m_refreshPeriod );
            
            if( c == null )
            {
                log.debug("...wasn't in the cache");
                c = refresh( page );

                if( c == null ) return null; // No such attachment
            }
            else
            {
                log.debug("...FOUND in the cache");
                m_cacheHits++;
            }

            return findAttachmentFromCollection( c, name );

        }
        catch( NeedsRefreshException nre )
        {
            log.debug("...needs refresh");
            Collection c = null;

            try
            {
                c = refresh( page );
            }
            catch( Exception ex )
            {
                log.warn("Provider failed, returning cached content",ex);

                m_cache.cancelUpdate( page.getName() );
                c = (Collection)nre.getCacheContent();
            }

            if( c != null )
            {
                return findAttachmentFromCollection( c, name );
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public List getVersionHistory( Attachment att )
    {
        return m_provider.getVersionHistory( att );
    }

    /**
     * {@inheritDoc}
     */
    public void deleteVersion( Attachment att )
        throws ProviderException
    {
        // This isn't strictly speaking correct, but it does not really matter
        m_cache.putInCache( att.getParentName(), null );
        m_provider.deleteVersion( att );
    }

    /**
     * {@inheritDoc}
     */
    public void deleteAttachment( Attachment att )
        throws ProviderException
    {
        m_cache.putInCache( att.getParentName(), null );
        m_provider.deleteAttachment( att );
    }

    /**
     * {@inheritDoc}
     */
    public synchronized String getProviderInfo()
    {              
        return "Real provider: "+m_provider.getClass().getName()+
               ".  Cache misses: "+m_cacheMisses+
               ".  Cache hits: "+m_cacheHits;
    }

    /**
     * {@inheritDoc}
     */
    public WikiAttachmentProvider getRealProvider()
    {
        return m_provider;
    }
    
    /**
     * {@inheritDoc}
     */
    public void moveAttachmentsForPage( String oldParent, String newParent )
        throws ProviderException
    {
        m_provider.moveAttachmentsForPage(oldParent, newParent);
        m_cache.putInCache( newParent, null ); // FIXME
        m_cache.putInCache( oldParent, null );
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.