jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r627255 [29/41] - in /incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/ com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/ com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...
Date Wed, 13 Feb 2008 05:54:24 GMT
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,910 @@
+/*
+    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.util.*;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.parser.MarkupParser;
+import com.ecyrd.jspwiki.render.RenderingManager;
+import com.ecyrd.jspwiki.util.ClassUtil;
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+import com.opensymphony.oscache.base.events.*;
+
+/**
+ *  Provides a caching page provider.  This class rests on top of a
+ *  real provider class and provides a cache to speed things up.  Only
+ *  if the cache copy of the page text has expired, we fetch it from
+ *  the provider.
+ *  <p>
+ *  This class also detects if someone has modified the page
+ *  externally, not through JSPWiki routines, and throws the proper
+ *  RepositoryModifiedException.
+ *  <p>
+ *  Heavily based on ideas by Chris Brooking.
+ *  <p>
+ *  Since 2.1.52 uses the OSCache library from OpenSymphony.
+ *
+ *  @author Janne Jalkanen
+ *  @since 1.6.4
+ *  @see RepositoryModifiedException
+ */
+// FIXME: Synchronization is a bit inconsistent in places.
+// FIXME: A part of the stuff is now redundant, since we could easily use the text cache
+//        for a lot of things.  RefactorMe.
+
+public class CachingProvider
+    implements WikiPageProvider, VersioningProvider
+{
+    private static final Logger log = Logger.getLogger(CachingProvider.class);
+
+    private WikiPageProvider m_provider;
+    // FIXME: Find another way to the search engine to use instead of from WikiEngine?
+    private WikiEngine       m_engine;
+
+    private Cache            m_cache;
+    private Cache            m_negCache; // Cache for holding non-existing pages
+
+    private Cache            m_textCache;
+    private Cache            m_historyCache;
+
+    private long             m_cacheMisses = 0;
+    private long             m_cacheHits   = 0;
+
+    private long             m_historyCacheMisses = 0;
+    private long             m_historyCacheHits   = 0;
+
+    private int              m_expiryPeriod = 30;
+
+    /**
+     *  This can be very long, as normally all modifications are noticed in an earlier
+     *  stage.
+     */
+    private int              m_pageContentExpiryPeriod = 24*60*60;
+
+    // FIXME: This MUST be cached somehow.
+
+    private boolean          m_gotall = false;
+
+    private CacheItemCollector m_allCollector = new CacheItemCollector();
+
+    /**
+     *  Defines, in seconds, the amount of time a text will live in the cache
+     *  at most before requiring a refresh.
+     */
+
+    public static final String PROP_CACHECHECKINTERVAL = "jspwiki.cachingProvider.cacheCheckInterval";
+    public static final String PROP_CACHECAPACITY      = "jspwiki.cachingProvider.capacity";
+
+    private static final int   DEFAULT_CACHECAPACITY   = 1000; // Good most wikis
+
+    private static final String OSCACHE_ALGORITHM      = "com.opensymphony.oscache.base.algorithm.LRUCache";
+
+
+    public void initialize( WikiEngine engine, Properties properties )
+        throws NoRequiredPropertyException,
+               IOException
+    {
+        log.debug("Initing CachingProvider");
+
+        // engine is used for getting the search engine
+        m_engine = engine;
+
+        //
+        //  Cache consistency checks
+        //
+        m_expiryPeriod = TextUtil.getIntegerProperty( properties,
+                                                      PROP_CACHECHECKINTERVAL,
+                                                      m_expiryPeriod );
+
+        log.debug("Cache expiry period is "+m_expiryPeriod+" s");
+
+        //
+        //  Text cache capacity
+        //
+        int capacity = TextUtil.getIntegerProperty( properties,
+                                                    PROP_CACHECAPACITY,
+                                                    DEFAULT_CACHECAPACITY );
+
+        log.debug("Cache capacity "+capacity+" pages.");
+
+        m_cache = new Cache( true, false, false );
+
+        //
+        //  OSCache documentation sucks big time.  The clazz-parameter is completely
+        //  undefined; I had to read the source code to figure out that you need
+        //  to declare what type of a listener you are adding by sending the type
+        //  of the interface.
+        //
+        m_cache.addCacheEventListener( m_allCollector, CacheEntryEventListener.class );
+
+        //
+        //  FIXME: There's an interesting issue here... It would probably be
+        //  possible to DOS a JSPWiki instance by bombarding it with names that
+        //  do not exist, as they would fill the negcache.  Will need to
+        //  think about this some more...
+        //
+        m_negCache = new Cache( true, false, false );
+
+        m_textCache = new Cache( true, false, false,
+                                 false,
+                                 OSCACHE_ALGORITHM,
+                                 capacity );
+
+        m_historyCache = new Cache( true, false, false, false,
+                                    OSCACHE_ALGORITHM,
+                                    capacity );
+
+        //
+        //  Find and initialize real provider.
+        //
+        String classname = WikiEngine.getRequiredProperty( properties,
+                                                           PageManager.PROP_PAGEPROVIDER );
+
+
+        try
+        {
+            Class providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
+                                                       classname );
+
+            m_provider = (WikiPageProvider)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");
+        }
+    }
+
+
+
+
+    private WikiPage getPageInfoFromCache( String name )
+        throws ProviderException,
+               RepositoryModifiedException
+    {
+        boolean wasUpdated = false;
+        // Sanity check; seems to occur sometimes
+        if( name == null ) return null;
+
+        try
+        {
+            WikiPage item = (WikiPage)m_cache.getFromCache( name, m_expiryPeriod );
+
+            wasUpdated = true;
+
+            if( item != null )
+                return item;
+
+            return null;
+        }
+        catch( NeedsRefreshException e )
+        {
+            WikiPage cached = (WikiPage)e.getCacheContent();
+
+            // int version = (cached != null) ? cached.getVersion() : WikiPageProvider.LATEST_VERSION;
+
+            WikiPage refreshed;
+
+            //
+            //  Just be careful that we don't accidentally leave the cache in a
+            //  hung state
+            //
+
+            refreshed = m_provider.getPageInfo( name, WikiPageProvider.LATEST_VERSION );
+
+            if( refreshed == null && cached != null )
+            {
+                //  Page has been removed evilly by a goon from outer space
+
+                log.debug("Page "+name+" has been removed externally.");
+
+                m_cache.putInCache( name, null );
+                m_textCache.putInCache( name, null );
+                m_historyCache.putInCache( name, null );
+                // We cache a page miss
+                m_negCache.putInCache( name, name );
+                wasUpdated = true;
+
+                throw new RepositoryModifiedException( "Removed: "+name, name );
+            }
+            else if( cached == null )
+            {
+                // The page did not exist in the first place
+
+                if( refreshed != null )
+                {
+                    // We must now add it
+                    m_cache.putInCache( name, refreshed );
+                    // Requests for this page are now no longer denied
+                    m_negCache.putInCache( name, null );
+                    wasUpdated = true;
+
+                    throw new RepositoryModifiedException( "Added: "+name, name );
+                }
+
+                // Cache page miss
+                m_negCache.putInCache( name, name );
+            }
+            else if( refreshed != null && cached.getVersion() != refreshed.getVersion() )
+            {
+                //  The newest version has been deleted, but older versions still remain
+                log.debug("Page "+cached.getName()+" newest version deleted, reloading...");
+
+                m_cache.putInCache( name, refreshed );
+                // Requests for this page are now no longer denied
+                m_negCache.putInCache( name, null );
+
+                m_textCache.flushEntry( name );
+                m_historyCache.flushEntry( name );
+                wasUpdated = true;
+
+                return refreshed;
+            }
+            else if( refreshed != null && Math.abs(refreshed.getLastModified().getTime()-cached.getLastModified().getTime()) > 1000L )
+            {
+                //  Yes, the page has been modified externally and nobody told us
+
+                log.info("Page "+cached.getName()+" changed, reloading...");
+
+                m_cache.putInCache( name, refreshed );
+                // Requests for this page are now no longer denied
+                m_negCache.putInCache( name, null );
+                m_textCache.flushEntry( name );
+                m_historyCache.flushEntry( name );
+                wasUpdated = true;
+
+                throw new RepositoryModifiedException( "Modified: "+name, name );
+            }
+            else
+            {
+                // Refresh the cache by putting the same object back
+                m_cache.putInCache( name, cached );
+                // Requests for this page are now no longer denied
+                m_negCache.putInCache( name, null );
+                wasUpdated = true;
+            }
+
+            return cached;
+        }
+        finally
+        {
+            if( !wasUpdated )
+                m_cache.cancelUpdate(name);
+        }
+    }
+
+    public boolean pageExists( String pageName, int version )
+    {
+        if( pageName == null ) return false;
+
+        //
+        //  First, check the negative cache if we've seen it before
+        //
+        try
+        {
+            String isNonExistant = (String) m_negCache.getFromCache( pageName, m_expiryPeriod );
+
+            if( isNonExistant != null ) return false; // No such page
+        }
+        catch( NeedsRefreshException e )
+        {
+            m_negCache.cancelUpdate(pageName);
+        }
+
+        WikiPage p = null;
+
+        try
+        {
+            p = getPageInfoFromCache( pageName );
+        }
+        catch( RepositoryModifiedException e )
+        {
+            // The repository was modified, we need to check now if the page was removed or
+            // added.
+            // TODO: This information would be available in the exception, but we would
+            //       need to subclass.
+
+            try
+            {
+                p = getPageInfoFromCache( pageName );
+            }
+            catch( Exception ex )
+            {
+                // This should not happen
+                return false;
+            }
+        }
+        catch( ProviderException e )
+        {
+            log.info("Provider failed while trying to check if page exists: "+pageName);
+            return false;
+        }
+
+        if( p != null )
+        {
+            int latestVersion = p.getVersion();
+
+            if( version == latestVersion || version == LATEST_VERSION )
+            {
+                return true;
+            }
+
+            if( m_provider instanceof VersioningProvider )
+                return ((VersioningProvider) m_provider).pageExists( pageName, version );
+        }
+
+        try
+        {
+            return getPageInfo( pageName, version ) != null;
+        }
+        catch( ProviderException e )
+        {}
+
+        return false;
+    }
+
+    public boolean pageExists( String pageName )
+    {
+        if( pageName == null ) return false;
+
+        //
+        //  First, check the negative cache if we've seen it before
+        //
+        try
+        {
+            String isNonExistant = (String) m_negCache.getFromCache( pageName, m_expiryPeriod );
+
+            if( isNonExistant != null ) return false; // No such page
+        }
+        catch( NeedsRefreshException e )
+        {
+            m_negCache.cancelUpdate(pageName);
+        }
+
+        WikiPage p = null;
+
+        try
+        {
+            p = getPageInfoFromCache( pageName );
+        }
+        catch( RepositoryModifiedException e )
+        {
+            // The repository was modified, we need to check now if the page was removed or
+            // added.
+            // TODO: This information would be available in the exception, but we would
+            //       need to subclass.
+
+            try
+            {
+                p = getPageInfoFromCache( pageName );
+            }
+            catch( Exception ex )
+            {
+                return false; // This should not happen
+            }
+        }
+        catch( ProviderException e )
+        {
+            log.info("Provider failed while trying to check if page exists: "+pageName);
+            return false;
+        }
+
+        //
+        //  A null item means that the page either does not
+        //  exist, or has not yet been cached; a non-null
+        //  means that the page does exist.
+        //
+        if( p != null )
+        {
+            return true;
+        }
+
+        //
+        //  If we have a list of all pages in memory, then any page
+        //  not in the cache must be non-existent.
+        //
+        //  FIXME: There's a problem here; if someone modifies the
+        //         repository by adding a page outside JSPWiki,
+        //         we won't notice it.
+
+        if( m_gotall )
+        {
+            return false;
+        }
+
+        //
+        //  We could add the page to the cache here as well,
+        //  but in order to understand whether that is a
+        //  good thing or not we would need to analyze
+        //  the JSPWiki calling patterns extensively.  Presumably
+        //  it would be a good thing if pageExists() is called
+        //  many times before the first getPageText() is called,
+        //  and the whole page is cached.
+        //
+        return m_provider.pageExists( pageName );
+    }
+
+    /**
+     *  @throws RepositoryModifiedException If the page has been externally modified.
+     */
+    public String getPageText( String pageName, int version )
+        throws ProviderException,
+               RepositoryModifiedException
+    {
+        String result = null;
+
+        if( pageName == null ) return null;
+
+        if( version == WikiPageProvider.LATEST_VERSION )
+        {
+            result = getTextFromCache( pageName );
+        }
+        else
+        {
+            WikiPage p = getPageInfoFromCache( pageName );
+
+            //
+            //  Or is this the latest version fetched by version number?
+            //
+            if( p != null && p.getVersion() == version )
+            {
+                result = getTextFromCache( pageName );
+            }
+            else
+            {
+                result = m_provider.getPageText( pageName, version );
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     *  @throws RepositoryModifiedException If the page has been externally modified.
+     */
+    private String getTextFromCache( String pageName )
+        throws ProviderException,
+               RepositoryModifiedException
+    {
+        String text;
+        boolean wasUpdated = false;
+
+        if( pageName == null ) return null;
+
+        WikiPage page = getPageInfoFromCache( pageName );
+
+        try
+        {
+            text = (String)m_textCache.getFromCache( pageName,
+                                                     m_pageContentExpiryPeriod );
+            wasUpdated = true;
+
+            if( text == null )
+            {
+                if( page != null )
+                {
+                    text = m_provider.getPageText( pageName, WikiPageProvider.LATEST_VERSION );
+
+                    m_textCache.putInCache( pageName, text );
+
+                    m_cacheMisses++;
+                }
+                else
+                {
+                    return null;
+                }
+            }
+            else
+            {
+                m_cacheHits++;
+            }
+        }
+        catch( NeedsRefreshException e )
+        {
+            if( pageExists(pageName) )
+            {
+                text = m_provider.getPageText( pageName, WikiPageProvider.LATEST_VERSION );
+
+                m_textCache.putInCache( pageName, text );
+                wasUpdated = true;
+
+                m_cacheMisses++;
+            }
+            else
+            {
+                m_textCache.putInCache( pageName, null );
+                wasUpdated = true;
+                return null; // No page exists
+            }
+        }
+        finally
+        {
+            if( !wasUpdated )
+                m_textCache.cancelUpdate(pageName);
+        }
+
+        return text;
+    }
+
+    public void putPageText( WikiPage page, String text )
+        throws ProviderException
+    {
+        synchronized(this)
+        {
+            m_provider.putPageText( page, text );
+
+            page.setLastModified( new Date() );
+
+            // Refresh caches properly
+
+            m_cache.flushEntry( page.getName() );
+            m_textCache.flushEntry( page.getName() );
+            m_historyCache.flushEntry( page.getName() );
+            m_negCache.flushEntry( page.getName() );
+
+            // Refresh caches
+            try
+            {
+                getPageInfoFromCache( page.getName() );
+            }
+            catch(RepositoryModifiedException e) {} // Expected
+        }
+    }
+
+
+    public Collection getAllPages()
+        throws ProviderException
+    {
+        Collection all;
+
+        if( m_gotall == false )
+        {
+            all = m_provider.getAllPages();
+
+            // Make sure that all pages are in the cache.
+
+            synchronized(this)
+            {
+                for( Iterator i = all.iterator(); i.hasNext(); )
+                {
+                    WikiPage p = (WikiPage) i.next();
+
+                    m_cache.putInCache( p.getName(), p );
+                    // Requests for this page are now no longer denied
+                    m_negCache.putInCache( p.getName(), null );
+                }
+
+                m_gotall = true;
+            }
+        }
+        else
+        {
+            all = m_allCollector.getAllItems();
+        }
+
+        return all;
+    }
+
+    public Collection getAllChangedSince( Date date )
+    {
+        return m_provider.getAllChangedSince( date );
+    }
+
+    public int getPageCount()
+        throws ProviderException
+    {
+        return m_provider.getPageCount();
+    }
+
+    public Collection findPages( QueryItem[] query )
+    {
+        //
+        //  If the provider is a fast searcher, then
+        //  just pass this request through.
+        //
+        return m_provider.findPages( query );
+
+        // FIXME: Does not implement fast searching
+    }
+
+    //
+    //  FIXME: Kludge: make sure that the page is also parsed and it gets all the
+    //         necessary variables.
+    //
+
+    private void refreshMetadata( WikiPage page )
+    {
+        if( page != null && !page.hasMetadata() )
+        {
+            RenderingManager mgr = m_engine.getRenderingManager();
+
+            try
+            {
+                String data = m_provider.getPageText(page.getName(), page.getVersion());
+
+                WikiContext ctx = m_engine.getWikiActionBeanFactory().newViewActionBean( page );
+                MarkupParser parser = mgr.getParser( ctx, data );
+
+                parser.parse();
+            }
+            catch( Exception ex )
+            {
+                log.debug("Failed to retrieve variables for wikipage "+page);
+            }
+        }
+    }
+
+    public WikiPage getPageInfo( String pageName, int version )
+        throws ProviderException,
+               RepositoryModifiedException
+    {
+        WikiPage page = null;
+        WikiPage cached = getPageInfoFromCache( pageName );
+
+        int latestcached = (cached != null) ? cached.getVersion() : Integer.MIN_VALUE;
+
+        if( version == WikiPageProvider.LATEST_VERSION ||
+            version == latestcached )
+        {
+            if( cached == null )
+            {
+                WikiPage data = m_provider.getPageInfo( pageName, version );
+
+                if( data != null )
+                {
+                    m_cache.putInCache( pageName, data );
+                    // Requests for this page are now no longer denied
+                    m_negCache.putInCache( pageName, null );
+                }
+                page = data;
+            }
+            else
+            {
+                page = cached;
+            }
+        }
+        else
+        {
+            // We do not cache old versions.
+            page = m_provider.getPageInfo( pageName, version );
+            //refreshMetadata( page );
+        }
+
+        refreshMetadata( page );
+
+        return page;
+    }
+
+    public List getVersionHistory( String pageName )
+        throws ProviderException
+    {
+        List history = null;
+        boolean wasUpdated = false;
+
+        if( pageName == null ) return null;
+        try
+        {
+            history = (List)m_historyCache.getFromCache( pageName,
+                                                         m_expiryPeriod );
+
+            log.debug("History cache hit for page "+pageName);
+            m_historyCacheHits++;
+            wasUpdated = true;
+        }
+        catch( NeedsRefreshException e )
+        {
+            history = m_provider.getVersionHistory( pageName );
+
+            m_historyCache.putInCache( pageName, history );
+
+            log.debug("History cache miss for page "+pageName);
+            m_historyCacheMisses++;
+            wasUpdated = true;
+        }
+        finally
+        {
+            if( !wasUpdated ) m_historyCache.cancelUpdate( pageName );
+        }
+
+        return history;
+    }
+
+    public synchronized String getProviderInfo()
+    {
+        return "Real provider: "+m_provider.getClass().getName()+
+               ". Cache misses: "+m_cacheMisses+
+               ". Cache hits: "+m_cacheHits+
+               ". History cache hits: "+m_historyCacheHits+
+               ". History cache misses: "+m_historyCacheMisses+
+               ". Cache consistency checks: "+m_expiryPeriod+"s";
+    }
+
+    public void deleteVersion( String pageName, int version )
+        throws ProviderException
+    {
+        //
+        //  Luckily, this is such a rare operation it is okay
+        //  to synchronize against the whole thing.
+        //
+        synchronized( this )
+        {
+            WikiPage cached = getPageInfoFromCache( pageName );
+
+            int latestcached = (cached != null) ? cached.getVersion() : Integer.MIN_VALUE;
+
+            //
+            //  If we have this version cached, remove from cache.
+            //
+            if( version == WikiPageProvider.LATEST_VERSION ||
+                version == latestcached )
+            {
+                m_cache.flushEntry( pageName );
+                m_textCache.putInCache( pageName, null );
+                m_historyCache.putInCache( pageName, null );
+            }
+
+            m_provider.deleteVersion( pageName, version );
+        }
+    }
+
+    public void deletePage( String pageName )
+        throws ProviderException
+    {
+        //
+        //  See note in deleteVersion().
+        //
+        synchronized(this)
+        {
+            m_cache.putInCache( pageName, null );
+            m_textCache.putInCache( pageName, null );
+            m_historyCache.putInCache( pageName, null );
+            m_negCache.putInCache( pageName, pageName );
+            m_provider.deletePage( pageName );
+        }
+    }
+
+    public void movePage( String from,
+                          String to )
+        throws ProviderException
+    {
+        m_provider.movePage( from, to );
+
+        synchronized(this)
+        {
+            // Clear any cached version of the old page
+            log.debug("Removing page "+from+" from cache");
+            m_cache.flushEntry( from );
+
+            // Clear the cache for the to page, if that page already exists
+            //if ( m_cache.get( to ) != null )
+            //{
+                log.debug("Removing page "+to+" from cache");
+                m_cache.flushEntry( to );
+            //}
+        }
+    }
+
+    /**
+     *  Returns the actual used provider.
+     *  @since 2.0
+     */
+    public WikiPageProvider getRealProvider()
+    {
+        return m_provider;
+    }
+
+    /**
+     *  This is a simple class that keeps a list of all WikiPages that
+     *  we have in memory.  Because the OSCache cannot give us a list
+     *  of all pages currently in cache, we'll have to check this
+     *  ourselves.
+     *
+     *  @author jalkanen
+     *
+     *  @since 2.4
+     */
+    private static class CacheItemCollector
+        implements CacheEntryEventListener
+    {
+        private Map m_allItems = new HashMap();
+
+        /**
+         * Returns a clone of the set - you cannot manipulate this.
+         *
+         * @return
+         */
+        public Set getAllItems()
+        {
+            Set ret = new TreeSet();
+            ret.addAll(m_allItems.values());
+
+            return ret;
+        }
+
+        public void cacheEntryAdded( CacheEntryEvent arg0 )
+        {
+            cacheEntryUpdated( arg0 );
+        }
+
+        public void cachePatternFlushed( CachePatternEvent ev )
+        {
+        }
+
+        public void cacheGroupFlushed( CacheGroupEvent ev )
+        {
+        }
+
+        public void cacheFlushed( CachewideEvent ev )
+        {
+        }
+
+        public void cacheEntryFlushed( CacheEntryEvent arg0 )
+        {
+            cacheEntryRemoved( arg0 );
+        }
+
+        public void cacheEntryRemoved( CacheEntryEvent arg0 )
+        {
+            WikiPage item = (WikiPage) arg0.getEntry().getContent();
+
+            if( item != null )
+            {
+                m_allItems.remove( item );
+            }
+        }
+
+        public void cacheEntryUpdated( CacheEntryEvent arg0 )
+        {
+            WikiPage item = (WikiPage) arg0.getEntry().getContent();
+
+            if( item != null )
+            {
+                // Item added or replaced.
+                m_allItems.put( item.getName(), item );
+            }
+            else
+            {
+                // Removed item
+                // FIXME: If the page system is changed during this time, we'll just fail gracefully
+
+                m_allItems.remove( arg0.getKey() );
+            }
+        }
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FastSearch.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FastSearch.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FastSearch.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FastSearch.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,33 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2003 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;
+
+/**
+ *  If a provider implements this interface, then CachingProvider
+ *  will never attempt to search on its own; it will always pass any searches
+ *  through to the actual provider.
+ *
+ *  @author Janne Jalkanen
+ *  @since  2.1.57
+ */
+public interface FastSearch
+{
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FileSystemProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FileSystemProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FileSystemProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/FileSystemProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,172 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 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.*;
+import java.util.Properties;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+
+/**
+ *  Provides a simple directory based repository for Wiki pages.
+ *  <P>
+ *  All files have ".txt" appended to make life easier for those
+ *  who insist on using Windows or other software which makes assumptions
+ *  on the files contents based on its name.
+ *
+ *  @author Janne Jalkanen
+ */
+public class FileSystemProvider
+    extends AbstractFileProvider
+{
+    private static final Logger   log = Logger.getLogger(FileSystemProvider.class);
+    /**
+     *  All metadata is stored in a file with this extension.
+     */
+    public static final String PROP_EXT = ".properties";
+
+    public void putPageText( WikiPage page, String text )        
+        throws ProviderException
+    {
+        try
+        {
+            super.putPageText( page, text );
+            putPageProperties( page );
+        }
+        catch( IOException e )
+        {
+            log.error( "Saving failed" );
+        }
+    }
+
+    /**
+     *  Stores basic metadata to a file.
+     */
+    private void putPageProperties( WikiPage page )        
+        throws IOException
+    {
+        Properties props = new Properties();        
+        OutputStream out = null;
+
+        try
+        {
+            String author = page.getAuthor();
+            String changenote = (String)page.getAttribute( WikiPage.CHANGENOTE );
+            
+            if( author != null )
+            {
+                props.setProperty( "author", author );
+            }
+            
+            if( changenote != null )
+            {
+                props.setProperty( "changenote", changenote );
+            }
+            
+            File file = new File( getPageDirectory(), 
+                                  mangleName(page.getName())+PROP_EXT );
+     
+            out = new FileOutputStream( file );
+
+            props.store( out, "JSPWiki page properties for page "+page.getName() );
+        }
+        finally
+        {
+            if( out != null ) out.close();
+        }
+    }
+
+    /**
+     *  Gets basic metadata from file.
+     */
+    private void getPageProperties( WikiPage page )
+        throws IOException
+    {
+        Properties  props = new Properties();
+        InputStream in    = null;
+
+        try
+        {
+            File file = new File( getPageDirectory(), 
+                                  mangleName(page.getName())+PROP_EXT );
+
+            if( file.exists() )
+            {
+                in = new FileInputStream( file );
+
+                props.load(in);
+
+                page.setAuthor( props.getProperty( "author" ) );
+                
+                String changenote = props.getProperty( "changenote" );
+                if( changenote != null )
+                {
+                    page.setAttribute( WikiPage.CHANGENOTE, changenote );
+                }
+            }            
+        }
+        finally
+        {
+            if( in != null ) in.close();
+        }
+    }
+
+    public WikiPage getPageInfo( String page, int version )
+        throws ProviderException
+    {
+        WikiPage p = super.getPageInfo( page, version );
+
+        if( p != null )
+        {
+            try
+            {
+                getPageProperties( p );
+            }
+            catch( IOException e )
+            {
+                log.error("Unable to read page properties", e );
+                throw new ProviderException("Unable to read page properties, check logs.");
+            }
+        }
+
+        return p;
+    }
+
+    public void deletePage(String pageName) throws ProviderException
+    {
+        super.deletePage(pageName);
+
+        File file = new File( getPageDirectory(), 
+                              mangleName(pageName)+PROP_EXT );
+        
+        if( file.exists() ) file.delete();
+    }
+
+    public void movePage( String from,
+                          String to )
+        throws ProviderException
+    {
+        File fromPage = findPage( from );
+        File toPage = findPage( to );
+        
+        fromPage.renameTo( toPage );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/NoSuchVersionException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/NoSuchVersionException.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/NoSuchVersionException.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/NoSuchVersionException.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,34 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 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;
+
+/**
+ *  Indicates that an non-existing version was specified.
+ */
+public class NoSuchVersionException
+    extends ProviderException
+{
+    private static final long serialVersionUID = 0L;
+    
+    public NoSuchVersionException( String msg )
+    {
+        super( msg );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/ProviderException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/ProviderException.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/ProviderException.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/ProviderException.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,39 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001 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 com.ecyrd.jspwiki.WikiException;
+
+/**
+ *  This exception represents the superclass of all exceptions that providers
+ *  may throw.  It is okay to throw it in case you cannot use any of
+ *  the specific subclasses, in which case the page loading is
+ *  considered to be broken, and the user is notified.
+ */
+public class ProviderException
+    extends WikiException
+{
+    private static final long serialVersionUID = 0L;
+    
+    public ProviderException( String msg )
+    {
+        super( msg );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RCSFileProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RCSFileProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RCSFileProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RCSFileProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,688 @@
+/*
+    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.File;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Date;
+import java.util.Iterator;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+import org.apache.log4j.Logger;
+import org.apache.oro.text.regex.*;
+
+import com.ecyrd.jspwiki.*;
+
+/**
+ *  This class implements a simple RCS file provider.  NOTE: You MUST
+ *  have the RCS package installed for this to work.  They must also
+ *  be in your path...
+ *
+ *  <P>
+ *  The RCS file provider extends from the FileSystemProvider, which
+ *  means that it provides the pages in the same way.  The only difference
+ *  is that it implements the version history commands, and also in each
+ *  checkin it writes the page to the RCS repository as well.
+ *  <p>
+ *  If you decide to dabble with the default commands, please make sure
+ *  that you do not check the default archive suffix ",v".  File deletion
+ *  depends on it.
+ *
+ *  @author Janne Jalkanen
+ */
+// FIXME: Not all commands read their format from the property file yet.
+public class RCSFileProvider
+    extends AbstractFileProvider
+{
+    private String m_checkinCommand  = "ci -m\"author=%u;changenote=%c\" -l -t-none %s";
+    private String m_checkoutCommand = "co -l %s";
+    private String m_logCommand      = "rlog -zLT -r %s";
+    private String m_fullLogCommand  = "rlog -zLT %s";
+    private String m_checkoutVersionCommand = "co -p -r1.%v %s";
+    private String m_deleteVersionCommand = "rcs -o1.%v %s";
+
+    private static final Logger   log = Logger.getLogger(RCSFileProvider.class);
+
+    public static final String    PROP_CHECKIN  = "jspwiki.rcsFileProvider.checkinCommand";
+    public static final String    PROP_CHECKOUT = "jspwiki.rcsFileProvider.checkoutCommand";
+    public static final String    PROP_LOG      = "jspwiki.rcsFileProvider.logCommand";
+    public static final String    PROP_FULLLOG  = "jspwiki.rcsFileProvider.fullLogCommand";
+    public static final String    PROP_CHECKOUTVERSION = "jspwiki.rcsFileProvider.checkoutVersionCommand";
+
+    private static final String   PATTERN_DATE      = "^date:\\s*(.*\\d);";
+    private static final String   PATTERN_AUTHOR    = "^\"?author=([\\w\\.\\s\\+\\.\\%]*)\"?";
+    private static final String   PATTERN_CHANGENOTE= ";changenote=([\\w\\.\\s\\+\\.\\%]*)\"?";
+    private static final String   PATTERN_REVISION  = "^revision \\d+\\.(\\d+)";
+
+    private static final String   RCSFMT_DATE       = "yyyy-MM-dd HH:mm:ss";
+    private static final String   RCSFMT_DATE_UTC   = "yyyy/MM/dd HH:mm:ss";
+
+    // Date format parsers, placed here to save on object creation
+    private SimpleDateFormat m_rcsdatefmt     = new SimpleDateFormat( RCSFMT_DATE );
+    private SimpleDateFormat m_rcsdatefmt_utc = new SimpleDateFormat( RCSFMT_DATE_UTC );
+
+    public void initialize( WikiEngine engine, Properties props )
+        throws NoRequiredPropertyException,
+               IOException
+    {
+        log.debug("Initing RCS");
+        super.initialize( engine, props );
+
+        m_checkinCommand = props.getProperty( PROP_CHECKIN, m_checkinCommand );
+        m_checkoutCommand = props.getProperty( PROP_CHECKOUT, m_checkoutCommand );
+        m_logCommand     = props.getProperty( PROP_LOG, m_logCommand );
+        m_fullLogCommand = props.getProperty( PROP_FULLLOG, m_fullLogCommand );
+        m_checkoutVersionCommand = props.getProperty( PROP_CHECKOUTVERSION, m_checkoutVersionCommand );
+
+        File rcsdir = new File( getPageDirectory(), "RCS" );
+
+        if( !rcsdir.exists() )
+        {
+            rcsdir.mkdirs();
+        }
+
+        log.debug("checkin="+m_checkinCommand);
+        log.debug("checkout="+m_checkoutCommand);
+        log.debug("log="+m_logCommand);
+        log.debug("fulllog="+m_fullLogCommand);
+        log.debug("checkoutversion="+m_checkoutVersionCommand);
+    }
+
+    // NB: This is a very slow method.
+
+    public WikiPage getPageInfo( String page, int version )
+        throws ProviderException
+    {
+        PatternMatcher  matcher  = new Perl5Matcher();
+        PatternCompiler compiler = new Perl5Compiler();
+        BufferedReader  stdout   = null;
+
+        WikiPage info = super.getPageInfo( page, version );
+
+        if( info == null ) return null;
+
+        try
+        {
+            String   cmd = m_fullLogCommand;
+
+            cmd = TextUtil.replaceString( cmd, "%s", mangleName(page)+FILE_EXT );
+
+            Process process = Runtime.getRuntime().exec( cmd, null, new File(getPageDirectory()) );
+
+            // FIXME: Should this use encoding as well?
+            stdout = new BufferedReader( new InputStreamReader(process.getInputStream() ) );
+
+            String line;
+            Pattern headpattern = compiler.compile( PATTERN_REVISION );
+            // This complicated pattern is required, since on Linux RCS adds
+            // quotation marks, but on Windows, it does not.
+            Pattern userpattern = compiler.compile( PATTERN_AUTHOR );
+            Pattern datepattern = compiler.compile( PATTERN_DATE );
+            Pattern notepattern = compiler.compile( PATTERN_CHANGENOTE );
+
+            boolean found = false;
+
+            while( (line = stdout.readLine()) != null )
+            {
+                if( matcher.contains( line, headpattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+
+                    try
+                    {
+                        int vernum = Integer.parseInt( result.group(1) );
+
+                        if( vernum == version || version == WikiPageProvider.LATEST_VERSION )
+                        {
+                            info.setVersion( vernum );
+                            found = true;
+                        }
+                    }
+                    catch( NumberFormatException e )
+                    {
+                        log.info("Failed to parse version number from RCS log: ",e);
+                        // Just continue reading through
+                    }
+                }
+                else if( matcher.contains( line, datepattern ) && found )
+                {
+                    MatchResult result = matcher.getMatch();
+                    Date d = parseDate( result.group(1) );
+
+                    if( d != null )
+                    {
+                        info.setLastModified( d );
+                    }
+                    else
+                    {
+                        log.info("WikiPage "+info.getName()+
+                                 " has null modification date for version "+
+                                 version);
+                    }
+                }
+                else if( found && line.startsWith("----")  )
+                {
+                    // End of line sign from RCS
+                    break;
+                }
+
+                if( found && matcher.contains( line, userpattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+                    info.setAuthor( TextUtil.urlDecodeUTF8(result.group(1)) );
+                }
+
+                if( found && matcher.contains( line, notepattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+
+                    info.setAttribute( WikiPage.CHANGENOTE, TextUtil.urlDecodeUTF8(result.group(1)) );
+                }
+            }
+
+            //
+            //  Especially with certain versions of RCS on Windows,
+            //  process.waitFor() hangs unless you read all of the
+            //  standard output.  So we make sure it's all emptied.
+            //
+
+            while( (line = stdout.readLine()) != null )
+            {
+            }
+
+            process.waitFor();
+
+            // we must close all by exec(..) opened streams: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
+            process.getInputStream().close();
+            process.getOutputStream().close();
+            process.getErrorStream().close();
+
+        }
+        catch( Exception e )
+        {
+            // This also occurs when 'info' was null.
+            log.warn("Failed to read RCS info",e);
+        }
+        finally
+        {
+            try
+            {
+                if( stdout != null ) stdout.close();
+            }
+            catch( IOException e ) {}
+        }
+
+        return info;
+    }
+
+    public String getPageText( String page, int version )
+        throws ProviderException
+    {
+        String result = null;
+        InputStream stdout = null;
+        BufferedReader stderr = null;
+        Process process = null;
+
+        // Let parent handle latest fetches, since the FileSystemProvider
+        // can do the file reading just as well.
+
+        if( version == WikiPageProvider.LATEST_VERSION )
+            return super.getPageText( page, version );
+
+        log.debug("Fetching specific version "+version+" of page "+page);
+
+        try
+        {
+            PatternMatcher  matcher           = new Perl5Matcher();
+            PatternCompiler compiler          = new Perl5Compiler();
+            int             checkedOutVersion = -1;
+            String          line;
+            String          cmd               = m_checkoutVersionCommand;
+
+            cmd = TextUtil.replaceString( cmd, "%s", mangleName(page)+FILE_EXT );
+            cmd = TextUtil.replaceString( cmd, "%v", Integer.toString(version ) );
+
+            log.debug("Command = '"+cmd+"'");
+
+            process = Runtime.getRuntime().exec( cmd, null, new File(getPageDirectory()) );
+            stdout = process.getInputStream();
+            result = FileUtil.readContents( stdout, m_encoding );
+
+            stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+
+            Pattern headpattern = compiler.compile( PATTERN_REVISION );
+
+            while( (line = stderr.readLine()) != null )
+            {
+                if( matcher.contains( line, headpattern ) )
+                {
+                    MatchResult mr = matcher.getMatch();
+                    checkedOutVersion = Integer.parseInt( mr.group(1) );
+                }
+            }
+
+            process.waitFor();
+
+            int exitVal = process.exitValue();
+
+            log.debug("Done, returned = "+exitVal);
+
+            //
+            //  If fetching failed, assume that this is because of the user
+            //  has just migrated from FileSystemProvider, and check
+            //  if he's getting version 1.  Else he might be trying to find
+            //  a version that has been deleted.
+            //
+            if( exitVal != 0 || checkedOutVersion == -1 )
+            {
+                if( version == 1 )
+                {
+                    result = super.getPageText( page, WikiProvider.LATEST_VERSION );
+                }
+                else
+                {
+                    throw new NoSuchVersionException( "Page: "+page+", version="+version);
+                }
+            }
+            else
+            {
+                //
+                //  Check which version we actually got out!
+                //
+
+                if( checkedOutVersion != version )
+                {
+                    throw new NoSuchVersionException( "Page: "+page+", version="+version);
+                }
+            }
+
+        }
+        catch( MalformedPatternException e )
+        {
+            throw new InternalWikiException("Malformed pattern in RCSFileProvider!");
+        }
+        catch( InterruptedException e )
+        {
+            // This is fine, we'll just log it.
+            log.info("RCS process was interrupted, we'll just return whatever we found.");
+        }
+        catch( IOException e )
+        {
+            log.error("RCS checkout failed",e);
+        }
+        finally
+        {
+            try
+            {
+                // we must close all by exec(..) opened streams: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
+                if( stdout != null ) stdout.close();
+                if( stderr != null ) stderr.close();
+                if( process != null ) process.getInputStream().close();
+            }
+            catch( Exception e )
+            {
+                log.error("Unable to close streams!");
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     *  Puts the page into RCS and makes sure there is a fresh copy in
+     *  the directory as well.
+     */
+    public void putPageText( WikiPage page, String text )
+        throws ProviderException
+    {
+        Process process = null;
+        String pagename = page.getName();
+        // Writes it in the dir.
+        super.putPageText( page, text );
+
+        log.debug( "Checking in text..." );
+
+        try
+        {
+            String cmd = m_checkinCommand;
+
+            String author = page.getAuthor();
+            if( author == null ) author = "unknown";
+
+            String changenote = (String)page.getAttribute(WikiPage.CHANGENOTE);
+            if( changenote == null ) changenote = "";
+
+            cmd = TextUtil.replaceString( cmd, "%s", mangleName(pagename)+FILE_EXT );
+            cmd = TextUtil.replaceString( cmd, "%u", TextUtil.urlEncodeUTF8(author) );
+            cmd = TextUtil.replaceString( cmd, "%c", TextUtil.urlEncodeUTF8(changenote) );
+            log.debug("Command = '"+cmd+"'");
+
+            process = Runtime.getRuntime().exec( cmd, null, new File(getPageDirectory()) );
+
+            process.waitFor();
+
+            //
+            //  Collect possible error output
+            //
+            BufferedReader error = null;
+            String elines = "";
+            error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+            String line = null;
+            while ((line = error.readLine()) != null)
+            {
+                elines = elines + line +"\n";
+            }
+
+            log.debug("Done, returned = "+process.exitValue());
+            log.debug(elines);
+            if (process.exitValue() != 0)
+            {
+                throw new ProviderException(cmd+"\n"+"Done, returned = "+process.exitValue()+"\n"+elines);
+            }
+        }
+        catch( Exception e )
+        {
+            log.error("RCS checkin failed",e);
+            ProviderException pe = new ProviderException("RCS checkin failed");
+            pe.initCause(e);
+            throw pe;
+        }
+        finally
+        {
+            // we must close all by exec(..) opened streams: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
+            if (process != null)
+            {
+                try
+                {
+                    if( process.getOutputStream() != null ) process.getOutputStream().close();
+                }
+                catch( Exception e ) {}
+                try
+                {
+                    if( process.getInputStream() != null ) process.getInputStream().close();
+                }
+                catch( Exception e ) {}
+                try
+                {
+                    if( process.getErrorStream() != null ) process.getErrorStream().close();
+                }
+                catch( Exception e ) {}
+            }
+        }
+    }
+
+    // FIXME: Put the rcs date formats into properties as well.
+    public List getVersionHistory( String page )
+    {
+        PatternMatcher matcher = new Perl5Matcher();
+        PatternCompiler compiler = new Perl5Compiler();
+        BufferedReader stdout  = null;
+
+        log.debug("Getting RCS version history");
+
+        ArrayList list = new ArrayList();
+
+        try
+        {
+            Pattern revpattern  = compiler.compile( PATTERN_REVISION );
+            Pattern datepattern = compiler.compile( PATTERN_DATE );
+            // This complicated pattern is required, since on Linux RCS adds
+            // quotation marks, but on Windows, it does not.
+            Pattern userpattern = compiler.compile( PATTERN_AUTHOR );
+
+            Pattern notepattern = compiler.compile( PATTERN_CHANGENOTE );
+
+            String cmd = TextUtil.replaceString( m_fullLogCommand,
+                                                 "%s",
+                                                 mangleName(page)+FILE_EXT );
+
+            Process process = Runtime.getRuntime().exec( cmd, null, new File(getPageDirectory()) );
+
+            // FIXME: Should this use encoding as well?
+            stdout = new BufferedReader( new InputStreamReader(process.getInputStream()) );
+
+            String line;
+
+            WikiPage info = null;
+
+            while( (line = stdout.readLine()) != null )
+            {
+                if( matcher.contains( line, revpattern ) )
+                {
+                    info = new WikiPage( m_engine, page );
+
+                    MatchResult result = matcher.getMatch();
+
+                    int vernum = Integer.parseInt( result.group(1) );
+                    info.setVersion( vernum );
+
+                    list.add( info );
+                }
+
+                if( matcher.contains( line, datepattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+
+                    Date d = parseDate( result.group(1) );
+
+                    info.setLastModified( d );
+                }
+
+                if( matcher.contains( line, userpattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+
+                    info.setAuthor( TextUtil.urlDecodeUTF8(result.group(1)) );
+                }
+
+                if( matcher.contains( line, notepattern ) )
+                {
+                    MatchResult result = matcher.getMatch();
+
+                    info.setAttribute( WikiPage.CHANGENOTE, TextUtil.urlDecodeUTF8(result.group(1)) );
+                }
+            }
+
+            process.waitFor();
+
+            // we must close all by exec(..) opened streams: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
+            process.getInputStream().close();
+            process.getOutputStream().close();
+            process.getErrorStream().close();
+
+            //
+            // FIXME: This is very slow
+            //
+            for( Iterator i = list.iterator(); i.hasNext(); )
+            {
+                WikiPage p = (WikiPage) i.next();
+
+                String content = getPageText( p.getName(), p.getVersion() );
+
+                p.setSize( content.length() );
+            }
+        }
+        catch( Exception e )
+        {
+            log.error( "RCS log failed", e );
+        }
+        finally
+        {
+            try
+            {
+                if( stdout != null ) stdout.close();
+            }
+            catch( IOException e ) {}
+        }
+
+        return list;
+    }
+
+    /**
+     *  Removes the page file and the RCS archive from the repository.
+     *  This method assumes that the page archive ends with ",v".
+     */
+    public void deletePage( String page )
+        throws ProviderException
+    {
+        log.debug( "Deleting page "+page );
+        super.deletePage( page );
+
+        File rcsdir  = new File( getPageDirectory(), "RCS" );
+
+        if( rcsdir.exists() && rcsdir.isDirectory() )
+        {
+            File rcsfile = new File( rcsdir, mangleName(page)+FILE_EXT+",v" );
+
+            if( rcsfile.exists() )
+            {
+                if( rcsfile.delete() == false )
+                {
+                    log.warn( "Deletion of RCS file "+rcsfile.getAbsolutePath()+" failed!" );
+                }
+            }
+            else
+            {
+                log.info( "RCS file does not exist for page: "+page );
+            }
+        }
+        else
+        {
+            log.info( "No RCS directory at "+rcsdir.getAbsolutePath() );
+        }
+    }
+
+    public void deleteVersion( String page, int version )
+    {
+        String         line = "<rcs not run>";
+        BufferedReader stderr  = null;
+        boolean        success = false;
+        String         cmd     = m_deleteVersionCommand;
+
+        log.debug("Deleting version "+version+" of page "+page);
+
+        cmd = TextUtil.replaceString( cmd, "%s", mangleName(page)+FILE_EXT );
+        cmd = TextUtil.replaceString( cmd, "%v", Integer.toString( version ) );
+
+        log.debug("Running command "+cmd);
+        Process process = null;
+
+        try
+        {
+            process = Runtime.getRuntime().exec( cmd, null, new File(getPageDirectory()) );
+
+            //
+            // 'rcs' command outputs to stderr methinks.
+            //
+
+            // FIXME: Should this use encoding as well?
+
+            stderr = new BufferedReader( new InputStreamReader(process.getErrorStream() ) );
+
+            while( (line = stderr.readLine()) != null )
+            {
+                log.debug( "LINE="+line );
+                if( line.equals("done") )
+                {
+                    success = true;
+                }
+            }
+        }
+        catch( IOException e )
+        {
+            log.error("Page deletion failed: ",e);
+        }
+        finally
+        {
+            try
+            {
+                if( stderr != null ) stderr.close();
+                if( process != null )
+                {
+                    process.getInputStream().close();
+                    process.getOutputStream().close();
+                }
+            }
+            catch( IOException e )
+            {
+                log.error("Cannot close streams for process while deleting page version.");
+            }
+        }
+
+        if( !success )
+        {
+            log.error("Version deletion failed. Last info from RCS is: "+line);
+        }
+    }
+
+    /**
+     *  util method to parse a date string in Local and UTC formats.  This method is synchronized
+     *  because SimpleDateFormat is not thread-safe.
+     *
+     *  @see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
+     */
+    private synchronized Date parseDate( String str )
+    {
+        Date d = null;
+
+        try
+        {
+            d = m_rcsdatefmt.parse( str );
+            return d;
+        }
+        catch ( ParseException pe )
+        { }
+
+        try
+        {
+            d = m_rcsdatefmt_utc.parse( str );
+            return d;
+        }
+        catch ( ParseException pe )
+        { }
+
+        return d;
+    }
+
+    public void movePage( String from,
+                          String to )
+        throws ProviderException
+    {
+        // XXX: Error checking could be better throughout this method.
+        File fromFile = findPage( from );
+        File toFile = findPage( to );
+
+        fromFile.renameTo( toFile );
+
+        String fromRCSName = "RCS/"+mangleName( from )+FILE_EXT+",v";
+        String toRCSName = "RCS/"+mangleName( to )+FILE_EXT+",v";
+
+        File fromRCSFile = new File( getPageDirectory(), fromRCSName );
+        File toRCSFile = new File( getPageDirectory(), toRCSName );
+
+        fromRCSFile.renameTo( toRCSFile );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RepositoryModifiedException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RepositoryModifiedException.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RepositoryModifiedException.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/RepositoryModifiedException.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,58 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001 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;
+
+/**
+ *  If the provider detects that someone has modified the repository
+ *  externally, it should throw this exception.
+ *  <p>
+ *  Any provider throwing this exception should first clean up any references
+ *  to the modified page it has, so that when we call this the next time,
+ *  the page is handled as completely, and we don't get the same exception
+ *  again.
+ *
+ *  @author Janne Jalkanen
+ *  @since  2.1.25
+ */
+public class RepositoryModifiedException
+    extends ProviderException
+{
+    private static final long serialVersionUID = 0L;
+
+    protected final String m_page;
+
+    /**
+     * Constructs the exception.
+     *
+     * @param msg
+     * @param pageName  The name of the page which was modified
+     */
+    public RepositoryModifiedException( String msg, String pageName )
+    {
+        super( msg );
+
+        m_page = pageName;
+    }
+
+    public String getPageName()
+    {
+        return m_page;
+    }
+}



Mime
View raw message