You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by ja...@apache.org on 2008/12/04 22:36:37 UTC

svn commit: r723450 - in /incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH: ./ src/com/ecyrd/jspwiki/ src/com/ecyrd/jspwiki/content/ tests/com/ecyrd/jspwiki/content/ tests/etc/

Author: jalkanen
Date: Thu Dec  4 13:36:37 2008
New Revision: 723450

URL: http://svn.apache.org/viewvc?rev=723450&view=rev
Log:
Initial commit of JCR ContentManager.

Added:
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/content/ContentManager.java
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/com/ecyrd/jspwiki/content/ContentManagerTest.java
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/etc/priha.properties
Modified:
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/.classpath
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiEngine.java
    incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java

Modified: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/.classpath
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/.classpath?rev=723450&r1=723449&r2=723450&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/.classpath (original)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/.classpath Thu Dec  4 13:36:37 2008
@@ -40,6 +40,8 @@
 	<classpathentry kind="lib" path="lib/stripes-1.5.jar" sourcepath="/stripes-1.5/stripes/src"/>
 	<classpathentry kind="lib" path="lib/slf4j-log4j12-1.5.6.jar"/>
 	<classpathentry kind="lib" path="lib/log4j-1.2.14.jar"/>
+	<classpathentry kind="lib" path="lib/jcr-1.0.jar"/>
+	<classpathentry kind="lib" path="lib/priha-0.1.11.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry kind="lib" path="lib/slf4j-api-1.5.6.jar"/>
 	<classpathentry kind="lib" path="lib/jul-to-slf4j-1.5.6.jar"/>

Modified: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiEngine.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiEngine.java?rev=723450&r1=723449&r2=723450&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiEngine.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiEngine.java Thu Dec  4 13:36:37 2008
@@ -47,6 +47,7 @@
 import com.ecyrd.jspwiki.auth.acl.AclManager;
 import com.ecyrd.jspwiki.auth.acl.DefaultAclManager;
 import com.ecyrd.jspwiki.auth.authorize.GroupManager;
+import com.ecyrd.jspwiki.content.ContentManager;
 import com.ecyrd.jspwiki.content.PageRenamer;
 import com.ecyrd.jspwiki.diff.DifferenceManager;
 import com.ecyrd.jspwiki.event.WikiEngineEvent;
@@ -265,6 +266,8 @@
 
     private AdminBeanManager m_adminBeanManager;
 
+    private ContentManager   m_contentManager;
+    
     /** Stores wikiengine attributes. */
     private Map<String,Object> m_attributes = Collections.synchronizedMap(new HashMap<String,Object>());
 
@@ -529,6 +532,7 @@
             m_urlConstructor = (URLConstructor) urlclass.newInstance();
             m_urlConstructor.initialize( this, props );
 
+            m_contentManager    = new ContentManager( this ); // FIXME: Should use getMappedObject
             m_pageManager       = (PageManager)ClassUtil.getMappedObject(PageManager.class.getName(), this, props );
             m_pluginManager     = (PluginManager)ClassUtil.getMappedObject(PluginManager.class.getName(), this, props );
             m_differenceManager = (DifferenceManager)ClassUtil.getMappedObject(DifferenceManager.class.getName(), this, props );
@@ -2354,6 +2358,11 @@
     {
         return m_editorManager;
     }
+    
+    public ContentManager getContentManager()
+    {
+        return m_contentManager;
+    }
 
     /**
      *  Returns the current i18n manager.

Modified: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java?rev=723450&r1=723449&r2=723450&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java Thu Dec  4 13:36:37 2008
@@ -25,6 +25,13 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.jcr.*;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jspwiki.api.WikiException;
+
 import com.ecyrd.jspwiki.auth.acl.Acl;
 import com.ecyrd.jspwiki.auth.acl.AclEntry;
 import com.ecyrd.jspwiki.auth.acl.AclImpl;
@@ -54,6 +61,8 @@
     private String           m_author = null;
     private final HashMap<String,Object> m_attributes = new HashMap<String,Object>();
 
+    private Node             m_node = null;
+    
     /**
      *  "Summary" is a short summary of the page.  It is a String.
      */
@@ -83,6 +92,17 @@
         m_wiki = engine.getApplicationName();
     }
 
+    // FIXME: This should be a part of the constructor
+    public void setJCRNode(Node nd)
+    {
+        m_node = nd;
+    }
+    
+    public Node getJCRNode()
+    {
+        return m_node;
+    }
+    
     /**
      *  Returns the name of the page.
      *  
@@ -414,4 +434,49 @@
     {
         return m_name.hashCode() * m_version;
     }
+
+    public void save() throws WikiException
+    {
+        try
+        {
+            m_node.save();
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Save failed",e);
+        }
+    }
+    
+    private static final String ATTR_CONTENT = "wiki:content";
+    
+    public String getContentAsString() throws WikiException
+    {
+        try
+        {
+            Property p = m_node.getProperty( ATTR_CONTENT );
+                
+            return p.getString();
+        }
+        catch( PathNotFoundException e )
+        {
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Unable to get property",e);
+        }
+        
+        return null;
+    }
+
+    public void setContent( String content ) throws WikiException
+    {
+        try
+        {
+            m_node.setProperty( ATTR_CONTENT, content );
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Unable to set content",e);
+        }
+    }
 }

Added: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/content/ContentManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/content/ContentManager.java?rev=723450&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/content/ContentManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/src/com/ecyrd/jspwiki/content/ContentManager.java Thu Dec  4 13:36:37 2008
@@ -0,0 +1,1051 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.      
+ */
+package com.ecyrd.jspwiki.content;
+
+import java.util.*;
+
+import javax.jcr.*;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.apache.jspwiki.api.WikiException;
+import org.priha.RepositoryManager;
+import org.priha.util.ConfigurationException;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.event.WikiEventManager;
+import com.ecyrd.jspwiki.event.WikiPageEvent;
+import com.ecyrd.jspwiki.log.Logger;
+import com.ecyrd.jspwiki.log.LoggerFactory;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.util.TextUtil;
+import com.ecyrd.jspwiki.util.WikiBackgroundThread;
+
+/**
+ *  Provides access to the content repository.
+ */
+public class ContentManager
+{
+    private static final long serialVersionUID = 2L;
+    
+    /** The property value for setting the amount of time before the page locks expire. 
+     *  Value is {@value}.
+     */
+    public static final String PROP_LOCKEXPIRY   = "jspwiki.lockExpiryTime";
+    
+    /** The message key for storing the text for the presave task.  Value is <tt>{@value}</tt>*/
+    public static final String PRESAVE_TASK_MESSAGE_KEY = "task.preSaveWikiPage";
+    
+    /** The workflow attribute which stores the wikiContext. */
+    public static final String PRESAVE_WIKI_CONTEXT = "wikiContext";
+    
+    /** The name of the key from jspwiki.properties which defines who shall approve
+     *  the workflow of storing a wikipage.  Value is <tt>{@value}</tt>*/
+    public static final String SAVE_APPROVER             = "workflow.saveWikiPage";
+    
+    /** The message key for storing the Decision text for saving a page.  Value is {@value}. */
+    public static final String SAVE_DECISION_MESSAGE_KEY = "decision.saveWikiPage";
+    
+    /** The message key for rejecting the decision to save the page.  Value is {@value}. */
+    public static final String SAVE_REJECT_MESSAGE_KEY   = "notification.saveWikiPage.reject";
+    
+    /** The message key of the text to finally approve a page save.  Value is {@value}. */
+    public static final String SAVE_TASK_MESSAGE_KEY     = "task.saveWikiPage";
+    
+    /** Fact name for storing the page name.  Value is {@value}. */
+    public static final String FACT_PAGE_NAME = "fact.pageName";
+    
+    /** Fact name for storing a diff text. Value is {@value}. */
+    public static final String FACT_DIFF_TEXT = "fact.diffText";
+    
+    /** Fact name for storing the current text.  Value is {@value}. */
+    public static final String FACT_CURRENT_TEXT = "fact.currentText";
+    
+    /** Fact name for storing the proposed (edited) text.  Value is {@value}. */
+    public static final String FACT_PROPOSED_TEXT = "fact.proposedText";
+    
+    /** Fact name for storing whether the user is authenticated or not.  Value is {@value}. */
+    public static final String FACT_IS_AUTHENTICATED = "fact.isAuthenticated";
+
+    /** MIME type for JSPWiki markup content. */
+    public static final String JSPWIKI_CONTENT_TYPE = "text/x-jspwiki";
+
+    private static final String NS_JSPWIKI = "http://www.jspwiki.org/ns#";
+    
+    private static final String DEFAULT_WORKSPACE = "jspwiki";
+    
+    static Logger log = LoggerFactory.getLogger( ContentManager.class );
+
+    protected HashMap<String,PageLock> m_pageLocks = new HashMap<String,PageLock>();
+
+    private WikiEngine m_engine;
+
+    private int m_expiryTime = 60;
+
+    private LockReaper m_reaper = null;
+
+    private Repository m_repository;
+    
+    private String m_workspaceName = DEFAULT_WORKSPACE; // FIXME: Make settable
+    
+    /** This is the name of the default wikispace. */
+    
+    private static final String DEFAULT_SPACE = "Main";
+    /**
+     *  Creates a new PageManager.
+     *  
+     *  @param engine WikiEngine instance
+     *  @param props Properties to use for initialization
+     *  @throws WikiException If anything goes wrong, you get this.
+     */
+    public ContentManager( WikiEngine engine )
+        throws WikiException
+    {
+        String classname;
+
+        m_engine = engine;
+
+        m_expiryTime = TextUtil.parseIntParameter( engine.getWikiProperties().getProperty( PROP_LOCKEXPIRY ), 60 );
+
+        InitialContext ctx;
+        try
+        {
+            //
+            //  Attempt to locate the repository object from the JNDI using
+            //  "java:comp/env/jcr/repository" name
+            //
+            ctx = new InitialContext();
+            
+            Context environment = (Context) ctx.lookup("java:comp/env");
+            m_repository = (Repository) environment.lookup("jcr/repository");
+        }
+        catch( NamingException e )
+        {
+            if( log.isDebugEnabled() )
+                log.debug( "Unable to locate the repository from JNDI",e );
+            else
+                log.info( "Unable to locate the repository from JNDI, attempting to locate from jspwiki.properties" );
+            
+            String repositoryName = engine.getWikiProperties().getProperty( "jspwiki.repository", "priha" );
+            
+            log.info( "Trying repository "+repositoryName );
+            
+            if( "priha".equals(repositoryName) )
+            {
+                try
+                {
+                    m_repository = RepositoryManager.getRepository();
+                }
+                catch( ConfigurationException e1 )
+                {
+                    throw new WikiException( "Unable to initialize Priha as the main repository",e1);
+                }
+            }
+            else
+            {
+                throw new WikiException("Unable to initialize repository for repositorytype "+repositoryName);
+            }
+        }
+        
+        try
+        {
+            initialize();
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Failed to initialize the repository content",e);
+        }
+    }
+
+    /**
+     *  Initializes the repository, making sure that everything is in place.
+     * @throws RepositoryException 
+     * @throws LoginException 
+     */
+    private void initialize() throws LoginException, RepositoryException 
+    {
+        Session session = m_repository.login(m_workspaceName);
+        
+        try
+        {
+            //
+            //  Create the proper namespaces
+            //
+            
+            session.getWorkspace().getNamespaceRegistry().registerNamespace( "wiki", NS_JSPWIKI );
+            
+            Node root = session.getRootNode();
+        
+            //
+            // Create main page directory
+            //
+            if( !root.hasNode( "pages" ) )
+            {
+                root.addNode( "pages" );
+            }
+        
+            //
+            //  Make sure at least the default "Main" wikispace exists.
+            //
+            
+            if( !root.hasNode( "pages/Main" ) )
+            {
+                root.addNode( "pages/Main" );
+            }
+            
+            session.save();
+        }
+        finally
+        {
+            session.logout();
+        }
+    }
+    
+    /**
+     *  Returns all pages in some random order.  If you need just the page names, 
+     *  please see {@link ReferenceManager#findCreated()}, which is probably a lot
+     *  faster.  This method may cause repository access.
+     *  
+     *  @return A Collection of WikiPage objects.
+     *  @throws ProviderException If the backend has problems.
+     */
+    /*
+    public Collection getAllPages()
+        throws ProviderException
+    {
+        return m_provider.getAllPages();
+    }
+*/
+    /**
+     *  
+     *  @param pageName The name of the page to fetch.
+     *  @param version The version to find
+     *  @return The page content as a raw string
+     *  @throws ProviderException If the backend has issues.
+     *  @deprecated
+     */
+    public String getPageText( String path, int version )
+        throws WikiException
+    {
+        if( path == null || path.length() == 0 )
+        {
+            throw new WikiException("Illegal page name");
+        }
+
+        WikiPage p = getPage( null, path, version );
+        
+        return p.getContentAsString();
+    }
+    
+    /**
+     *  Returns the WikiEngine to which this PageManager belongs to.
+     *  
+     *  @return The WikiEngine object.
+     */
+    public WikiEngine getEngine()
+    {
+        return m_engine;
+    }
+
+    /**
+     *  Locks page for editing.  Note, however, that the PageManager
+     *  will in no way prevent you from actually editing this page;
+     *  the lock is just for information.
+     *
+     *  @param page WikiPage to lock
+     *  @param user Username to use for locking
+     *  @return null, if page could not be locked.
+     */
+//    public PageLock lockPage( WikiPage page, String user )
+//    {
+//        PageLock lock = null;
+//
+//        if( m_reaper == null )
+//        {
+//            //
+//            //  Start the lock reaper lazily.  We don't want to start it in
+//            //  the constructor, because starting threads in constructors
+//            //  is a bad idea when it comes to inheritance.  Besides,
+//            //  laziness is a virtue.
+//            //
+//            m_reaper = new LockReaper( m_engine );
+//            m_reaper.start();
+//        }
+//
+//        synchronized( m_pageLocks )
+//        {
+//            fireEvent( WikiPageEvent.PAGE_LOCK, page.getName() ); // prior to or after actual lock?
+//
+//            lock = m_pageLocks.get( page.getName() );
+//
+//            if( lock == null )
+//            {
+//                //
+//                //  Lock is available, so make a lock.
+//                //
+//                Date d = new Date();
+//                lock = new PageLock( page, user, d,
+//                                     new Date( d.getTime() + m_expiryTime*60*1000L ) );
+//
+//                m_pageLocks.put( page.getName(), lock );
+//
+//                log.debug( "Locked page "+page.getName()+" for "+user);
+//            }
+//            else
+//            {
+//                log.debug( "Page "+page.getName()+" already locked by "+lock.getLocker() );
+//                lock = null; // Nothing to return
+//            }
+//        }
+//
+//        return lock;
+//    }
+    /**
+     *  Marks a page free to be written again.  If there has not been a lock,
+     *  will fail quietly.
+     *
+     *  @param lock A lock acquired in lockPage().  Safe to be null.
+     */
+    public void unlockPage( PageLock lock )
+    {
+        if( lock == null ) return;
+
+        synchronized( m_pageLocks )
+        {
+            m_pageLocks.remove( lock.getPage() );
+
+            log.debug( "Unlocked page "+lock.getPage() );
+        }
+
+        fireEvent( WikiPageEvent.PAGE_UNLOCK, lock.getPage() );
+    }
+
+    /**
+     *  Returns the current lock owner of a page.  If the page is not
+     *  locked, will return null.
+     *
+     *  @param page The page to check the lock for
+     *  @return Current lock, or null, if there is no lock
+     */
+    public PageLock getCurrentLock( WikiPage page )
+    {
+        PageLock lock = null;
+
+        synchronized( m_pageLocks )
+        {
+            lock = m_pageLocks.get( page.getName() );
+        }
+
+        return lock;
+    }
+
+    /**
+     *  Returns a list of currently applicable locks.  Note that by the time you get the list,
+     *  the locks may have already expired, so use this only for informational purposes.
+     *
+     *  @return List of PageLock objects, detailing the locks.  If no locks exist, returns
+     *          an empty list.
+     *  @since 2.0.22.
+     */
+    public List getActiveLocks()
+    {
+        ArrayList<PageLock> result = new ArrayList<PageLock>();
+
+        synchronized( m_pageLocks )
+        {
+            for( PageLock lock : m_pageLocks.values() )
+            {
+                result.add( lock );
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     *  Finds a WikiPage object describing a particular page and version.
+     *  
+     *  @param pageName  The name of the page
+     *  @param version   A version number
+     *  @return          A WikiPage object, or null, if the page does not exist
+     *  @throws ProviderException If there is something wrong with the page 
+     *                            name or the repository
+     */
+    /*
+    public WikiPage getPageInfo( String pageName, int version )
+        throws ProviderException
+    {
+        if( pageName == null || pageName.length() == 0 )
+        {
+            throw new ProviderException("Illegal page name '"+pageName+"'");
+        }
+
+        WikiPage page = null;
+
+        try
+        {
+            page = m_provider.getPageInfo( pageName, version );
+        }
+        catch( RepositoryModifiedException e )
+        {
+            //
+            //  This only occurs with the latest version.
+            //
+            log.info("Repository has been modified externally while fetching info for "+pageName );
+
+            page = m_provider.getPageInfo( pageName, version );
+
+            if( page != null )
+            {
+                m_engine.updateReferences( page );
+            }
+            else
+            {
+                m_engine.getReferenceManager().pageRemoved( new WikiPage(m_engine,pageName) );
+            }
+        }
+
+        //
+        //  Should update the metadata.
+        //
+        
+        if( page != null && !page.hasMetadata() )
+        {
+            WikiContext ctx = new WikiContext(m_engine,page);
+            m_engine.textToHTML( ctx, getPageText(pageName,version) );
+        }
+        
+        return page;
+    }
+*/
+    /**
+     *  Gets a version history of page.  Each element in the returned
+     *  List is a WikiPage.
+     *  
+     *  @param pageName The name of the page to fetch history for
+     *  @return If the page does not exist, returns null, otherwise a List
+     *          of WikiPages.
+     *  @throws ProviderException If the repository fails.
+     */
+    /*
+    public List getVersionHistory( String pageName )
+        throws ProviderException
+    {
+        if( pageExists( pageName ) )
+        {
+            return m_provider.getVersionHistory( pageName );
+        }
+
+        return null;
+    }
+*/
+    /**
+     *  Returns a human-readable description of the current provider.
+     *  
+     *  @return A human-readable description.
+     */
+    /*
+    public String getProviderDescription()
+    {
+        return m_provider.getProviderInfo();
+    }
+*/
+    /**
+     *  Returns the total count of all pages in the repository. This
+     *  method is equivalent of calling getAllPages().size(), but
+     *  it swallows the ProviderException and returns -1 instead of
+     *  any problems.
+     *  
+     *  @return The number of pages, or -1, if there is an error.
+     */
+    /*
+    public int getTotalPageCount()
+    {
+        try
+        {
+            return m_provider.getAllPages().size();
+        }
+        catch( ProviderException e )
+        {
+            log.error( "Unable to count pages: ",e );
+            return -1;
+        }
+    }
+*/
+    /**
+     *  Returns true, if the page exists (any version).
+     *  
+     *  @param pageName  Name of the page.
+     *  @return A boolean value describing the existence of a page
+     *  @throws ProviderException If the backend fails or the name is illegal.
+     */
+    /*
+    public boolean pageExists( String pageName )
+        throws ProviderException
+    {
+        if( pageName == null || pageName.length() == 0 )
+        {
+            throw new ProviderException("Illegal page name");
+        }
+
+        return m_provider.pageExists( pageName );
+    }
+*/
+    /**
+     *  Checks for existence of a specific page and version.
+     *  
+     *  @since 2.3.29
+     *  @param pageName Name of the page
+     *  @param version The version to check
+     *  @return <code>true</code> if the page exists, <code>false</code> otherwise
+     *  @throws ProviderException If backend fails or name is illegal
+     */
+    public boolean pageExists( WikiContext ctx, String path, int version )
+        throws WikiException
+    {
+        if( path == null || path.length() == 0 )
+        {
+            throw new WikiException("Illegal page name");
+        }
+
+        Session session;
+        try
+        {
+            session = getJCRSession( ctx );
+            
+            return session.itemExists( getJCRPath( (WikiContext) ctx, path ) );
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Unable to check for page existence",e);
+        }
+    }
+
+    /**
+     *  Deletes only a specific version of a WikiPage.
+     *  
+     *  @param page The page to delete.
+     *  @throws ProviderException if the page fails
+     */
+    public void deleteVersion( WikiPage page )
+        throws WikiException
+    {
+        fireEvent( WikiPageEvent.PAGE_DELETE_REQUEST, page.getName() );
+
+        try
+        {
+            page.getJCRNode().remove();
+            page.save();
+            
+            fireEvent( WikiPageEvent.PAGE_DELETED, page.getName() );
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Unable to delete a page",e);
+        }
+    }
+    
+    /**
+     *  Deletes an entire page, all versions, all traces.
+     *  
+     *  @param page The WikiPage to delete
+     *  @throws ProviderException If the repository operation fails
+     */
+    
+    public void deletePage( WikiPage page )
+        throws WikiException
+    {
+        fireEvent( WikiPageEvent.PAGE_DELETE_REQUEST, page.getName() );
+
+        VersionHistory vh;
+        try
+        {
+            Node nd = page.getJCRNode();
+            
+            // Remove version history
+            if( nd.isNodeType( "mix:versionable" ) )
+            {
+                vh = nd.getVersionHistory();
+            
+                for( VersionIterator iter = vh.getAllVersions(); iter.hasNext(); )
+                {
+                    Version v = iter.nextVersion();
+                
+                    v.remove();
+                    v.save();
+                }
+                vh.save();
+            }
+            
+            // Remove the node itself.
+            nd.remove();
+            
+            nd.getParent().save();
+            
+            fireEvent( WikiPageEvent.PAGE_DELETED, page.getName() );
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException("Deletion of pages failed.",e);
+        }
+    }
+
+    /**
+     *  This is a simple reaper thread that runs roughly every minute
+     *  or so (it's not really that important, as long as it runs),
+     *  and removes all locks that have expired.
+     */
+
+    private class LockReaper extends WikiBackgroundThread
+    {
+        /**
+         *  Create a LockReaper for a given engine.
+         *  
+         *  @param engine WikiEngine to own this thread.
+         */
+        public LockReaper( WikiEngine engine )
+        {
+            super( engine, 60 );
+            setName("JSPWiki Lock Reaper");
+        }
+
+        public void backgroundTask() throws Exception
+        {
+            synchronized( m_pageLocks )
+            {
+                Collection entries = m_pageLocks.values();
+
+                Date now = new Date();
+
+                for( Iterator i = entries.iterator(); i.hasNext(); )
+                {
+                    PageLock p = (PageLock) i.next();
+
+                    if( now.after( p.getExpiryTime() ) )
+                    {
+                        i.remove();
+
+                        log.debug( "Reaped lock: "+p.getPage()+
+                                   " by "+p.getLocker()+
+                                   ", acquired "+p.getAcquisitionTime()+
+                                   ", and expired "+p.getExpiryTime() );
+                    }
+                }
+            }
+        }
+    }
+
+    // workflow task inner classes....................................................
+
+    /**
+     * Inner class that handles the page pre-save actions. If the proposed page
+     * text is the same as the current version, the {@link #execute()} method
+     * returns {@link com.ecyrd.jspwiki.workflow.Outcome#STEP_ABORT}. Any
+     * WikiExceptions thrown by page filters will be re-thrown, and the workflow
+     * will abort.
+     *
+     * @author Andrew Jaquith
+     */
+//    public static class PreSaveWikiPageTask extends Task
+//    {
+//        private static final long serialVersionUID = 6304715570092804615L;
+//        private final WikiContext m_context;
+//        private final String m_proposedText;
+//
+//        /**
+//         *  Creates the task.
+//         *  
+//         *  @param context The WikiContext
+//         *  @param proposedText The text that was just saved.
+//         */
+//        public PreSaveWikiPageTask( WikiContext context, String proposedText )
+//        {
+//            super( PRESAVE_TASK_MESSAGE_KEY );
+//            m_context = context;
+//            m_proposedText = proposedText;
+//        }
+//
+//        /**
+//         *  {@inheritDoc}
+//         */
+//        @Override
+//        public Outcome execute() throws WikiException
+//        {
+//            // Retrieve attributes
+//            WikiEngine engine = m_context.getEngine();
+//            Workflow workflow = getWorkflow();
+//
+//            // Get the wiki page
+//            WikiPage page = m_context.getPage();
+//
+//            // Figure out who the author was. Prefer the author
+//            // set programmatically; otherwise get from the
+//            // current logged in user
+//            if ( page.getAuthor() == null )
+//            {
+//                Principal wup = m_context.getCurrentUser();
+//
+//                if ( wup != null )
+//                    page.setAuthor( wup.getName() );
+//            }
+//
+//            // Run the pre-save filters. If any exceptions, add error to list, abort, and redirect
+//            String saveText;
+//            try
+//            {
+//                saveText = engine.getFilterManager().doPreSaveFiltering( m_context, m_proposedText );
+//            }
+//            catch ( FilterException e )
+//            {
+//                throw e;
+//            }
+//
+//            // Stash the wiki context, old and new text as workflow attributes
+//            workflow.setAttribute( PRESAVE_WIKI_CONTEXT, m_context );
+//            workflow.setAttribute( FACT_PROPOSED_TEXT, saveText );
+//            return Outcome.STEP_COMPLETE;
+//        }
+//    }
+//
+//    /**
+//     * Inner class that handles the actual page save and post-save actions. Instances
+//     * of this class are assumed to have been added to an approval workflow via
+//     * {@link com.ecyrd.jspwiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, com.ecyrd.jspwiki.workflow.Fact[], Task, String)};
+//     * they will not function correctly otherwise.
+//     *
+//     * @author Andrew Jaquith
+//     */
+//    public static class SaveWikiPageTask extends Task
+//    {
+//        private static final long serialVersionUID = 3190559953484411420L;
+//
+//        /**
+//         *  Creates the Task.
+//         */
+//        public SaveWikiPageTask()
+//        {
+//            super( SAVE_TASK_MESSAGE_KEY );
+//        }
+//
+//        /** {@inheritDoc} */
+//        @Override
+//        public Outcome execute() throws WikiException
+//        {
+//            // Retrieve attributes
+//            WikiContext context = (WikiContext) getWorkflow().getAttribute( PRESAVE_WIKI_CONTEXT );
+//            String proposedText = (String) getWorkflow().getAttribute( FACT_PROPOSED_TEXT );
+//
+//            WikiEngine engine = context.getEngine();
+//            WikiPage page = context.getPage();
+//
+//            // Let the rest of the engine handle actual saving.
+//            engine.getPageManager().putPageText( page, proposedText );
+//
+//            // Refresh the context for post save filtering.
+//            engine.getPage( page.getName() );
+//            engine.textToHTML( context, proposedText );
+//            engine.getFilterManager().doPostSaveFiltering( context, proposedText );
+//
+//            return Outcome.STEP_COMPLETE;
+//        }
+//    }
+
+    // events processing .......................................................
+
+    /**
+     *  Fires a WikiPageEvent of the provided type and page name
+     *  to all registered listeners.
+     *
+     * @see com.ecyrd.jspwiki.event.WikiPageEvent
+     * @param type       the event type to be fired
+     * @param pagename   the wiki page name as a String
+     */
+    protected final void fireEvent( int type, String pagename )
+    {
+        if ( WikiEventManager.isListening(this) )
+        {
+            WikiEventManager.fireEvent(this,new WikiPageEvent(m_engine,type,pagename));
+        }
+    }
+
+    private Session getJCRSession( WikiContext context ) throws RepositoryException
+    {
+        Session session = (Session)context.getVariable( "jcrsession" );
+        
+        if( session == null )
+        {
+            session = m_repository.login(m_workspaceName);
+            
+            context.setVariable( "jcrsession", session );
+        }
+        
+        return session;
+    }
+    
+    protected static String getJCRPath( WikiContext ctx, String wikipath ) throws WikiException
+    {
+        String spaceName;
+        String spacePath;
+        
+        int colon = wikipath.indexOf(':');
+        
+        if( colon != -1 )
+        {
+            // This is a FQN
+            spaceName = wikipath.substring( 0, colon );
+            spacePath = wikipath.substring( colon+1 );
+        }
+        else if( ctx != null && ctx instanceof WikiContext )
+        {
+            WikiPage contextPage = ((WikiContext)ctx).getPage();
+            
+            // Not an FQN, the wiki name is missing, so we'll use the context to figure it out
+            spaceName = contextPage.getWiki();
+            
+            // If the wikipath starts with "/", we assume it is an absolute path within this
+            // wiki space.  Otherwise, it must be relative to the current path.
+            if( wikipath.startsWith( "/" ) )
+            {
+                spacePath = wikipath;
+            }
+            else
+            {
+                spacePath = contextPage.getName()+"/"+wikipath;
+            }
+        }
+        else
+        {
+            spaceName = DEFAULT_SPACE;
+            spacePath = wikipath;
+        }
+        
+        return "/pages/"+spaceName+"/"+spacePath;
+    }
+
+    protected static String getWikiPath( String jcrpath ) throws WikiException
+    {
+        if( jcrpath.startsWith("/pages/") )
+        {
+            String wikiPath = jcrpath.substring( "/pages/".length() );
+
+            int firstSlash = wikiPath.indexOf( '/' );
+            
+            return wikiPath.substring( 0, firstSlash )+":"+wikiPath.substring( firstSlash+1 );
+        }
+        
+        throw new WikiException("This is not a valid JCR path: "+jcrpath);
+    }
+    
+    /**
+     *  Adds new content to the repository.  To update, get a page, modify
+     *  it, then store it back using save().
+     *  
+     *  @param path
+     *  @param contentType
+     *  @return
+     */
+    public WikiPage addPage( WikiContext context, String path, String contentType ) throws WikiException
+    {
+        try
+        {
+            Session session = getJCRSession( context );
+        
+            Node nd = session.getRootNode().addNode( getJCRPath(null, path) );
+            
+            WikiPage page = new WikiPage(m_engine, path);
+            page.setJCRNode( nd );
+            
+            return page;
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException( "Unable to add a page", e );
+        }
+    }
+
+    /**
+     *  Get content from the repository.
+     *  
+     *  @param path
+     *  @return
+     */
+    public WikiPage getPage( WikiContext context, String path ) throws WikiException
+    {
+        try
+        {
+            Session session = getJCRSession( context );
+        
+            Node nd = session.getRootNode().getNode( getJCRPath(context, path) );
+            WikiPage page = new WikiPage(m_engine, path);
+            page.setJCRNode( nd );
+            
+            return page;
+        }
+        catch( PathNotFoundException e )
+        {
+            return null;
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException( "Unable to get a page", e );
+        }
+    }
+
+    public WikiPage getPage( WikiContext context, String path, int version ) throws WikiException
+    {
+        try
+        {
+            Session session = getJCRSession( context );
+        
+            Node nd = session.getRootNode().getNode( getJCRPath(null, path) );
+
+            VersionHistory vh = nd.getVersionHistory();
+            
+            Version v = vh.getVersion( Integer.toString( version ) );
+            
+            WikiPage page = new WikiPage(m_engine, path);
+            page.setJCRNode( v );
+            
+            return page;
+        }
+        catch( RepositoryException e )
+        {
+            throw new WikiException( "Unable to get a page", e );
+        }
+    }
+    
+    /**
+     *  Listens for {@link com.ecyrd.jspwiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED}
+     *  events. If a user profile's name changes, each page ACL is inspected. If an entry contains
+     *  a name that has changed, it is replaced with the new one. No events are emitted
+     *  as a consequence of this method, because the page contents are still the same; it is
+     *  only the representations of the names within the ACL that are changing.
+     * 
+     *  @param event The event
+     */
+//    public void actionPerformed(WikiEvent event)
+//    {
+//        if (! ( event instanceof WikiSecurityEvent ) )
+//        {
+//            return;
+//        }
+//
+//        WikiSecurityEvent se = (WikiSecurityEvent)event;
+//        if ( se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED )
+//        {
+//            UserProfile[] profiles = (UserProfile[])se.getTarget();
+//            Principal[] oldPrincipals = new Principal[]
+//                { new WikiPrincipal( profiles[0].getLoginName() ),
+//                  new WikiPrincipal( profiles[0].getFullname() ),
+//                  new WikiPrincipal( profiles[0].getWikiName() ) };
+//            Principal newPrincipal = new WikiPrincipal( profiles[1].getFullname() );
+//
+//            // Examine each page ACL
+//            try
+//            {
+//                int pagesChanged = 0;
+//                Collection pages = getAllPages();
+//                for ( Iterator it = pages.iterator(); it.hasNext(); )
+//                {
+//                    WikiPage page = (WikiPage)it.next();
+//                    boolean aclChanged = changeAcl( page, oldPrincipals, newPrincipal );
+//                    if ( aclChanged )
+//                    {
+//                        // If the Acl needed changing, change it now
+//                        try
+//                        {
+//                            m_engine.getAclManager().setPermissions( page, page.getAcl() );
+//                        }
+//                        catch ( WikiSecurityException e )
+//                        {
+//                            log.error( "Could not change page ACL for page " + page.getName() + ": " + e.getMessage() );
+//                        }
+//                        pagesChanged++;
+//                    }
+//                }
+//                log.info( "Profile name change for '" + newPrincipal.toString() +
+//                          "' caused " + pagesChanged + " page ACLs to change also." );
+//            }
+//            catch ( ProviderException e )
+//            {
+//                // Oooo! This is really bad...
+//                log.error( "Could not change user name in Page ACLs because of Provider error:" + e.getMessage() );
+//            }
+//        }
+//    }
+
+    /**
+     *  For a single wiki page, replaces all Acl entries matching a supplied array of Principals 
+     *  with a new Principal.
+     * 
+     *  @param page the wiki page whose Acl is to be modified
+     *  @param oldPrincipals an array of Principals to replace; all AclEntry objects whose
+     *   {@link AclEntry#getPrincipal()} method returns one of these Principals will be replaced
+     *  @param newPrincipal the Principal that should receive the old Principals' permissions
+     *  @return <code>true</code> if the Acl was actually changed; <code>false</code> otherwise
+     */
+//    protected boolean changeAcl( WikiPage page, Principal[] oldPrincipals, Principal newPrincipal )
+//    {
+//        Acl acl = page.getAcl();
+//        boolean pageChanged = false;
+//        if ( acl != null )
+//        {
+//            Enumeration entries = acl.entries();
+//            Collection<AclEntry> entriesToAdd    = new ArrayList<AclEntry>();
+//            Collection<AclEntry> entriesToRemove = new ArrayList<AclEntry>();
+//            while ( entries.hasMoreElements() )
+//            {
+//                AclEntry entry = (AclEntry)entries.nextElement();
+//                if ( ArrayUtils.contains( oldPrincipals, entry.getPrincipal() ) )
+//                {
+//                    // Create new entry
+//                    AclEntry newEntry = new AclEntryImpl();
+//                    newEntry.setPrincipal( newPrincipal );
+//                    Enumeration permissions = entry.permissions();
+//                    while ( permissions.hasMoreElements() )
+//                    {
+//                        Permission permission = (Permission)permissions.nextElement();
+//                        newEntry.addPermission(permission);
+//                    }
+//                    pageChanged = true;
+//                    entriesToRemove.add( entry );
+//                    entriesToAdd.add( newEntry );
+//                }
+//            }
+//            for ( Iterator ix = entriesToRemove.iterator(); ix.hasNext(); )
+//            {
+//                AclEntry entry = (AclEntry)ix.next();
+//                acl.removeEntry( entry );
+//            }
+//            for ( Iterator ix = entriesToAdd.iterator(); ix.hasNext(); )
+//            {
+//                AclEntry entry = (AclEntry)ix.next();
+//                acl.addEntry( entry );
+//            }
+//        }
+//        return pageChanged;
+//    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/com/ecyrd/jspwiki/content/ContentManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/com/ecyrd/jspwiki/content/ContentManagerTest.java?rev=723450&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/com/ecyrd/jspwiki/content/ContentManagerTest.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/com/ecyrd/jspwiki/content/ContentManagerTest.java Thu Dec  4 13:36:37 2008
@@ -0,0 +1,100 @@
+package com.ecyrd.jspwiki.content;
+
+import java.util.Properties;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.jspwiki.api.WikiException;
+
+import com.ecyrd.jspwiki.TestEngine;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiPage;
+
+public class ContentManagerTest extends TestCase
+{
+    ContentManager m_mgr;
+    TestEngine     m_engine;
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        
+        Properties props = new Properties();
+        props.load( TestEngine.findTestProperties() );
+        
+        m_engine = new TestEngine(props);
+        
+        m_mgr = m_engine.getContentManager();
+    }
+
+    @Override
+    protected void tearDown() throws Exception
+    {
+        WikiPage p = m_mgr.getPage( m_engine.createContext( null, WikiContext.VIEW ), 
+                                    "Main:TestPage" );
+        
+        if( p != null ) m_mgr.deletePage( p );
+        
+        super.tearDown();
+    }
+
+    public void testContentManagerGet() throws WikiException
+    {
+        assertNotNull(m_mgr);
+    }
+    
+    public void testBasicGet() throws Exception
+    {
+        String content = "Test Content";
+        WikiContext ctx = m_engine.createContext( null, WikiContext.VIEW );
+        WikiPage page = m_mgr.addPage( ctx, "Main:TestPage", ContentManager.JSPWIKI_CONTENT_TYPE );
+        
+        assertNotNull("WikiPage create", page);
+        
+        page.setContent( content );
+        
+        page.save();
+        
+        WikiPage page2 = m_mgr.getPage( ctx, "Main:TestPage" );
+        
+        assertNotNull( "page2", page2 );
+        
+        assertEquals("Content", page.getContentAsString(), page2.getContentAsString() );
+        assertEquals("Content 2", content, page2.getContentAsString() );
+    }
+    
+    public void testBasicGetDefaultSpace() throws Exception
+    {
+        WikiContext ctx = m_engine.createContext( null, WikiContext.VIEW );
+        String content = "Test Content";
+        WikiPage page = m_mgr.addPage( ctx, "TestPage", ContentManager.JSPWIKI_CONTENT_TYPE );
+        
+        assertNotNull("WikiPage create", page);
+        
+        page.setContent( content );
+        
+        page.save();
+        
+        WikiPage page2 = m_mgr.getPage( ctx, "TestPage" );
+        
+        assertNotNull( "page2", page2 );
+        
+        assertEquals("Content", page.getContentAsString(), page2.getContentAsString() );
+        assertEquals("Content 2", content, page2.getContentAsString() );
+    }
+    
+    public void testPaths() throws Exception
+    {
+        assertEquals( "One", "/pages/Main/MainPage", ContentManager.getJCRPath( null, "Main:MainPage" ) );
+        
+        assertEquals( "Back", "Main:MainPage", ContentManager.getWikiPath( "/pages/Main/MainPage" ) );
+    }
+    
+    public static Test suite()
+    {
+        return new TestSuite( ContentManagerTest.class );
+    }
+    
+}

Added: incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/etc/priha.properties
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/etc/priha.properties?rev=723450&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/etc/priha.properties (added)
+++ incubator/jspwiki/branches/JSPWIKI_JCR_BRANCH/tests/etc/priha.properties Thu Dec  4 13:36:37 2008
@@ -0,0 +1,5 @@
+#
+#  File to give a test configuration for Priha.
+#
+
+priha.provider.defaultProvider.workspaces = default jspwiki