Author: ajaquith
Date: Fri Mar 19 12:15:45 2010
New Revision: 925195

URL: http://svn.apache.org/viewvc?rev=925195&view=rev
Log:
New singleton-per-wiki class WikiPathResolver, part of the org.apache.content 
package, caches UUIDs and WikiPaths. It also constructs canonical WikiPaths 
that based on WikiPage ATTR_TITLE attributes from the JCR. This ensures 
consistent naming. These too are cached. The caching is primitive and *should* 
use EHcache, but doesn't yet. It *probably* makes sense to move logic from 
WikiEngine.getFinalPageName (and related code) here, but that wasn't a goal for 
this checkin.

Added:
    
incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPathResolver.java
Modified:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPath.java

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPath.java
URL: 
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPath.java?rev=925195&r1=925194&r2=925195&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPath.java 
(original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPath.java Fri 
Mar 19 12:15:45 2010
@@ -51,6 +51,8 @@ public final class WikiPath implements S
 
     private final String m_stringRepresentation;
 
+    private final String m_insensitiveString;
+
     /**
      * Create a WikiPath from a space and a path.
      * 
@@ -81,6 +83,7 @@ public final class WikiPath implements S
         }
         m_space = space == null ? ContentManager.DEFAULT_SPACE : space;
         m_stringRepresentation = m_space + ":" + m_path;
+        m_insensitiveString = m_stringRepresentation.toLowerCase();
     }
 
     /**
@@ -190,15 +193,14 @@ public final class WikiPath implements S
 
     /**
      * The hashcode of the WikiPath is exactly the same as the hashcode of its
-     * String representation. This is to fulfil the general contract of
+     * String representation. This is to fulfill the general contract of
      * equals().
      * 
      * @return {...@inheritdoc}
      */
-    // FIXME: Slow, since it creates a new String every time.
     public int hashCode()
     {
-        return m_stringRepresentation.toLowerCase().hashCode();
+        return m_insensitiveString.hashCode();
     }
 
     /**
@@ -210,7 +212,7 @@ public final class WikiPath implements S
     // FIXME: Slow, since it creates a new String every time.
     public int compareTo( WikiPath o )
     {
-        return toString().toLowerCase().compareTo( o.toString().toLowerCase() 
);
+        return m_insensitiveString.compareTo( o.m_insensitiveString );
     }
 
     /**
@@ -227,12 +229,11 @@ public final class WikiPath implements S
         if( o instanceof WikiPath )
         {
             WikiPath n = (WikiPath) o;
-
-            return m_space.equalsIgnoreCase( n.m_space ) && 
m_path.equalsIgnoreCase( n.m_path );
+            return m_insensitiveString.equals( n.m_insensitiveString );
         }
         else if( o instanceof String )
         {
-            return toString().equalsIgnoreCase( (String)o );
+            return m_insensitiveString.equals( ((String)o).toLowerCase() );
         }
         return false;
     }

Added: 
incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPathResolver.java
URL: 
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPathResolver.java?rev=925195&view=auto
==============================================================================
--- 
incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPathResolver.java 
(added)
+++ 
incubator/jspwiki/trunk/src/java/org/apache/wiki/content/WikiPathResolver.java 
Fri Mar 19 12:15:45 2010
@@ -0,0 +1,368 @@
+package org.apache.wiki.content;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
+import org.apache.wiki.content.jcr.JCRWikiPage;
+import org.apache.wiki.providers.ProviderException;
+
+/**
+ * <p>
+ * Provides methods for looking up, resolving and canonicalizing correct
+ * WikiNames based on the contents of the JCR. For example, a WikiPage 
originally
+ * saved to the JCR with the name "Main:TestPage" can also be referred to by
+ * WikiPaths "main:Testpage" or "Main:testPage". However, the canonical name is
+ * "Main:TestPage"; this is the one that should always be used when a canonical
+ * name is needed.
+ * </p>
+ * <p>
+ * WikiPathResolver also provides utility methods for caching mappings between
+ * WikiPaths and JCR Node UUIDs, which is used by {...@link ReferenceManager}.
+ * </p>
+ * <p>
+ * WikiPathResolver is a singleton instance per ContentManager. The current
+ * instance for a given ContentManager can be obtained by calling
+ * {...@link #getInstance(ContentManager)}.
+ * </p>
+ */
+public class WikiPathResolver
+{
+    public static final PathRoot[] PATH_ROOTS = new PathRoot[] { 
PathRoot.NOT_CREATED, PathRoot.PAGES };
+
+    private Node[] m_searchNodes = null;
+
+    /**
+     * A branch of the JCR repository where pages are stored.
+     */
+    public static class PathRoot
+    {
+        /**
+         * JCR branch for pages that have been saved, or are newly created but
+         * not yet saved.
+         */
+        public static final PathRoot PAGES = new PathRoot( 
ContentManager.JCR_PAGES_PATH );
+
+        /**
+         * JCR branch for pages that are referred to by other pages, but have
+         * not yet been created. These pages are essentially "stubs" and 
contain
+         * only a limited subset of page attributes, such as
+         * {...@link ReferenceManager#PROPERTY_REFERRED_BY}, the UUID, and a 
few
+         * others. The JCR node hierarchy mirrors the {...@link #PAGES} path 
root.
+         */
+        public static final PathRoot NOT_CREATED = new PathRoot( 
ReferenceManager.NOT_CREATED + "/" );
+
+        private final String m_path;
+
+        /**
+         * Returns the JCR path prefix; will always start and end with a slash
+         * (/);
+         * 
+         * @return
+         */
+        public String path()
+        {
+            return m_path;
+        }
+
+        /**
+         * Creates a new page PathRoot object.
+         * 
+         * @param jcrPath the path prefix; will always start and end with a
+         *            slash (/);
+         */
+        private PathRoot( String jcrPath )
+        {
+            m_path = jcrPath;
+        }
+    }
+
+    private static final Map<ContentManager, WikiPathResolver> c_resolvers = 
new HashMap<ContentManager, WikiPathResolver>();
+
+    private final ContentManager m_cm;
+
+    /**
+     * Private constructor to prevent direct instantiation.
+     * 
+     * @param contentManager the ContentManager used to look up JCR nodes.
+     */
+    private WikiPathResolver( ContentManager contentManager )
+    {
+        super();
+        m_cm = contentManager;
+    }
+
+    private final Map<String, WikiPath> m_uuids = new HashMap<String, 
WikiPath>();
+
+    private final Map<WikiPath, String> m_paths = new HashMap<WikiPath, 
String>();
+
+    private final Map<String, WikiPath> m_canonicalPaths = new HashMap<String, 
WikiPath>();
+
+    /**
+     * Returns the WikiPathResolver for the ContentManager, lazily creating one
+     * if needed.
+     * 
+     * @param contentManager the ContentManager
+     * @return the instantiated WikiPathResolver
+     */
+    public static WikiPathResolver getInstance( ContentManager contentManager )
+    {
+        WikiPathResolver cache = c_resolvers.get( contentManager );
+        if( cache == null )
+        {
+            cache = new WikiPathResolver( contentManager );
+            c_resolvers.put( contentManager, cache );
+        }
+        return cache;
+    }
+
+    /**
+     * Constructs and returns the JCR path for a given WikiPath in a
+     * {...@link PathRoot} subtree. All path components are converted to lower
+     * case.
+     * 
+     * @param path the WikiPath
+     * @param foundry the JCR subtree containing the nodes, for example
+     *            {...@link PathRoot#PAGES} or {...@link PathRoot#NOT_CREATED}
+     * @return a full JCR path. The WikiPath's space and page name will be
+     *         converted to lower case.
+     */
+    public static String getJCRPath( WikiPath path, PathRoot foundry )
+    {
+        String spaceName = path.getSpace().toLowerCase();
+        String spacePath = path.getPath().toLowerCase();
+        return foundry.path() + spaceName + "/" + spacePath;
+    }
+
+    /**
+     * Flushes the resolver's cache.
+     */
+    public synchronized void clear()
+    {
+        m_canonicalPaths.clear();
+        m_paths.clear();
+        m_uuids.clear();
+    }
+
+    /**
+     * Adds a single WikiPath/UUID to the cache.
+     * 
+     * @param path the WikiPath
+     * @param uuid the UUID
+     * @throws RepositoryException
+     */
+    protected synchronized void add( WikiPath path, String uuid ) throws 
RepositoryException
+    {
+        WikiPath canonicalPath = canonicalizeFromJCRPath( path.getSpace() + 
"/" + path.getPath() );
+        m_paths.put( canonicalPath, uuid );
+        m_uuids.put( uuid, canonicalPath );
+    }
+
+    /**
+     * Removes a single WikiPath from the cache.
+     * 
+     * @param path the WikiPath
+     */
+    protected synchronized void remove( WikiPath path )
+    {
+        if( m_paths.containsKey( path ) )
+        {
+            String uuid = m_paths.get( path );
+            m_canonicalPaths.remove( path );
+            m_paths.remove( path );
+            m_uuids.remove( uuid );
+        }
+    }
+
+    /**
+     * Extracts a WikiPath from a full JCR path, which must start with the path
+     * to a supplied {...@link PathRoot} subtree, for example
+     * {...@link PathRoot#PAGES}. Each path component is constructed by 
examining
+     * each intermediate Node's {...@link JCRWikiPage#ATTR_TITLE} property.
+     * Resolved WikiPaths are cached.
+     * 
+     * @param jcrPath the full JCR Path used to get the {...@link WikiPath}
+     * @param foundry the JCR subtree containing the nodes, for example
+     *            {...@link PathRoot#PAGES} or {...@link PathRoot#NOT_CREATED}
+     * @return the {...@link WikiPath} for the requested JCR path
+     * @throws ProviderException if the backend fails.
+     */
+    // FIXME: Should be protected - fix once WikiPage moves to content-package
+    public WikiPath getWikiPath( String jcrPath, PathRoot foundry ) throws 
ProviderException
+    {
+        if( jcrPath.startsWith( foundry.path() ) )
+        {
+            try
+            {
+                return canonicalizeFromJCRPath( jcrPath.substring( 
foundry.path().length() ) );
+            }
+            catch( RepositoryException e )
+            {
+                throw new ProviderException( "Could not canonicalize WikiPath: 
" + jcrPath, e );
+            }
+        }
+        throw new ProviderException( "This is not a valid JSPWiki JCR path: " 
+ jcrPath );
+    }
+
+    /**
+     * Builds and caches a canonicalized WikiPath with canonical,
+     * case-sensitive, names based on a JCR path fragment. Each path component
+     * is constructed by examining each intermediate Node's
+     * {...@link JCRWikiPage#ATTR_TITLE} property. Resolved WikiPaths are 
cached.
+     * 
+     * @param rawPath the raw (lowercase) JCR path, stripped of page roots, for
+     *            example "main/foobar"
+     * @return the resolved WikiPath
+     * @throws RepositoryException if any Nodes cannot be retrieved
+     */
+    protected WikiPath canonicalizeFromJCRPath( String rawPath ) throws 
RepositoryException
+    {
+        // Quick check: have we seen this wikipath before?
+        WikiPath canonicalPath = m_canonicalPaths.get( rawPath );
+        if( canonicalPath != null )
+        {
+            return canonicalPath;
+        }
+
+        // Init search nodes
+        if( m_searchNodes == null )
+        {
+            Node root = m_cm.getCurrentSession().getRootNode();
+            m_searchNodes = new Node[] { root.getNode( 
PathRoot.NOT_CREATED.path() ), root.getNode( PathRoot.PAGES.path() ) };
+        }
+
+        // Split path components
+        String[] components = rawPath.split( "/" );
+        String path = "";
+        String jcrPath = "";
+        boolean seenSpace = false;
+        for( String component : components )
+        {
+            String title = null;
+            jcrPath = jcrPath + component;
+            for( Node searchNode : m_searchNodes )
+            {
+                try
+                {
+                    if( searchNode.hasNode( jcrPath ) )
+                    {
+                        Node node = searchNode.getNode( jcrPath );
+                        title = node.getProperty( JCRWikiPage.ATTR_TITLE 
).getString();
+                        break;
+                    }
+                }
+                catch( PathNotFoundException e )
+                {
+                }
+            }
+            jcrPath = jcrPath + "/";
+            path = path + (title == null ? component : title) + (seenSpace ? 
"/" : ":");
+            seenSpace = true;
+        }
+        if( path.endsWith( "/" ) )
+            path = path.substring( 0, path.length() - 1 );
+        canonicalPath = WikiPath.valueOf( path );
+        m_canonicalPaths.put( rawPath, canonicalPath );
+        return canonicalPath;
+    }
+
+    /**
+     * Looks up and retrieves a WikiPage by UUID and returns the WikiPath,
+     * regardless of which branch the page is in, for example the "pages" or
+     * "uncreated" branches.
+     * 
+     * @param uuid the UUID of the {...@link Node}
+     * @throws RepositoryException if the back-end JCR throws any other
+     *             exception
+     */
+    protected WikiPath getByUUID( String uuid ) throws RepositoryException
+    {
+        if( uuid == null )
+        {
+            throw new IllegalArgumentException( "null UUID given to 
getByUUID()" );
+        }
+
+        // Return the path if we've stashed it already
+        WikiPath path = m_uuids.get( uuid );
+        if( path != null )
+        {
+            return path;
+        }
+
+        // Construct a new WikiPath based on Node's location
+        Node node = m_cm.getCurrentSession().getNodeByUUID( uuid );
+        String jcrPath = node.getPath();
+        for( PathRoot pathRoot : PATH_ROOTS )
+        {
+            String prefix = pathRoot.path();
+            if( jcrPath.startsWith( prefix ) )
+            {
+                try
+                {
+                    return getWikiPath( jcrPath, pathRoot );
+                }
+                catch( ProviderException e )
+                {
+                    throw new RepositoryException( "Could not construct 
WikiPath for " + jcrPath, e );
+                }
+            }
+        }
+        throw new RepositoryException( "This is not a valid JSPWiki JCR path: 
" + jcrPath );
+    }
+
+    /**
+     * Looks up and retrieves the UUID for the JCR node for a given wiki page,
+     * whether or not it has been created. If the page exists, the existing
+     * Node's UUID will be returned. If it does not exist, an
+     * {...@link ItemNotFoundException} will be thrown.
+     * 
+     * @param path the path
+     * @return the {...@link Node} UUID
+     * @throws ItemNotFoundException if the backend fails
+     * @throws ProviderException if the backend fails
+     */
+    protected String getUUID( WikiPath path ) throws RepositoryException, 
ItemNotFoundException
+    {
+        if( path == null )
+        {
+            throw new IllegalArgumentException( "null WikiPath given to 
getUUID()" );
+        }
+        JCRWikiPage page;
+
+        // Return the UUID if we've stashed it already
+        String uuid = m_paths.get( path );
+        if( uuid != null )
+        {
+            return uuid;
+        }
+
+        // Get UUID of path
+        try
+        {
+            if( m_cm.pageExists( path ) )
+            {
+                page = m_cm.getPage( path );
+                uuid = page.getJCRNode().getUUID();
+                add( path, uuid );
+                return uuid;
+            }
+        }
+
+        catch( ProviderException e )
+        {
+            throw new RepositoryException( "Error getting path " + 
path.toString() + ".", e );
+        }
+
+        catch( PageNotFoundException e )
+        {
+            throw new RepositoryException( "Bug: could not retrieve " + 
path.toString()
+                                           + " even though ContentManager said 
it existed.", e );
+        }
+
+        throw new ItemNotFoundException( "No saved node for path " + path + " 
was found." );
+    }
+}


Reply via email to