You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2009/05/02 16:15:14 UTC

svn commit: r770959 [1/2] - in /incubator/jspwiki/trunk: ./ src/java/org/apache/wiki/ src/java/org/apache/wiki/action/ src/java/org/apache/wiki/api/ src/java/org/apache/wiki/auth/ src/java/org/apache/wiki/content/ src/java/org/apache/wiki/event/ src/ja...

Author: ajaquith
Date: Sat May  2 14:15:13 2009
New Revision: 770959

URL: http://svn.apache.org/viewvc?rev=770959&view=rev
Log:
Major re-write of ReferenceManager. The refactoring included moving little-used WikiEngine.scanWikiLinks() to ReferenceManager.extractLinks(). This method should never have been in WikiEngine to begin with. ReferenceManager is also completely "unhooked" from FilterManager/PageEvent events, and instead listens to (new) ContentEvents that are emitted when JCR events happen. This is much, much cleaner and will allow PageRenamer to be eliminated Real Soon Now (and moved to ContentManager, where it belongs). SearchManager is similarly unhooked from FilterManager and listens for ContentEvents. Among other things, this also means that saved pages won't be indexed twice. 10 failing unit tests remain in ReferenceManager, and a few bugs still need to be pounded out. I will make one more major checkin of ReferenceManager, soon, to fix remaining tests. Overall passrate is now nearly 93%.

Added:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/event/ContentEvent.java
    incubator/jspwiki/trunk/tests/etc/TestMigratorForm.jsp
Modified:
    incubator/jspwiki/trunk/ChangeLog
    incubator/jspwiki/trunk/src/java/org/apache/wiki/ReferenceManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/WikiEngine.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/action/RenameActionBean.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/api/WikiPage.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/content/PageRenamer.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/plugin/ReferringPagesPlugin.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/plugin/UndefinedPagesPlugin.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/plugin/UnusedPagesPlugin.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/render/RenderingManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/search/SearchManager.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/ReferenceManagerTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/WikiEngineTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/action/RenameActionBeanTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/content/PageRenamerTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/search/SearchManagerTest.java

Modified: incubator/jspwiki/trunk/ChangeLog
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/ChangeLog?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/ChangeLog (original)
+++ incubator/jspwiki/trunk/ChangeLog Sat May  2 14:15:13 2009
@@ -1,3 +1,27 @@
+2009-05-02  Andrew Jaquith <ajaquith AT apache DOT org>
+
+        * 3.0.0-svn-108
+
+        * Major re-write of ReferenceManager. The refactoring included moving little-used
+        WikiEngine.scanWikiLinks() to ReferenceManager.extractLinks(). This method should
+        never have been in WikiEngine to begin with. ReferenceManager is also completely
+        "unhooked" from FilterManager/PageEvent events, and instead listens to (new)
+        ContentEvents that are emitted when JCR events happen. This is much, much cleaner
+        and will allow PageRenamer to be eliminated Real Soon Now (and moved to
+        ContentManager, where it belongs). SearchManager is similarly unhooked from
+        FilterManager and listens for ContentEvents. Among other things, this also means that
+        saved pages won't be indexed twice. 10 failing unit tests remain in ReferenceManager,
+        and a few bugs still need to be pounded out. I will make one more major checkin of
+        ReferenceManager, soon, to fix remaining tests. Overall passrate is now nearly 93%.
+        
+        * Fixed subtle bug in JCRWikiPage that was causing isAttachment() to incorrectly
+        report its status. Also, JCRWikiPage no longer manages its own references (now
+        handled by ReferenceManager) or saves itself to the JCR (it delegates to
+        ContentManager, so that ContentEvent creation can be guaranteed).
+        
+        * Added varargs to WikiEvent and subclasses, so that arbitrary numbers of
+        arguments can be supplied with events.
+
 2009-04-30  Janne Jalkanen <ja...@apache.org>
 
         * 3.0.0-svn-107

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/ReferenceManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/ReferenceManager.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/ReferenceManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/ReferenceManager.java Sat May  2 14:15:13 2009
@@ -20,591 +20,1134 @@
  */
 package org.apache.wiki;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Set;
-import java.util.TreeSet;
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.jcr.*;
 
-import org.apache.commons.lang.time.StopWatch;
+import org.apache.wiki.api.WikiException;
 import org.apache.wiki.api.WikiPage;
 import org.apache.wiki.content.ContentManager;
 import org.apache.wiki.content.PageNotFoundException;
 import org.apache.wiki.content.WikiPath;
-import org.apache.wiki.event.WikiEvent;
-import org.apache.wiki.event.WikiEventListener;
-import org.apache.wiki.event.WikiEventUtils;
-import org.apache.wiki.event.WikiPageEvent;
-import org.apache.wiki.filters.BasicPageFilter;
-import org.apache.wiki.log.Logger;
-import org.apache.wiki.log.LoggerFactory;
+import org.apache.wiki.event.*;
 import org.apache.wiki.modules.InternalModule;
+import org.apache.wiki.parser.JSPWikiMarkupParser;
+import org.apache.wiki.parser.MarkupParser;
 import org.apache.wiki.providers.ProviderException;
 import org.apache.wiki.util.TextUtil;
 
-
 /**
- *  Calculates wikipage references:
- *  <UL>
- *  <LI>What pages a given page refers to
- *  <LI>What pages refer to a given page
- *  </UL>
- *
+ * <p>
+ * Manages internal references between wikipages: which pages each page
+ * <em>refers to</em> (outbound links), and how page is <em>referred by</em>
+ * other pages (inbound). The pages that a given WikiPage refers to can be
+ * obtained by calling {@link #getRefersTo(WikiPath)}. The pages that refer to
+ * the page can be obtained via {@link #getReferredBy(WikiPath)}. In both
+ * cases, the argument supplied to these methods is a WikiPath that denotes a
+ * WikiPage.
+ * </p>
+ * <p>
+ * ReferenceManager stores outbound links ("refers to") with the WikiPage that
+ * originates the link, as the multi-valued property named {@value #REFERS_TO}}.
+ * Inbound links ("referred by") are stored in a separate part of the content
+ * repository, in the special path {@value #REFERENCES_ROOT}, where each node
+ * represents the page that is being linked to (<em>i.e,</em> a page link
+ * named in wiki markup). The multi-valued property named {@value #REFERRED_BY}
+ * stores the source of the link.
+ * </p>
+ * <p>
+ * To ensure that ReferenceManager operates as efficiently as possible, page
+ * references are recalculated only when changes to pages are made: that is,
+ * when they are saved, renamed or deleted. ReferenceManager listens for the
+ * events {@link ContentEvent#NODE_SAVED}, {@link ContentEvent#NODE_RENAMED}
+ * and {@link ContentEvent#NODE_DELETE_REQUEST}. When one of these events is
+ * detected, ReferenceManager updates the inbound and oubound link references as
+ * required. The end result of these choices means that ReferenceManager is
+ * relatively fast at reading references, but a bit slower at updating them.
+ * Given the asymmetric nature of most wikis -- there are usually far more
+ * readers than editors -- this is an appropriate way to do things.
+ * </p>
+ * <p>
+ * In addition to keeping track of links between pages, ReferenceManager also
+ * keeps two other lists up to date: the names of pages that are referenced by a
+ * WikiPage but haven't been created yet (uncreated); and the names of pages
+ * that have been created but not linked to by any other others (unreferenced).
+ * These lists are updated whenever wiki pages are saved, renamed or deleted.
+ * </p>
+ * <p>
+ * It is always possible that, despite JSPWiki's best efforts, that the link
+ * table becomes corrupt. When this happens, the method {@link #rebuild()} will
+ * erase and reconstruct all of the link references.
+ * </p>
  */
-public class ReferenceManager
-    extends BasicPageFilter
-    implements InternalModule, WikiEventListener
+public class ReferenceManager implements InternalModule, WikiEventListener
 {
 
     /** The WikiEngine that owns this object. */
-    private WikiEngine     m_engine;
+    private WikiEngine m_engine;
+
+    private ContentManager m_cm;
+
+    private boolean m_camelCase = false;
 
-    private boolean        m_matchEnglishPlurals = false;
+    private boolean m_matchEnglishPlurals = false;
 
-    private static Logger log = LoggerFactory.getLogger(ReferenceManager.class);
+    public static final String REFERS_TO = "wiki:refersTo";
 
     /** We use this also a generic serialization id */
     private static final long serialVersionUID = 4L;
 
-    private static final String REFERENCES_ROOT = "/wiki:references";
+    protected static final String REFERENCES_ROOT = "/wiki:references";
+
+    protected static final String NOT_REFERENCED = "notReferenced";
+
+    protected static final String NOT_CREATED = "notCreated";
+
+    private static final List<WikiPath> NO_LINKS = Collections.emptyList();
+
+    private static final String[] NO_VALUES = new String[0];
     
+
+    private static final Pattern LINK_PATTERN = Pattern
+        .compile( "([\\[\\~]?)\\[([^\\|\\]]*)(\\|)?([^\\|\\]]*)(\\|)?([^\\|\\]]*)\\]" );
+
+    /**
+     * JCR path path prefix for inbound "referredby" links, used by
+     * {@link #addReferredBy(WikiPath, List)}.
+     */
+    private static final String REFERRED_BY = "wiki:referredBy";
+
     /**
-     *  Builds a new ReferenceManager.
-     *
-     *  @param engine The WikiEngine to which this is managing references to.
+     * Default constructor that creates a new ReferenceManager. Callers must
+     * should call {@link #initialize(WikiEngine, Properties)} to activate the
+     * ReferenceManager.
      */
-    public ReferenceManager( WikiEngine engine )
+    public ReferenceManager()
     {
-        m_engine = engine;
+        // Do nothing, really.
+        super();
+    }
 
-        m_matchEnglishPlurals = TextUtil.getBooleanProperty( engine.getWikiProperties(),
-                                                             WikiEngine.PROP_MATCHPLURALS,
-                                                             m_matchEnglishPlurals );
+    /**
+     * Rebuilds the internal references database by parsing every wiki page.
+     * 
+     * @throws RepositoryException
+     * @throws LoginException
+     */
+    public void rebuild() throws RepositoryException
+    {
+        // Remove all of the 'referencedBy' inbound links
+        ContentManager cm = m_engine.getContentManager();
+        Session s = cm.getCurrentSession();
+
+        if( s.getRootNode().hasNode( REFERENCES_ROOT ) )
+        {
+            Node nd = s.getRootNode().getNode( REFERENCES_ROOT );
+            nd.remove();
+        }
+        s.save();
+
+        initReferredByNodes();
+        s.save();
+        cm.release();
+
+        // TODO: we should actually parse the pages
+    }
 
+    /**
+     * Verifies that the JCR nodes for storing references exist, and creates
+     * then if they do not. The nodes are NOT saved; that is the responsibility
+     * of callers.
+     */
+    private void initReferredByNodes() throws RepositoryException
+    {
+        ContentManager cm = m_engine.getContentManager();
+        Session s = cm.getCurrentSession();
+        if( !s.getRootNode().hasNode( REFERENCES_ROOT ) )
+        {
+            s.getRootNode().addNode( REFERENCES_ROOT );
+        }
     }
 
     /**
-     *  Initializes the entire reference manager with the initial set of pages
-     *  from the collection.
-     *
-     *  @param pages A collection of all pages you want to be included in the reference
-     *               count.
-     *  @since 2.2
-     *  @throws ProviderException If reading of pages fail.
+     * Initializes the reference manager. Scans all existing WikiPages for
+     * internal links and adds them to the ReferenceManager object.
+     * 
+     * @param engine The WikiEngine to which this is managing references to.
+     * @param props the properties for initializing the WikiEngine
+     * @throws WikiException If the reference manager initialization fails.
      */
-    public void initialize( Collection<WikiPage> pages )
-        throws ProviderException
+    public void initialize( WikiEngine engine, Properties props ) throws WikiException
     {
+        m_engine = engine;
+        m_cm = engine.getContentManager();
+
+        m_matchEnglishPlurals = TextUtil.getBooleanProperty( engine.getWikiProperties(), WikiEngine.PROP_MATCHPLURALS,
+                                                             m_matchEnglishPlurals );
+
+        m_camelCase = TextUtil.getBooleanProperty( m_engine.getWikiProperties(), JSPWikiMarkupParser.PROP_CAMELCASELINKS, false );
         try
         {
             Session session = m_engine.getContentManager().getCurrentSession();
-        
+
             if( !session.getRootNode().hasNode( REFERENCES_ROOT ) )
             {
                 session.getRootNode().addNode( REFERENCES_ROOT );
-        
+
                 session.save();
             }
         }
         catch( RepositoryException e )
         {
-            throw new ProviderException("Failed to initialize repository contents",e);
+            throw new ProviderException( "Failed to initialize repository contents", e );
         }
-        
-        log.debug( "Initializing new ReferenceManager with "+pages.size()+" initial pages." );
-        StopWatch sw = new StopWatch();
-        sw.start();
-        log.info( "Starting cross reference scan of WikiPages" );
 
-        /*
-        //
-        //  First, try to serialize old data from disk.  If that fails,
-        //  we'll go and update the entire reference lists (which'll take
-        //  time)
-        //
-        try
-        {
-            //
-            //  Unserialize things.  The loop below cannot be combined with
-            //  the other loop below, simply because engine.getPage() has
-            //  side effects such as loading initializing the user databases,
-            //  which in turn want all of the pages to be read already...
-            //
-            //  Yes, this is a kludge.  We know.  Will be fixed.
-            //
-            long saved = unserializeFromDisk();
+        // Make sure we catch any page add/save/rename events
+        WikiEventManager.addWikiEventListener( engine.getContentManager(), this );
+    }
 
-            for( Iterator it = pages.iterator(); it.hasNext(); )
-            {
-                WikiPage page = (WikiPage) it.next();
+    /**
+     * <p>
+     * Removes all links between a source page and one or more destination
+     * pages, and vice-versa. The source page must exist, although the
+     * destinations may not. The modified nodes are saved.
+     * <p>
+     * Within the m_refersTo map the pagename is a key. The whole key-value-set
+     * has to be removed to keep the map clean. Within the m_referredBy map the
+     * name is stored as a value. Since a key can have more than one value we
+     * have to delete just the key-value-pair referring page:deleted page.
+     * </p>
+     * 
+     * @param page Name of the page to remove from the maps.
+     * @throws PageNotFoundException if the source page does not exist
+     * @throws ProviderException
+     * @throws RepositoryException if the links cannot be reset
+     */
+    protected synchronized void removeLinks( WikiPath page ) throws ProviderException, RepositoryException
+    {
+        Session s = m_cm.getCurrentSession();
 
-                unserializeAttrsFromDisk( page );
-            }
+        // Remove all inbound links TO the page
+        // Let's pretend B and C ---> A
 
-            //
-            //  Now we must check if any of the pages have been changed
-            //  while we were in the electronic la-la-land, and update
-            //  the references for them.
-            //
+        // First, remove all inbound links from B & C to A
+        String jcrPath = getReferencedByJCRNode( page );
+        List<WikiPath> inboundLinks = getReferredBy( page );
+        for( WikiPath source : inboundLinks )
+        {
+            removeAllFromValues( jcrPath, REFERRED_BY, source.toString() );
+            s.save();
+        }
 
-            Iterator it = pages.iterator();
+        // Remove all outbound links FROM the page
+        // Let's pretend A ---> B and C
 
-            while( it.hasNext() )
-            {
-                WikiPage page = (WikiPage) it.next();
+        // Remove all inbound links from B &C to A
+        List<WikiPath> outboundLinks = getRefersTo( page );
+        for( WikiPath destination : outboundLinks )
+        {
+            jcrPath = ContentManager.getJCRPath( page );
+            removeAllFromValues( jcrPath, REFERS_TO, destination.toString() );
+            s.save();
 
-                if( page.isAttachment() )
-                {
-                    // Skip attachments
-                }
-                else
-                {
+            jcrPath = ContentManager.getJCRPath( destination );
+            removeAllFromValues( jcrPath, REFERS_TO, page.toString() );
+            s.save();
 
-                    // Refresh with the latest copy
-                    page = m_engine.getPage( page.getName() );
+            jcrPath = getReferencedByJCRNode( destination );
+            removeAllFromValues( jcrPath, REFERRED_BY, page.toString() );
+            s.save();
+        }
 
-                    if( page.getLastModified() == null )
-                    {
-                        log.error( "Provider returns null lastModified.  Please submit a bug report." );
-                    }
-                    else if( page.getLastModified().getTime() > saved )
-                    {
-                        updatePageReferences( page );
-                    }
-                }
-            }
+    }
 
-        }
-        catch( Exception e )
-        {
-            log.info("Unable to unserialize old refmgr information, rebuilding database: "+e.getMessage());
-            buildKeyLists( pages );
+    /**
+     * Build the path which is used to store the ReferredBy data
+     */
+    private String getReferencedByJCRNode( WikiPath name )
+    {
+        return "/wiki:references/" + name.getSpace() + "/" + name.getPath();
+    }
 
-            // Scan the existing pages from disk and update references in the manager.
-            Iterator it = pages.iterator();
-            while( it.hasNext() )
-            {
-                WikiPage page  = (WikiPage)it.next();
+    /**
+     * <p>
+     * Returns all pages that refers to a destination page. You can use this as
+     * a quick way of getting the inbound links to a page from other pages. The
+     * page being looked up need not exist. The requested page is not resolved
+     * in any way, so if the page is not found as specified exactly by the path,
+     * a zero-length list will be returned.
+     * </p>
+     * 
+     * @param destination the page to look up
+     * @return the list of pages that link to this page
+     * @throws ProviderException If something goes wrong
+     * @throws RepositoryException If the referredBy root cannot be checked (or
+     *             created)
+     * @since 3.0
+     */
+    public List<WikiPath> getReferredBy( WikiPath destination ) throws ProviderException
+    {
+        String jcrPath = getReferencedByJCRNode( destination );
 
-                if( page.isAttachment() )
-                {
-                    // We cannot build a reference list from the contents
-                    // of attachments, so we skip them.
-                }
-                else
-                {
-                    updatePageReferences( page );
+        try
+        {
+            jcrPath += "/" + REFERRED_BY;
 
-                    serializeAttrsToDisk( page );
-                }
+            Property p = (Property) m_engine.getContentManager().getCurrentSession().getItem( jcrPath );
+
+            ArrayList<WikiPath> result = new ArrayList<WikiPath>();
 
+            for( Value v : p.getValues() )
+            {
+                result.add( WikiPath.valueOf( v.getString() ) );
             }
 
-            serializeToDisk();
+            return result;
         }
-*/
-        sw.stop();
-        log.info( "Cross reference scan done in "+sw );
+        catch( PathNotFoundException e )
+        {
+            // Fine, we can return an empty set
+            return Collections.emptyList();
+        }
+        catch( RepositoryException e )
+        {
+            throw new ProviderException( "Unable to get the referred-by list", e );
+        }
+    }
+
+    /**
+     * Adds a "referredBy" inbound link to a page from a source page that links
+     * to it. That is, for the destination page, a "referredBy" entry is made
+     * that contains the name of the source page. Neither the source or
+     * destination pages need exist. This method saves the underlying Node after
+     * processing is complete.
+     * 
+     * @param page the page that is the destination for the link
+     * @param from the page that originates the link
+     * @throws RepositoryException if the underlying JCR node cannot be
+     *             retrieved
+     */
+    protected void addReferredBy( WikiPath page, WikiPath from ) throws RepositoryException
+    {
+        ContentManager cm = m_engine.getContentManager();
+        Session s = cm.getCurrentSession();
 
-        WikiEventUtils.addWikiEventListener(m_engine.getContentManager(),
-                                            WikiPageEvent.PAGE_DELETE_REQUEST, this);
+        // Make sure the 'referredBy' root exists
+        initReferredByNodes();
+
+        // Set the inverse 'referredBy' link for the destination (referred by
+        // the source)
+        String jcrPath = getReferencedByJCRNode( page );
+        addToValues( jcrPath, REFERRED_BY, from.toString() );
+
+        // Save the node
+        s.save();
     }
 
     /**
-     *  After the page has been saved, updates the reference lists.
-     *  
-     *  @param context {@inheritDoc}
-     *  @param content {@inheritDoc}
+     * Retrieves an array of Strings stored at a given JCR node and
+     * {@link javax.jcr.Property}. The property is assumed to return an array
+     * of {@link javax.jcr.Value} objects. If the node does not exist, a zero-length
+     * array is returned.
+     * 
+     * @param jcrNode the JCR path to the node
+     * @param property the property to read
+     * @throws RepositoryException
      */
-    public void postSave( WikiContext context, String content )
+    protected String[] getValues( String jcrNode, String property ) throws RepositoryException
     {
-        WikiPage page = context.getPage();
+        // Retrieve the destination node for the page
+        ContentManager cm = m_engine.getContentManager();
+        Node node = null;
+        try
+        {
+            node = (Node) cm.getCurrentSession().getItem( jcrNode );
+        }
+        catch( PathNotFoundException e )
+        {
+            return NO_VALUES;
+        }
 
+        // Retrieve the property; re-pack value array into String array
+        String[] stringValues = NO_VALUES;
         try
         {
-            updateReferences( page,
-                              context.getEngine().scanWikiLinks( page, content ) );
+            Property p = (Property) node.getProperty( property );
+            Value[] values = p.getValues();
+            stringValues = new String[values.length];
+            for( int i = 0; i < values.length; i++ )
+            {
+                stringValues[i] = values[i].getString();
+            }
         }
-        catch( ProviderException e )
+        catch( PathNotFoundException e )
         {
-            log.error("ReferenceManager updates failed, repo is now in inconsistent state!",e);
+            return NO_VALUES;
         }
+        
+        return stringValues;
     }
-
+    
     /**
-     * Updates the m_referedTo and m_referredBy hashmaps when a page has been
-     * deleted.
-     * <P>
-     * Within the m_refersTo map the pagename is a key. The whole key-value-set
-     * has to be removed to keep the map clean.
-     * Within the m_referredBy map the name is stored as a value. Since a key
-     * can have more than one value we have to delete just the key-value-pair
-     * referring page:deleted page.
-     *
-     *  @param pageName Name of the page to remove from the maps.
-     * @throws PageNotFoundException 
-     * @throws ProviderException 
+     * Adds a single String value to a given JCR node and
+     * {@link javax.jcr.Property}. The property is assumed to return an array
+     * of {@link javax.jcr.Value} objects. The node is created if it does not
+     * exist. Modifications to the node are not saved.
+     * 
+     * @param jcrNode the JCR path to the node
+     * @param property the property to add to
+     * @param value the value to add
+     * @param addAgain whether the value should be added again if it already exists in the list
      */
-    public synchronized void pageRemoved( WikiPath pageName ) throws ProviderException, PageNotFoundException
+    protected void addToValues( String jcrNode, String property, String newValue, boolean addAgain ) throws RepositoryException
     {
-        WikiPage page = m_engine.getContentManager().getPage( pageName );
-        
-        Collection<WikiPath> refTo = page.getRefersTo();
+        // Retrieve (or create) the destination node for the page
+        ContentManager cm = m_engine.getContentManager();
+        Session s = cm.getCurrentSession();
+        Node node = null;
+        try
+        {
+            node = (Node) cm.getCurrentSession().getItem( jcrNode );
+        }
+        catch( PathNotFoundException e )
+        {
+            if( !s.itemExists( jcrNode ) )
+            {
+                node = cm.createJCRNode( jcrNode );
+            }
+        }
 
-        for( WikiPath referred : refTo )
+        // Retrieve the property; add value to the end
+        List<String>newValues = new ArrayList<String>();
+        try
         {
-            log.debug( "Removing references to page %s from page %s", page.getPath(), referred );
-            Set<WikiPath> referredBy = findReferrers(referred);
-            
-            referredBy.remove( page.getPath() );
-            
-            try
+            boolean notFound = true;
+            Property p = (Property) node.getProperty( property );
+            Value[] values = p.getValues();
+            for( int i = 0; i < values.length; i++ )
             {
-                setReferredBy( referred, referredBy );
+                newValues.add( values[i].getString() );
+                if ( values[i].equals( newValue ) )
+                {
+                    notFound = false;
+                }
             }
-            catch( RepositoryException e )
+            if ( notFound || addAgain )
             {
-                throw new ProviderException("Failed to change the contents of pages",e);
+                newValues.add( newValue );
             }
+            node.setProperty( property, newValues.toArray(new String[newValues.size()]) );
+        }
+        catch( PathNotFoundException e )
+        {
+            node.setProperty( property, new String[]{ newValue } );
         }
-
     }
-
+    
     /**
-     *  Build the path which is used to store the ReferredBy data  
+     * Adds a single String value to a given JCR node and
+     * {@link javax.jcr.Property}. The property is assumed to return an array
+     * of {@link javax.jcr.Value} objects. The node is created if it does not
+     * exist. Modifications to the node are not saved.
+     * 
+     * @param jcrNode the JCR path to the node
+     * @param property the property to add to
+     * @param value the value to add. It it already exists in the list, it will
+     *            be added again.
      */
-    private final String getReferredByJCRPath(WikiPath name)
+    protected void addToValues( String jcrNode, String property, String newValue ) throws RepositoryException
     {
-        return "/wiki:references/"+name.getSpace()+"/"+name.getPath();
+        addToValues( jcrNode, property, newValue, true );
     }
-    
+
     /**
-     *  Finds all the pages which are referenced by the given page.
-     *  
-     *  @param name The WikiName to find.
-     *  @return A set of WikiNames. If the page in question does not exist, returns
-     *          an empty set.
-     *  @throws ProviderException If something goes wrong.
-     *  @since 3.0
+     * Removes a String value from a given JCR node and
+     * {@link javax.jcr.Property}. The property is assumed to return an array
+     * of {@link javax.jcr.Value} objects. The node is created if it does not
+     * exist. Modifications to the node are not saved.
+     * 
+     * @param jcrNode the JCR path to the node
+     * @param property the property to add to
+     * @param value the value to remove. All occurrences of the matching value
+     *            will be removed.
      */
-    public Set<WikiPath> findReferrers(WikiPath name) throws ProviderException
+    protected void removeAllFromValues( String jcrNode, String property, String value ) throws RepositoryException
     {
-        String jcrPath = getReferredByJCRPath( name );
-        
+        // Retrieve (or create) the destination node for the page
+        ContentManager cm = m_engine.getContentManager();
+        Session s = cm.getCurrentSession();
+        Node node = null;
         try
         {
-            jcrPath += "/wiki:referredBy";
-            
-            Property p = (Property)m_engine.getContentManager().getCurrentSession().getItem(jcrPath);
-            
-            TreeSet<WikiPath> result = new TreeSet<WikiPath>();
-            
-            for( Value v : p.getValues() )
+            node = (Node) cm.getCurrentSession().getItem( jcrNode );
+        }
+        catch( PathNotFoundException e )
+        {
+            if( !s.itemExists( jcrNode ) )
             {
-                result.add( WikiPath.valueOf( v.getString() ) );
+                node = cm.createJCRNode( jcrNode );
+            }
+        }
+
+        // Retrieve the property; remove all instances of value
+        List<String> newValues = new ArrayList<String>();
+        try
+        {
+            Property p = (Property) node.getProperty( property );
+            Value[] values = p.getValues();
+            for( int i = 0; i < values.length; i++ )
+            {
+                if( !values[i].getString().equals( value ) )
+                {
+                    newValues.add( values[i].getString() );
+                }
+            }
+            if( newValues.size() == 0 )
+            {
+                // This seems like a hack, but zero-length arrays don't seem to
+                // work
+                // unless we remove the property entirely first.
+                p.remove();
             }
-            
-            return result;
         }
         catch( PathNotFoundException e )
         {
-            // Fine, we can return an empty set
-            return new TreeSet<WikiPath>();
+            // No worries
         }
-        catch( RepositoryException e )
+
+        // Set/remove the property
+        if( newValues.size() > 0 )
         {
-            throw new ProviderException("Unable to get the referred-by list",e);
+            node.setProperty( property, newValues.toArray( new String[newValues.size()] ) );
         }
     }
-        
+
     /**
-     *  Set the referredBy attribute set.
-     *  
-     *  @param name
-     *  @param references
-     *  @throws RepositoryException
+     * <p>
+     * Sets links between a WikiPage (source) and a list of pages it links to
+     * (destinations). The source page must exist, but the destination paths
+     * need not. In the source WikiPage, existing outbound <code>refersTo</code>
+     * links for the page are replaced. For all destination pages the page
+     * previously linked to, these pages' inbound <code>referredBy</code>
+     * links are also replaced.
+     * </p>
+     * <p>
+     * Use this method when a new page has been saved, to a) set up its
+     * references and b) notify the referred pages of the references. This
+     * method does not synchronize the database to disk.
+     * </p>
+     * 
+     * @param source path of the page whose links should be updated
+     * @param destinations the paths the page should link to
+     * @throws ProviderException
+     * @throws RepositoryException
      */
-    private void setReferredBy(WikiPath name,Set<WikiPath> references) throws RepositoryException
+    protected synchronized void setLinks( WikiPath source, List<WikiPath> destinations )
+                                                                                        throws ProviderException,
+                                                                                            RepositoryException
     {
-        String jcrPath = getReferredByJCRPath( name );
-        Property p = null;
 
-        String[] value = new String[references.size()];
-        WikiPath[] refs = references.toArray(new WikiPath[references.size()]);
-        
-        for( int i = 0; i < references.size(); i++ )
+        Session s = m_cm.getCurrentSession();
+
+        // Remove all referredBy links from this page
+
+        // First, find all the current outbound links
+        List<WikiPath> oldDestinations = getRefersTo( source );
+        for( WikiPath oldDestination : oldDestinations )
         {
-            value[i] = refs[i].toString();
+            String jcrPath = getReferencedByJCRNode( oldDestination );
+            removeAllFromValues( jcrPath, REFERRED_BY, source.toString() );
         }
-        
-        ContentManager mgr = m_engine.getContentManager();
-        
-        try
-        {
-            p = (Property)mgr.getCurrentSession().getItem(jcrPath+"/wiki:referredBy");
 
-            p.setValue( value );
-        }
-        catch( PathNotFoundException e )
+        // Set the new outbound links
+        setRefersTo( source, destinations );
+
+        // Set the new referredBy links
+        for( WikiPath destination : destinations )
         {
-            Session s = mgr.getCurrentSession();
-            if( !s.itemExists( jcrPath ) )
-            {
-                mgr.createJCRNode(jcrPath);
-            }
-            
-            Node nd = (Node) s.getItem(jcrPath);
-            
-            nd.setProperty( "wiki:referredBy", value );
+            addReferredBy( destination, source );
         }
-        
-        mgr.getCurrentSession().getItem( "/wiki:references" ).save();
+
+        s.save();
     }
-    
+
     /**
-     *  Updates list of pages a new or edited WikiPage refers to. If a refersTo
-     *  entry for this page already exists, it is removed and a new one is built
-     *  from scratch. Also calls updateReferredBy() for each referenced page.
-     *  <P>
-     *  This is the method to call when a new page has been created and we
-     *  want to a) set up its references and b) notify the referred pages
-     *  of the references. Use this method during run-time.
-     *
-     *  @param page Name of the page to update.
-     *  @param references A Collection of Strings, each one pointing to a page this page references.
+     * Sets the "refersTo" outbound links between a source page and multiple
+     * destination pages. The source page must exist, although the destination
+     * pages need not.
+     * 
+     * @param source the page that originates the link
+     * @param destinations the pages that the source page links to
+     * @throws RepositoryException if the underlying JCR node cannot be
+     *             retrieved
      */
-    public synchronized void updateReferences( WikiPage page, Collection<WikiPath> references ) 
-        throws ProviderException
+    protected void setRefersTo( WikiPath source, List<WikiPath> destinations ) throws ProviderException, RepositoryException
     {
-        try
+        if( !m_cm.pageExists( source ) )
         {
-            internalUpdateReferences( (JCRWikiPage)page, references);
+            return;
         }
-        catch( RepositoryException e )
+
+        // Transform the destination paths into a String array
+        String[] destinationStrings = new String[destinations.size()];
+        for( int i = 0; i < destinations.size(); i++ )
         {
-            throw new ProviderException("Failed to update references",e);
+            destinationStrings[i] = destinations.get( i ).toString();
         }
+
+        // Retrieve the JCR node and add the 'refersTo' links
+        ContentManager cm = m_engine.getContentManager();
+        Node nd = cm.getJCRNode( ContentManager.getJCRPath( source ) );
+        nd.setProperty( REFERS_TO, destinationStrings );
+        nd.save();
     }
 
     /**
-     *  Updates the referred pages of a new or edited WikiPage. If a refersTo
-     *  entry for this page already exists, it is removed and a new one is built
-     *  from scratch. Also calls updateReferredBy() for each referenced page.
-     *  <p>
-     *  This method does not synchronize the database to disk.
-     *
-     *  @param page Name of the page to update.
-     *  @param newRefersTo A Collection of Strings, each one pointing to a page this page references.
-     * @throws ProviderException 
-     * @throws RepositoryException 
+     * <p>
+     * Returns a list of Strings representing pages that have been created,
+     * but not yet referenced in wiki markup by any other pages.
+     * Each not-referenced page name is shown only once.
+     * </p>
+     * 
+     * @return A list of Strings, where each names a page that hasn't been
+     *         created
      */
-
-    private void internalUpdateReferences(JCRWikiPage page, Collection<WikiPath> newRefersTo) throws ProviderException, RepositoryException
+    public List<String> findUnreferenced() throws RepositoryException
     {
-        //
-        //  Get the old refererences so that we can go and ping every page on that
-        //  list and make sure that their referredBy lists are fine.
-        //
-        Collection<WikiPath> oldRefersTo = page.getRefersTo();
+        String[] linkStrings= getValues( REFERENCES_ROOT, NOT_REFERENCED );
+        List<String> links = new ArrayList<String>();
+        for ( String link : linkStrings )
+        {
+            links.add( link );
+        }
+        return links;
+    }
 
-        //
-        //  Set up the new references list
-        //
+    /**
+     * <p>
+     * Returns a list of Strings representing pages that are referenced in wiki
+     * markup, but have not yet been created. Each non-existent page name is
+     * shown only once - we don't return information on who referred to it.
+     * </p>
+     * 
+     * @return A list of Strings, where each names a page that hasn't been
+     *         created
+     */
+    public List<String> findUncreated() throws RepositoryException
+    {
+        String[] linkStrings= getValues( REFERENCES_ROOT, NOT_CREATED );
+        List<String> links = new ArrayList<String>();
+        for ( String link : linkStrings )
+        {
+            links.add( link );
+        }
+        return links;
+    }
 
-        WikiPath[] wn = newRefersTo.toArray(new WikiPath[newRefersTo.size()]);
-        String[] nr = new String[newRefersTo.size()];
-        
-        for( int i = 0; i < nr.length; i++ )
+    /**
+     * Returns all pages that this page refers to. You can use this as a quick
+     * way of getting the links from a page, but note that it does not link any
+     * InterWiki, image, or external links. It does contain attachments, though.
+     * <p>
+     * The Collection returned is immutable, so you cannot change it. It does
+     * reflect the current status and thus is a live object. So, if you are
+     * using any kind of an iterator on it, be prepared for
+     * ConcurrentModificationExceptions.
+     * <p>
+     * The returned value is a Collection, because a page may refer to another
+     * page multiple times.
+     * 
+     * @param pageName Page name to query
+     * @return A Collection of Strings containing the names of the pages that
+     *         this page refers to. May return null, if the page does not exist
+     *         or has not been indexed yet.
+     * @throws PageNotFoundException
+     * @throws ProviderException
+     * @since 2.2.33
+     * @deprecated Use {@link #getRefersTo(String)} instead
+     */
+    public List<String> findRefersTo( String pageName ) throws ProviderException
+    {
+        List<WikiPath> links = getRefersTo( WikiPath.valueOf( pageName ) );
+        List<String> results = new ArrayList<String>();
+        for( WikiPath link : links )
         {
-            nr[i] = wn[i].toString();
+            results.add( link.toString() );
         }
-        
-        Node nd = page.getJCRNode();
-      
-        nd.setProperty( JCRWikiPage.REFERSTO, nr );
-        
-        //
-        //  Go ping the old pages that the reference list has changed.
-        //
-        
-        for( WikiPath name : oldRefersTo )
+        return results;
+    }
+
+    /**
+     * <p>
+     * Returns all destination pages that a page refers to. You can use this as
+     * a quick way of getting the outbound links from a page to the destination
+     * pages its markup refers to, but note that it does not link any InterWiki,
+     * image, or external links. It does contain attachments, though. The
+     * requested page is not resolved in any way, so if the page is not found as
+     * specified exactly by the path, a zero-length list will be returned.
+     * </p>
+     * 
+     * @param source the page to look up
+     * @return the list of pages this page links to
+     * @throws ProviderException
+     */
+    public List<WikiPath> getRefersTo( WikiPath source ) throws ProviderException
+    {
+        ContentManager cm = m_engine.getContentManager();
+        List<WikiPath> links = NO_LINKS;
+
+        try
         {
-            if( !newRefersTo.contains( name ) )
+            links = new ArrayList<WikiPath>();
+
+            Node node = cm.getJCRNode( ContentManager.getJCRPath( source ) );
+            Property p = node.getProperty( REFERS_TO );
+
+            Value[] values = p.getValues();
+
+            for( Value v : values )
             {
-                // A page is no longer referenced, so this page is removed from its
-                // referencedBy list.
-                Set<WikiPath> refs = findReferrers( name );
-                refs.remove( page.getPath() );
-                setReferredBy( name, refs );
+                links.add( WikiPath.valueOf( v.getString() ) );
             }
         }
-        
-        for( WikiPath name : newRefersTo )
+        catch( PathNotFoundException e )
         {
-            if( !oldRefersTo.contains(name) )
-            {
-                // There is a new reference which is not in the old references list,
-                // so we will need to add it to the new page's referencedBy list.
-                Set<WikiPath> refs = findReferrers( name );
-                refs.add( page.getPath() );
-                setReferredBy( name, refs );
-            }
+            // No worries! Just return the empty list...
         }
-        
+        catch( RepositoryException e )
+        {
+            throw new ProviderException( "Unable to get the referrals", e );
+        }
+        return links;
     }
 
+    /**
+     * Returns a list of all pages that the ReferenceManager knows about. This
+     * should be roughly equivalent to PageManager.getAllPages(), but without
+     * the potential disk access overhead. Note that this method is not
+     * guaranteed to return a Set of really all pages (especially during
+     * startup), but it is very fast.
+     * 
+     * @return A Set of all defined page names that ReferenceManager knows
+     *         about.
+     * @throws ProviderException
+     * @since 2.3.24
+     * @deprecated
+     */
+    public Set<String> findCreated() throws ProviderException
+    {
+        Set<String> result = new TreeSet<String>();
+        Collection<WikiPage> c = m_engine.getContentManager().getAllPages( null );
+
+        for( WikiPage p : c )
+            result.add( p.getPath().toString() );
 
+        return result;
+    }
 
     /**
-     *  Finds all unreferenced pages. This requires a linear scan through
-     *  m_referredBy to locate keys with null or empty values.
-     *  
-     *  @return The Collection of Strings
+     * {@inheritDoc} After the page has been saved, updates the reference lists.
      */
-    public synchronized Collection<String> findUnreferenced()
+    public void actionPerformed( WikiEvent event )
     {
-        ArrayList<String> unref = new ArrayList<String>();
+        if( !(event instanceof WikiPageEvent) )
+        {
+            return;
+        }
+
+        String pageName = ((WikiPageEvent) event).getPageName();
+        if( pageName == null )
+        {
+            return;
+        }
 
-        // FIXME: Not implemented yet
-        /*
-        for( String key : m_referredBy.keySet() )
+        try
         {
-            Set<?> refs = getReferenceList( m_referredBy, key );
-            
-            if( refs == null || refs.isEmpty() )
+            switch( event.getType() )
             {
-                unref.add( key );
+                // ========= page deleted ==============================
+                
+                // If page was deleted, remove all references to it/from it
+                case (ContentEvent.NODE_DELETE_REQUEST ): {
+                    WikiPath path = resolvePage( WikiPath.valueOf( pageName ) );
+                    List<WikiPath> referenced = getRefersTo( path );
+                    
+                    // Remove the links from the deleted page to its referenced pages
+                    removeLinks( path );
+                    
+                    // Check each previously-referenced page; see if they still have inbound refs
+                    for ( WikiPath ref : referenced )
+                    {
+                        if ( getReferredBy( ref ).size() == 0 )
+                        {
+                            addToValues( REFERENCES_ROOT, NOT_REFERENCED, pageName, false );
+                        }
+                        else
+                        {
+                            removeAllFromValues( REFERENCES_ROOT, NOT_REFERENCED, pageName );
+                        }
+                    }
+                    
+                    // Remove the deleted page from the 'uncreated' and 'unreferenced' lists
+                    removeAllFromValues( REFERENCES_ROOT, NOT_CREATED, pageName );
+                    removeAllFromValues( REFERENCES_ROOT, NOT_REFERENCED, pageName );
+                    
+                    m_cm.getCurrentSession().save();
+                    
+                    break;
+                }
+
+                // ========= page saved ==============================
+                
+                // If page was saved, update all references
+                case (ContentEvent.NODE_SAVED ): {
+                    WikiPath path = resolvePage( WikiPath.valueOf( pageName ) );
+                    
+                    // Get old linked pages, and add to 'unreferenced list' if needed
+                    List<WikiPath> referenced = extractLinks( path );
+                    for ( WikiPath ref : referenced )
+                    {
+                        ref = resolvePage( ref );
+                        List<WikiPath> referredBy = getReferredBy( ref );
+                        boolean unreferenced = referredBy.size() == 0 || ( referredBy.size() == 1 && referredBy.contains( path ) );
+                        if ( unreferenced )
+                        {
+                            addToValues( REFERENCES_ROOT, NOT_REFERENCED, ref.toString() );
+                        }
+                    }
+                    
+                    // Get the new linked pages, and set refersTo/referencedBy links
+                    referenced = extractLinks( path );
+                    setLinks( path, referenced );
+                    
+                    // Remove the saved page from the 'uncreated' list
+                    removeAllFromValues( REFERENCES_ROOT, NOT_CREATED, pageName );
+                    
+                    // Subtract each link from the 'unreferenced' list; possibly subtract from 'uncreated'
+                    for ( WikiPath ref : referenced )
+                    {
+                        ref = resolvePage( ref );
+                        removeAllFromValues( REFERENCES_ROOT, NOT_REFERENCED, ref.toString() );
+                        if ( m_cm.pageExists( ref ) )
+                        {
+                            removeAllFromValues( REFERENCES_ROOT, NOT_CREATED, ref.toString() );
+                        }
+                        else
+                        {
+                            addToValues( REFERENCES_ROOT, NOT_CREATED, ref.toString(), false );
+                        }
+                    }
+                    
+                    m_cm.getCurrentSession().save();
+                    
+                    break;
+                }
+
+                // ========= page renamed ==============================
+                
+                case (ContentEvent.NODE_RENAMED ): {
+                    // Update references from this page
+                    WikiPath toPage = WikiPath.valueOf( pageName );
+                    WikiPath fromPage = WikiPath.valueOf( (String) ((WikiPageEvent) event).getArgs()[0] );
+                    Boolean changeReferrers = (Boolean) ((WikiPageEvent) event).getArgs()[1];
+                    removeLinks( fromPage );
+                    setLinks( toPage, extractLinks( toPage ) );
+
+                    // Change references to the old page; use the new name
+                    if( changeReferrers )
+                    {
+                        renameLinksTo( fromPage, toPage );
+                    }
+                    
+                    m_cm.getCurrentSession().save();
+                    
+                    break;
+                }
             }
         }
-*/
-        return unref;
+        catch( PageNotFoundException e )
+        {
+            e.printStackTrace();
+        }
+        catch( ProviderException e )
+        {
+            e.printStackTrace();
+        }
+        catch( RepositoryException e )
+        {
+            e.printStackTrace();
+        }
     }
 
+    private WikiPath resolvePage( WikiPath path ) throws ProviderException
+    {
+        WikiPath finalPath = m_engine.getFinalPageName( path );
+        return finalPath == null ? path : finalPath;
+    }
 
     /**
-     * Finds all references to non-existant pages. This requires a linear
-     * scan through m_refersTo values; each value must have a corresponding
-     * key entry in the reference Maps, otherwise such a page has never
-     * been created.
-     * <P>
-     * Returns a Collection containing Strings of unreferenced page names.
-     * Each non-existant page name is shown only once - we don't return information
-     * on who referred to it.
+     * This method finds all the pages which refer to <code>oldPath</code> and
+     * change their references to <code>newPath</code>.
      * 
-     * @return A Collection of Strings
+     * @param oldPath The old page
+     * @param newPath The new page
      */
-    public synchronized Collection<String> findUncreated()
+    private void renameLinksTo( WikiPath oldPath, WikiPath newPath ) throws ProviderException, RepositoryException
     {
-        TreeSet<String> uncreated = new TreeSet<String>();
+        List<WikiPath> referrers = getReferredBy( oldPath );
+        if( referrers.isEmpty() )
+            return; // No referrers
+
+        for( WikiPath path : referrers )
+        {
+            // In case the page was just changed from under us, let's do this
+            // small kludge.
+            if( path.equals( oldPath.getPath() ) )
+            {
+                path = newPath;
+            }
 
-        // Go through m_refersTo values and check that m_refersTo has the corresponding keys.
-        // We want to reread the code to make sure our HashMaps are in sync...
+            try
+            {
+                ContentManager cm = m_engine.getContentManager();
+                WikiPage p = cm.getPage( path );
 
-        // FIXME: Not yet done
-        /*
-        Collection<Collection<String>> allReferences = m_refersTo.values();
+                String sourceText = m_engine.getPureText( p );
 
-        for( Collection<String> refs : allReferences )
-        {
-            if( refs != null )
-            {
-                for( String aReference : refs )
+                String newText = renameLinks( sourceText, oldPath.toString(), newPath.toString() );
+
+                if( m_camelCase )
+                    newText = renameCamelCaseLinks( newText, oldPath.toString(), newPath.toString() );
+
+                if( !sourceText.equals( newText ) )
                 {
-                    if( m_engine.pageExists( aReference ) == false )
-                    {
-                        uncreated.add( aReference );
-                    }
+                    p.setAttribute( WikiPage.CHANGENOTE, oldPath.toString() + " ==> " + newPath.toString() );
+                    p.setContent( newText );
+                    // TODO: do we want to set the author here? (We used to...)
+                    cm.save( p );
+                    setLinks( path, extractLinks( newPath ) );
                 }
             }
+            catch( PageNotFoundException e )
+            {
+                // Just continue
+            }
         }
-         */
-        return uncreated;
     }
 
     /**
-     *  Returns all pages that this page refers to.  You can use this as a quick
-     *  way of getting the links from a page, but note that it does not link any
-     *  InterWiki, image, or external links.  It does contain attachments, though.
-     *  <p>
-     *  The Collection returned is unmutable, so you cannot change it.  It does reflect
-     *  the current status and thus is a live object.  So, if you are using any
-     *  kind of an iterator on it, be prepared for ConcurrentModificationExceptions.
-     *  <p>
-     *  The returned value is a Collection, because a page may refer to another page
-     *  multiple times.
-     *
-     * @param pageName Page name to query
-     * @return A Collection of Strings containing the names of the pages that this page
-     *         refers to. May return null, if the page does not exist or has not
-     *         been indexed yet.
-     * @throws PageNotFoundException 
-     * @throws ProviderException 
-     * @since 2.2.33
-     * @deprecated Use {@link org.apache.wiki.api.WikiPage.getRefersTo()} instead
+     * Reads a WikiPage full of data from a String and returns all links
+     * internal to this Wiki in a Collection.
+     * 
+     * @param page The WikiPage to scan
+     * @return a Collection of Strings
+     * @throws ProviderException if the page contents cannot be retrieved, or if
+     *             MarkupParser canot parse the document
      */
-    public Collection<String> findRefersTo( String pageName ) throws ProviderException, PageNotFoundException
+    protected List<WikiPath> extractLinks( WikiPath path ) throws PageNotFoundException, ProviderException
     {
-        ArrayList<String> result = new ArrayList<String>();
-        
-        Collection<WikiPath> refs = m_engine.getPage( pageName ).getRefersTo();
-        
-        for( WikiPath wn : refs )
-            result.add( wn.toString() );
-        
-        return result;
+        // Set up a streamlined parser to collect links
+        WikiPage page = m_engine.getPage( path );
+        LinkCollector localCollector = new LinkCollector();
+        String pagedata = page.getContentAsString();
+        WikiContext context = m_engine.getWikiContextFactory().newViewContext( page );
+        MarkupParser mp = m_engine.getRenderingManager().getParser( context, pagedata );
+        mp.addLocalLinkHook( localCollector );
+        mp.disableAccessRules();
+
+        // Parse the page, and collect the links
+        try
+        {
+            mp.parse();
+        }
+        catch( IOException e )
+        {
+            // Rethrow any parsing exceptions
+            throw new ProviderException( "Could not parse the document.", e );
+        }
+
+        // Return a WikiPath for each link
+        ArrayList<WikiPath> links = new ArrayList<WikiPath>();
+        for( String s : localCollector.getLinks() )
+        {
+            WikiPath finalPath = resolvePage( WikiPath.valueOf( s ) );
+            links.add( finalPath );
+        }
+
+        return links;
     }
 
+    /**
+     * Replaces camelcase links.
+     */
+    private static String renameCamelCaseLinks( String sourceText, String from, String to )
+    {
+        StringBuilder sb = new StringBuilder( sourceText.length() + 32 );
+
+        Pattern linkPattern = Pattern.compile( "\\p{Lu}+\\p{Ll}+\\p{Lu}+[\\p{L}\\p{Digit}]*" );
+
+        Matcher matcher = linkPattern.matcher( sourceText );
+
+        int start = 0;
+
+        while ( matcher.find( start ) )
+        {
+            String match = matcher.group();
+
+            sb.append( sourceText.substring( start, matcher.start() ) );
+
+            int lastOpenBrace = sourceText.lastIndexOf( '[', matcher.start() );
+            int lastCloseBrace = sourceText.lastIndexOf( ']', matcher.start() );
+
+            if( match.equals( from ) && lastCloseBrace >= lastOpenBrace )
+            {
+                sb.append( to );
+            }
+            else
+            {
+                sb.append( match );
+            }
+
+            start = matcher.end();
+        }
+
+        sb.append( sourceText.substring( start ) );
+
+        return sb.toString();
+    }
 
     /**
-     *  Returns a list of all pages that the ReferenceManager knows about.
-     *  This should be roughly equivalent to PageManager.getAllPages(), but without
-     *  the potential disk access overhead.  Note that this method is not guaranteed
-     *  to return a Set of really all pages (especially during startup), but it is
-     *  very fast.
-     *
-     *  @return A Set of all defined page names that ReferenceManager knows about.
-     *  @throws ProviderException 
-     *  @since 2.3.24
-     *  @deprecated
+     * Renames a link in a given source text into a new name, and returns the
+     * transformed text.
+     * 
+     * @param sourceText the source text
+     * @param from the link to change, for example, "Main"
+     * @param to the name to change the link to, for example "RenamedMain"
+     * @return the transformed text
      */
-    public Set<String> findCreated() throws ProviderException
+    protected static String renameLinks( String sourceText, String from, String to )
     {
-        Set<String> result = new TreeSet<String>();
-        Collection<WikiPage> c = m_engine.getContentManager().getAllPages( null );
-        
-        for( WikiPage p : c )
-            result.add( p.getPath().toString() );
-        
-        return result;
+        StringBuilder sb = new StringBuilder( sourceText.length() + 32 );
+
+        //
+        // This monstrosity just looks for a JSPWiki link pattern. But it is
+        // pretty
+        // cool for a regexp, isn't it? If you can understand this in a single
+        // reading,
+        // you have way too much time in your hands.
+        //
+        Matcher matcher = LINK_PATTERN.matcher( sourceText );
+
+        int start = 0;
+
+        // System.out.println("====");
+        // System.out.println("SRC="+sourceText.trim());
+        while ( matcher.find( start ) )
+        {
+            char charBefore = (char) -1;
+
+            if( matcher.start() > 0 )
+                charBefore = sourceText.charAt( matcher.start() - 1 );
+
+            if( matcher.group( 1 ).length() > 0 || charBefore == '~' || charBefore == '[' )
+            {
+                //
+                // Found an escape character, so I am escaping.
+                //
+                sb.append( sourceText.substring( start, matcher.end() ) );
+                start = matcher.end();
+                continue;
+            }
+
+            String text = matcher.group( 2 );
+            String link = matcher.group( 4 );
+            String attr = matcher.group( 6 );
+
+            /*
+             * System.out.println("MATCH="+matcher.group(0));
+             * System.out.println(" text="+text); System.out.println("
+             * link="+link); System.out.println(" attr="+attr);
+             */
+            if( link.length() == 0 )
+            {
+                text = renameLink( text, from, to );
+            }
+            else
+            {
+                link = renameLink( link, from, to );
+
+                //
+                // A very simple substitution, but should work for quite a few
+                // cases.
+                //
+                text = TextUtil.replaceString( text, from, to );
+            }
+
+            //
+            // Construct the new string
+            //
+            sb.append( sourceText.substring( start, matcher.start() ) );
+            sb.append( "[" + text );
+            if( link.length() > 0 )
+                sb.append( "|" + link );
+            if( attr.length() > 0 )
+                sb.append( "|" + attr );
+            sb.append( "]" );
+
+            start = matcher.end();
+        }
+
+        sb.append( sourceText.substring( start ) );
+
+        return sb.toString();
     }
 
     /**
-     *  {@inheritDoc}
+     * This method does a correct replacement of a single link, taking into
+     * account anchors and attachments.
      */
-    public void actionPerformed(WikiEvent event)
+    private static String renameLink( String original, String from, String newlink )
     {
-        if( (event instanceof WikiPageEvent) && (event.getType() == WikiPageEvent.PAGE_DELETE_REQUEST) )
+        int hash = original.indexOf( '#' );
+        int slash = original.indexOf( '/' );
+        String reallink = original;
+        String oldStyleRealLink;
+
+        if( hash != -1 )
+            reallink = original.substring( 0, hash );
+        if( slash != -1 )
+            reallink = original.substring( 0, slash );
+
+        reallink = MarkupParser.cleanLink( reallink );
+        oldStyleRealLink = MarkupParser.wikifyLink( reallink );
+
+        // WikiPage realPage = context.getEngine().getPage( reallink );
+        // WikiPage p2 = context.getEngine().getPage( from );
+
+        // System.out.println(" "+reallink+" :: "+ from);
+        // System.out.println(" "+p+" :: "+p2);
+
+        //
+        // Yes, these point to the same page.
+        //
+        if( reallink.equals( from ) || original.equals( from ) || oldStyleRealLink.equals( from ) )
         {
-            String pageName = ((WikiPageEvent) event).getPageName();
+            //
+            // if the original contains blanks, then we should introduce a link,
+            // for example: [My Page] => [My Page|My Renamed Page]
+            int blank = reallink.indexOf( " " );
 
-            if( pageName != null )
+            if( blank != -1 )
             {
-                try
-                {
-                    pageRemoved( WikiPath.valueOf(pageName) );
-                }
-                catch( ProviderException e )
-                {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
-                }
-                catch( PageNotFoundException e )
-                {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
-                }
+                return original + "|" + newlink;
             }
+
+            return newlink + ((hash > 0) ? original.substring( hash ) : "") + ((slash > 0) ? original.substring( slash ) : "");
         }
+
+        return original;
     }
 }

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java Sat May  2 14:15:13 2009
@@ -77,7 +77,7 @@
      *  <p>
      *  If the build identifier is empty, it is not added.
      */
-    public static final String     BUILD         = "107";
+    public static final String     BUILD         = "108";
     
     /**
      *  This is the generic version string you should use

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/WikiEngine.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/WikiEngine.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/WikiEngine.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/WikiEngine.java Sat May  2 14:15:13 2009
@@ -556,8 +556,6 @@
             // m_filterManager     = (FilterManager)ClassUtil.getMappedObject(FilterManager.class.getName(), this, props );
             m_renderingManager  = (RenderingManager) ClassUtil.getMappedObject(RenderingManager.class.getName());
 
-            m_searchManager     = (SearchManager)ClassUtil.getMappedObject(SearchManager.class.getName(), this, props );
-
             m_authenticationManager = (AuthenticationManager) ClassUtil.getMappedObject(AuthenticationManager.class.getName());
             m_authorizationManager  = (AuthorizationManager) ClassUtil.getMappedObject( AuthorizationManager.class.getName());
             m_userManager           = (UserManager) ClassUtil.getMappedObject(UserManager.class.getName());
@@ -601,20 +599,15 @@
             m_renderingManager.initialize( this, props );
 
             //
-            //  ReferenceManager has the side effect of loading all
-            //  pages.  Therefore after this point, all page attributes
-            //  are available.
-            //
-            //  initReferenceManager is indirectly using m_filterManager, therefore
-            //  it has to be called after it was initialized.
-            //
-            initReferenceManager();
-
-            //
-            //  Hook the different manager routines into the system.
+            //  ReferenceManager and SearchManager  must start after ContentManager does.
             //
-            getFilterManager().addPageFilter(m_referenceManager, -1001 );
-            getFilterManager().addPageFilter(m_searchManager, -1002 );
+            m_referenceManager = (ReferenceManager)
+                ClassUtil.getMappedObject(ReferenceManager.class.getName() );
+            m_referenceManager.initialize( this, props );
+            
+            m_searchManager     = (SearchManager)
+                ClassUtil.getMappedObject(SearchManager.class.getName() );
+            m_searchManager.initialize( this, props );
         }
 
         catch( RuntimeException e )
@@ -695,35 +688,6 @@
     }
 
     /**
-     *  Initializes the reference manager. Scans all existing WikiPages for
-     *  internal links and adds them to the ReferenceManager object.
-     *
-     *  @throws WikiException If the reference manager initialization fails.
-     */
-    public void initReferenceManager() throws WikiException
-    {
-        try
-        {
-            ArrayList<WikiPage> pages = new ArrayList<WikiPage>();
-            pages.addAll( m_contentManager.getAllPages(null) );
-
-            // Build a new manager with default key lists.
-            if( m_referenceManager == null )
-            {
-                m_referenceManager =
-                    (ReferenceManager) ClassUtil.getMappedObject(ReferenceManager.class.getName(), this );
-                m_referenceManager.initialize( pages );
-            }
-
-        }
-        catch( ProviderException e )
-        {
-            log.error("PageProvider is unable to list pages: ", e);
-        }
-    }
-
-
-    /**
      *  Throws an exception if a property is not found.
      *
      *  @param props A set of properties to search the key in.
@@ -1580,36 +1544,6 @@
     }
 
     /**
-     *  Reads a WikiPage full of data from a String and returns all links
-     *  internal to this Wiki in a Collection.
-     *
-     *  @param page The WikiPage to scan
-     *  @param pagedata The page contents
-     *  @return a Collection of Strings
-     */
-    public List<WikiPath> scanWikiLinks( WikiPage page, String pagedata )
-    {
-        LinkCollector localCollector = new LinkCollector();
-
-        textToHTML( m_contextFactory.newViewContext( page ),
-                    pagedata,
-                    localCollector,
-                    null,
-                    localCollector,
-                    false,
-                    true );
-
-        ArrayList<WikiPath> response = new ArrayList<WikiPath>();
-        
-        for( String s : localCollector.getLinks() )
-        {
-            response.add( WikiPath.valueOf( s ) );
-        }
-        
-        return response;
-    }
-
-    /**
      *  Just convert WikiText to HTML.
      *
      *  @param context The WikiContext in which to do the conversion
@@ -1725,21 +1659,6 @@
     }
 
     /**
-     *  Updates all references for the given page.
-     *
-     *  @param page wiki page for which references should be updated
-     * @throws ProviderException 
-     */
-    public void updateReferences( WikiPage page ) throws ProviderException
-    {
-        String pageData = getPureText( page.getName(), WikiProvider.LATEST_VERSION );
-
-        m_referenceManager.updateReferences( page,
-                                             scanWikiLinks( page, pageData ) );
-    }
-
-
-    /**
      *  Writes the WikiText of a page into the
      *  page repository. If the <code>jspwiki.properties</code> file contains
      *  the property <code>jspwiki.approver.workflow.saveWikiPage</code> and
@@ -2359,7 +2278,7 @@
                               boolean changeReferrers)
         throws WikiException
     {
-        return m_pageRenamer.renamePage(context, renameFrom, renameTo, changeReferrers);
+        return m_contentManager.renamePage(context, renameFrom, renameTo, changeReferrers);
     }
 
     /**

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/action/RenameActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/action/RenameActionBean.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/action/RenameActionBean.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/action/RenameActionBean.java Sat May  2 14:15:13 2009
@@ -35,6 +35,7 @@
 import org.apache.wiki.WikiEngine;
 import org.apache.wiki.api.WikiException;
 import org.apache.wiki.auth.permissions.PagePermission;
+import org.apache.wiki.content.PageRenamer;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
 import org.apache.wiki.ui.stripes.HandlerPermission;
@@ -120,7 +121,8 @@
         HttpServletRequest request = getContext().getRequest();
         log.info( "Page rename request for page '" + renameFrom + "' to new name '" + m_renameTo + "' from "
                   + request.getRemoteAddr() + " by " + request.getRemoteUser() );
-        String renamedTo = engine.renamePage( getContext(), renameFrom, m_renameTo, m_changeReferences );
+        PageRenamer renamer = engine.getPageRenamer();
+        String renamedTo = renamer.renamePage( getContext(), renameFrom, m_renameTo, m_changeReferences );
         
         deleteFromBreadCrumb( renameFrom );
         log.info( "Page successfully renamed to '" + renamedTo + "'" );

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/api/WikiPage.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/api/WikiPage.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/api/WikiPage.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/api/WikiPage.java Sat May  2 14:15:13 2009
@@ -22,7 +22,6 @@
 
 import java.io.InputStream;
 import java.io.Serializable;
-import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -214,6 +213,7 @@
      *  repository.
      *  
      *  @throws ProviderException If the save cannot be completed.
+     *  @deprecated use {@link org.apache.wiki.content.ContentManager#save(WikiPage)} instead
      */
     public void save() throws ProviderException;
     
@@ -241,7 +241,7 @@
      *          page refers to no other pages.
      *  @throws ProviderException If the references cannot be fetched.
      */
-    public Collection<WikiPath> getRefersTo() throws ProviderException;
+    public List<WikiPath> getRefersTo() throws ProviderException;
     
     /**
      *  Returns the parent of the page. 

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java Sat May  2 14:15:13 2009
@@ -76,8 +76,8 @@
     private static Logger log = LoggerFactory.getLogger(UserManager.class);
 
     /** Message key for the "save profile" message. */
-    public  static final String SAVE_APPROVER               = "workflow.createUserProfile";
-    private static final String PROP_DATABASE               = "jspwiki.userdatabase";
+    public static final String SAVE_APPROVER               = "workflow.createUserProfile";
+    public static final String PROP_DATABASE               = "jspwiki.userdatabase";
     protected static final String SAVE_TASK_MESSAGE_KEY     = "task.createUserProfile";
     protected static final String SAVED_PROFILE             = "userProfile";
     protected static final String SAVE_DECISION_MESSAGE_KEY = "decision.createUserProfile";

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java?rev=770959&r1=770958&r2=770959&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/content/ContentManager.java Sat May  2 14:15:13 2009
@@ -54,6 +54,7 @@
 import org.apache.wiki.event.*;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
+import org.apache.wiki.parser.MarkupParser;
 import org.apache.wiki.providers.ProviderException;
 import org.apache.wiki.util.TextUtil;
 import org.apache.wiki.util.WikiBackgroundThread;
@@ -173,6 +174,8 @@
     
     private static final String DEFAULT_WORKSPACE = "jspwiki";
     
+    private static final Serializable[] NO_ARGS = new Serializable[0];
+
     static Logger log = LoggerFactory.getLogger( ContentManager.class );
 
     protected HashMap<String,PageLock> m_pageLocks = new HashMap<String,PageLock>();
@@ -327,6 +330,22 @@
     }
     
     /**
+     * Saves a WikiPage to the repositiry.
+     * @param page
+     */
+    public void save( WikiPage page ) throws RepositoryException
+    {
+        WikiPath path = page.getPath();
+        Node nd = getJCRNode( getJCRPath( path ) );
+        if( nd.isNew() )
+            nd.getParent().save();
+        else
+            nd.save();
+        
+        fireEvent( ContentEvent.NODE_SAVED, page.getName(), NO_ARGS );
+    }
+    
+    /**
      *  Shuts down the ContentManager in a good fashion.
      */
     @SuppressWarnings("unchecked")
@@ -511,7 +530,7 @@
 
         synchronized( m_pageLocks )
         {
-            fireEvent( WikiPageEvent.PAGE_LOCK, page.getName() ); // prior to or after actual lock?
+            fireEvent( WikiPageEvent.PAGE_LOCK, page.getName(), NO_ARGS ); // prior to or after actual lock?
 
             lock = m_pageLocks.get( page.getName() );
 
@@ -554,7 +573,7 @@
             log.debug( "Unlocked page "+lock.getPage() );
         }
 
-        fireEvent( WikiPageEvent.PAGE_UNLOCK, lock.getPage() );
+        fireEvent( WikiPageEvent.PAGE_UNLOCK, lock.getPage(), NO_ARGS );
     }
 
     /**
@@ -810,7 +829,7 @@
     public boolean deleteVersion( WikiPage page )
         throws ProviderException
     {
-        fireEvent( WikiPageEvent.PAGE_DELETE_REQUEST, page.getName() );
+        fireEvent( ContentEvent.NODE_DELETE_REQUEST, page.getName(), NO_ARGS );
 
         JCRWikiPage jcrPage = (JCRWikiPage)page;
         try
@@ -818,7 +837,7 @@
             jcrPage.getJCRNode().remove();
             jcrPage.save();
             
-            fireEvent( WikiPageEvent.PAGE_DELETED, jcrPage.getName() );
+            fireEvent( ContentEvent.NODE_DELETED, page.getName(), NO_ARGS );
             
             return true;
         }
@@ -844,7 +863,7 @@
     public boolean deletePage( WikiPage page )
         throws ProviderException
     {
-        fireEvent( WikiPageEvent.PAGE_DELETE_REQUEST, page.getName() );
+        fireEvent( ContentEvent.NODE_DELETE_REQUEST, page.getName(), NO_ARGS );
 
         VersionHistory vh;
         try
@@ -871,7 +890,7 @@
             
             nd.getParent().save();
             
-            fireEvent( WikiPageEvent.PAGE_DELETED, page.getName() );
+            fireEvent( ContentEvent.NODE_DELETED, page.getName(), NO_ARGS );
             
             return true;
         }
@@ -996,8 +1015,8 @@
 
             // Stash the page ACL, author, attributes, modified-date, name and new text as workflow attributes
 
-            // FIXME: This does not work, since the attribute list can be exceedingly big (in the order of gigabytes).
-            //        Alternate method required.
+            // FIXME: This works now,  but will not scale, because the attribute list can be exceedingly
+            // big (in the order of gigabytes). Alternate method required.
             
             workflow.setAttribute( PRESAVE_PAGE_ACL, page.getAcl() );
             workflow.setAttribute( PRESAVE_PAGE_AUTHOR, author );
@@ -1096,6 +1115,121 @@
         }
     }
 
+    // page renaming code....................................................
+    
+    /**
+     *  Renames a page.
+     *  
+     *  @param context The current context.
+     *  @param renameFrom The name from which to rename.
+     *  @param renameTo The new name.
+     *  @param changeReferrers If true, also changes all the referrers.
+     *  @return The final new name (in case it had to be modified)
+     *  @throws WikiException If the page cannot be renamed.
+     */
+    public String renamePage( WikiContext context, 
+                              String renameFrom, 
+                              String renameTo, 
+                              boolean changeReferrers )
+        throws WikiException
+    {
+        //
+        //  Sanity checks first
+        //
+        if( renameFrom == null || renameFrom.length() == 0 )
+        {
+            throw new WikiException( "From name may not be null or empty" );
+        }
+        if( renameTo == null || renameTo.length() == 0 )
+        {
+            throw new WikiException( "To name may not be null or empty" );
+        }
+       
+        //
+        //  Clean up the "to" -name so that it does not contain anything illegal
+        //
+        
+        renameTo = MarkupParser.cleanLink( renameTo.trim() );
+        
+        if( renameTo.equals(renameFrom) )
+        {
+            throw new WikiException( "You cannot rename the page to itself" );
+        }
+        
+        //
+        //  Preconditions: "from" page must exist, and "to" page must not yet exist.
+        //
+        WikiEngine engine = context.getEngine();
+        WikiPage fromPage;
+        try
+        {
+            fromPage = engine.getPage( renameFrom );
+        }
+        catch( PageNotFoundException e )
+        {
+            throw new WikiException("No such page "+renameFrom, e );
+        }
+        
+        WikiPage toPage;
+        try
+        {
+            toPage = engine.getPage( renameTo );
+            if( toPage != null )
+            {
+                throw new WikiException("Page already exists "+renameTo);
+            }
+        }
+        catch( PageNotFoundException e )
+        {
+            // Good. The page should NOT exist already.
+        }
+        
+        //
+        //  Do the actual rename by changing from the frompage to the topage, including
+        //  all of the attachments
+        //
+        
+        engine.getPageManager().getProvider().movePage( renameFrom, renameTo );
+        /*
+        if( engine.getAttachmentManager().attachmentsEnabled() )
+        {
+            engine.getAttachmentManager().getCurrentProvider().moveAttachmentsForPage( renameFrom, renameTo );
+        }
+*/
+        //
+        //  Add a comment to the page notifying what changed.  This adds a new revision
+        //  to the repo with no actual change.
+        //
+        
+        try
+        {
+            toPage = engine.getPage( renameTo );
+        }
+        catch( PageNotFoundException e )
+        {
+            throw new InternalWikiException( "Rename seems to have failed for some strange reason - please check logs!" );
+        }
+
+        toPage.setAttribute( WikiPage.CHANGENOTE, fromPage.getName() + " ==> " + toPage.getName() );
+        toPage.setAuthor( context.getCurrentUser().getName() );
+        
+        engine.getPageManager().putPageText( toPage, engine.getPureText( toPage ) );
+
+        // Tell everyone we moved the page
+        fireEvent( ContentEvent.NODE_RENAMED, toPage.getName(), fromPage.getName(), Boolean.valueOf( changeReferrers ) );
+        
+        //
+        //  re-index the page 
+        //
+        engine.getSearchManager().reindexPage(toPage);
+
+        
+        //
+        //  Done, return the new name.
+        //
+        return renameTo;
+    }
+
     // events processing .......................................................
 
     /**
@@ -1103,14 +1237,15 @@
      *  to all registered listeners.
      *
      * @see org.apache.wiki.event.WikiPageEvent
-     * @param type       the event type to be fired
-     * @param pagename   the wiki page name as a String
+     * @param type the event type to be fired
+     * @param pagename the wiki page name as a String
+     * @param args additional arguments to pass to the event
      */
-    protected final void fireEvent( int type, String pagename )
+    protected final void fireEvent( int type, String pagename, Serializable... args )
     {
         if ( WikiEventManager.isListening(this) )
         {
-            WikiEventManager.fireEvent(this,new WikiPageEvent(m_engine,type,pagename));
+            WikiEventManager.fireEvent(this,new WikiPageEvent(m_engine,type,pagename,args));
         }
     }