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

svn commit: r723408 - in /incubator/sling/trunk/jcr/resource/src: main/java/org/apache/sling/jcr/resource/internal/ main/java/org/apache/sling/jcr/resource/internal/helper/ test/java/org/apache/sling/jcr/resource/internal/

Author: fmeschbe
Date: Thu Dec  4 11:42:36 2008
New Revision: 723408

URL: http://svn.apache.org/viewvc?rev=723408&view=rev
Log:
SLING-752 Implement map() methods correctly using configuration
and the map entries from /etc/map

Added:
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java
Modified:
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2.java
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java
    incubator/sling/trunk/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2Test.java

Modified: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2.java?rev=723408&r1=723407&r2=723408&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2.java (original)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2.java Thu Dec  4 11:42:36 2008
@@ -25,9 +25,11 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Stack;
 import java.util.StringTokenizer;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
@@ -52,6 +54,7 @@
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.jcr.resource.JcrResourceUtil;
+import org.apache.sling.jcr.resource.internal.helper.MapEntries;
 import org.apache.sling.jcr.resource.internal.helper.MapEntry;
 import org.apache.sling.jcr.resource.internal.helper.Mapping;
 import org.apache.sling.jcr.resource.internal.helper.RedirectResource;
@@ -77,10 +80,6 @@
 
     private static final String MANGLE_NAMESPACE_OUT = "/([^:]+):";
 
-    private static final String ANY_SCHEME_HOST = "[^/]+/[^/]+";
-
-    private static final String MAP_ROOT = "/etc/map";
-
     public static final String PROP_REG_EXP = "sling:match";
 
     public static final String PROP_REDIRECT_INTERNAL = "sling:internalRedirect";
@@ -98,16 +97,13 @@
 
     private final JcrResourceResolverFactoryImpl factory;
 
-    private final List<MapEntry> maps;
-    
-    private Set<String> namespaces;
+    private final MapEntries resourceMapper;
 
     public JcrResourceResolver2(JcrResourceProviderEntry rootProvider,
-            JcrResourceResolverFactoryImpl factory) {
+            JcrResourceResolverFactoryImpl factory, MapEntries resourceMapper) {
         this.rootProvider = rootProvider;
         this.factory = factory;
-
-        this.maps = getMap();
+        this.resourceMapper = resourceMapper;
     }
 
     // ---------- resolving resources
@@ -146,8 +142,59 @@
     // the content and in /etc/map
     public String map(HttpServletRequest request, String resourcePath) {
         
-        StringBuilder sb = new StringBuilder();
+        String mappedPath = resourcePath;
+        boolean mappedPathIsUrl = false;
+
+        Resource res = getResourceInternal(mappedPath);
+        if (res != null) {
+            
+            // find aliases for segments
+            LinkedList<String> names = new LinkedList<String>();
+            while (res != null) {
+                String alias = getProperty(res, PROP_ALIAS);
+                if (alias == null) {
+                    alias = ResourceUtil.getName(res);
+                }
+                if (alias != null && alias.length() > 0) {
+                    names.add(alias);
+                }
+                res = ResourceUtil.getParent(res);
+            }
+            
+            // build path from segment names
+            StringBuilder buf = new StringBuilder();
+            while (!names.isEmpty()) {
+                buf.append('/');
+                buf.append(names.removeLast());
+            }
+            mappedPath = buf.toString();
+        }
+        
+        for (MapEntry mapEntry : resourceMapper.getMapMaps()) {
+            String[] mappedPaths = mapEntry.replace(mappedPath);
+            if (mappedPaths != null && mappedPaths.length > 0) {
+                log.debug(
+                    "resolve: MapEntry {} matches, mapped path is {}",
+                    mapEntry, mappedPaths);
+
+                mappedPath = mappedPaths[0];
+                mappedPathIsUrl = !mapEntry.isInternal();
+                break;
+            }
+        }
+
+        // this should not be the case, since mappedPath is primed
+        if (mappedPath == null) {
+            mappedPath = resourcePath;
+        }
+        
+        if (mappedPathIsUrl) {
+            // TODO: probably need to mangle name spaces
+            return mappedPath;
+        }
         
+        StringBuilder sb = new StringBuilder();
+
         if (request != null) {
             sb.append(request.getScheme()).append("://");
             sb.append(request.getServerName());
@@ -161,7 +208,7 @@
         }
 
         // mangle the namespaces
-        sb.append(mangleNamespaces(resourcePath));
+        sb.append(mangleNamespaces(mappedPath));
         
         return sb.toString();
     }
@@ -290,23 +337,6 @@
         return rootProvider.getSession();
     }
     
-    private Set<String> getNamespaces() {
-        if (namespaces == null) {
-            
-            // get the current set of namespaces, we cache throughout our
-            // life time..
-            String[] namespaceList;
-            try {
-                namespaceList = getSession().getNamespacePrefixes();
-            } catch (RepositoryException re) {
-                namespaceList = new String[0];
-            }
-
-            namespaces = new HashSet<String>(Arrays.asList(namespaceList));
-        }
-        return namespaces;
-    }
-
     // expect absPath to be non-null and absolute
     public Resource resolveInternal(HttpServletRequest request, String absPath,
             boolean requireResource) {
@@ -333,7 +363,7 @@
         for (int i = 0; i < 100; i++) {
 
             String[] mappedPath = null;
-            for (MapEntry mapEntry : maps) {
+            for (MapEntry mapEntry : resourceMapper.getResolveMaps()) {
                 mappedPath = mapEntry.replace(requestPath);
                 if (mappedPath != null) {
                     log.debug(
@@ -597,7 +627,7 @@
         return null;
     }
 
-    private String getProperty(Resource res, String propName) {
+    public String getProperty(Resource res, String propName) {
         
         // check the property in the resource itself
         ValueMap props = res.adaptTo(ValueMap.class);
@@ -633,128 +663,6 @@
         return path;
     }
 
-    private List<MapEntry> getMap() {
-        List<MapEntry> entries = new ArrayList<MapEntry>();
-
-        // the standard map configuration
-        Resource res = getResourceInternal(MAP_ROOT);
-        if (res != null) {
-            gather(entries, res, "");
-        }
-
-        // backwards-compatible sling:vanityPath stuff
-        gatherVanityPaths(entries);
-        
-        // backwards-compatibility: read current configuration
-        gatherConfiguration(entries);
-        
-        return entries;
-    }
-
-    private void gather(List<MapEntry> entries, Resource parent,
-            String parentPath) {
-        // scheme list
-        Iterator<Resource> children = listChildren(parent);
-        while (children.hasNext()) {
-            Resource child = children.next();
-            String name = getProperty(child, PROP_REG_EXP);
-            if (name == null) {
-                name = ResourceUtil.getName(child);
-            }
-            String childPath = parentPath + name;
-
-            MapEntry mapEntry = MapEntry.create(childPath, child);
-            if (mapEntry != null) {
-                entries.add(mapEntry);
-            }
-
-            // add trailing slash to child path to append the child
-            childPath += "/";
-
-            // gather the children of this entry
-            gather(entries, child, childPath);
-        }
-    }
-    
-    private void gatherVanityPaths(List<MapEntry> entries) {
-        // sling:VanityPath (uppercase V) is the mixin name
-        // sling:vanityPath (lowercase) is the property name 
-        final String queryString = "SELECT sling:vanityPath, sling:redirect FROM sling:VanityPath WHERE sling:vanityPath IS NOT NULL ORDER BY sling:vanityOrder DESC";
-        final Iterator<Map<String, Object>> i = queryResources(queryString, Query.SQL);
-        while (i.hasNext()) {
-            Map<String, Object> row = i.next();
-            
-            // url is ignoring scheme and host.port and the path is
-            // what is stored in the sling:vanityPath property
-            Object pVanityPath = row.get("sling:vanityPath");
-            if (pVanityPath != null) {
-                String url = "^" + ANY_SCHEME_HOST + String.valueOf(pVanityPath);
-
-                // redirect target is the node providing the sling:vanityPath
-                // property (or its parent if the node is called jcr:content)
-                String redirect = String.valueOf(row.get("jcr:path"));
-                if (ResourceUtil.getName(redirect).equals("jcr:content")) {
-                    redirect = ResourceUtil.getParent(redirect);
-                }
-                
-                // whether the target is attained by a 302/FOUND or by an
-                // internal redirect is defined by the sling:redirect property
-                int status = -1;
-                if (row.containsKey("sling:redirect")
-                    && Boolean.valueOf(String.valueOf(row.get("sling:redirect")))) {
-                    status = HttpServletResponse.SC_FOUND;
-                }
-
-                // 1. entry with exact match
-                entries.add(new MapEntry(url + "$", redirect + ".html", status));
-
-                // 2. entry with match supporting selectors and extension
-                entries.add(new MapEntry(url + "(\\..*)", redirect + "$1", status));
-            }
-        }
-    }
-    
-    private void gatherConfiguration(List<MapEntry> entries) {
-        // virtual uris
-        Map<?, ?> virtuals = factory.getVirtualURLMap();
-        if (virtuals != null) {
-            for (Entry<?, ?> virtualEntry : virtuals.entrySet()) {
-                String extPath = (String) virtualEntry.getKey();
-                String intPath = (String) virtualEntry.getValue();
-                if (!extPath.equals(intPath)) {
-                    // this regular expression must match the whole URL !!
-                    String url = "^" + ANY_SCHEME_HOST + extPath + "$";
-                    String redirect = intPath;
-                    entries.add(new MapEntry(url, redirect, -1));
-                }
-            }
-        }
-        
-        // URL Mappings
-        Mapping[] mappings = factory.getMappings();
-        if (mappings != null) {
-            Map<String, List<String>> map = new HashMap<String, List<String>>();
-            for (Mapping mapping : mappings) {
-                if (mapping.mapsInbound()) {
-                    String url = mapping.getTo();
-                    String alias = mapping.getFrom();
-                    if (url.length() > 0) {
-                        List<String> aliasList = map.get(url);
-                        if (aliasList == null) {
-                            aliasList = new ArrayList<String>();
-                            map.put(url, aliasList);
-                        }
-                        aliasList.add(alias);
-                    }
-                }
-            }
-            for (Entry<String, List<String>> entry : map.entrySet()) {
-                entries.add(new MapEntry(ANY_SCHEME_HOST + entry.getKey(),
-                    entry.getValue().toArray(new String[0]), -1));
-            }
-        }
-    }
-
     private String mangleNamespaces(String absPath) {
         if (factory.isMangleNamespacePrefixes() && absPath.contains(MANGLE_NAMESPACE_OUT_SUFFIX)) {
             Pattern p = Pattern.compile(MANGLE_NAMESPACE_OUT);
@@ -773,7 +681,7 @@
 
     private String unmangleNamespaces(String absPath) {
         if (factory.isMangleNamespacePrefixes() && absPath.contains(MANGLE_NAMESPACE_IN_PREFIX)) {
-            Set<String> namespaces = getNamespaces();
+            Set<String> namespaces = resourceMapper.getNamespacePrefixes();
             Pattern p = Pattern.compile(MANGLE_NAMESPACE_IN);
             Matcher m = p.matcher(absPath);
             StringBuffer buf = new StringBuffer();

Modified: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java?rev=723408&r1=723407&r2=723408&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java (original)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java Thu Dec  4 11:42:36 2008
@@ -37,6 +37,7 @@
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.resource.JcrResourceResolverFactory;
 import org.apache.sling.jcr.resource.JcrResourceTypeProvider;
+import org.apache.sling.jcr.resource.internal.helper.MapEntries;
 import org.apache.sling.jcr.resource.internal.helper.Mapping;
 import org.apache.sling.jcr.resource.internal.helper.ResourceProviderEntry;
 import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderEntry;
@@ -186,6 +187,9 @@
 
     protected ComponentContext componentContext;
 
+    // helper for the new JcrResourceResolver2
+    private MapEntries mapEntries = MapEntries.EMPTY;
+    
     /** all mappings */
     private Mapping[] mappings;
 
@@ -233,7 +237,7 @@
             session, rootProviderEntry, getJcrResourceTypeProvider());
 
         if (useNewResourceResolver) {
-            return new JcrResourceResolver2(sessionRoot, this);
+            return new JcrResourceResolver2(sessionRoot, this, mapEntries);
         }
 
         return new JcrResourceResolver(sessionRoot, this);
@@ -273,11 +277,11 @@
                 : null;
     }
 
-    BidiMap getVirtualURLMap() {
+    public BidiMap getVirtualURLMap() {
         return virtualURLMap;
     }
 
-    Mapping[] getMappings() {
+    public Mapping[] getMappings() {
         return mappings;
     }
 
@@ -389,6 +393,27 @@
         }
         delayedResourceProviders.clear();
         this.processDelayedJcrResourceTypeProviders();
+        
+        // set up the map entries from configuration
+        if (useNewResourceResolver) {
+            try {
+                mapEntries = new MapEntries(this, getRepository());
+            } catch (Exception e) {
+                log.error(
+                    "activate: Cannot access repository, failed setting up Mapping Support",
+                    e);
+            }
+        }
+    }
+
+    /** Deativates this component, called by SCR to take out of service */
+    protected void deactivate(ComponentContext componentContext) {
+        if (useNewResourceResolver) {
+            mapEntries.dispose();
+            mapEntries = MapEntries.EMPTY;
+        }
+
+        this.componentContext = null;
     }
 
     private ResourcePattern[] getResourcePatterns(String[] patternList) {
@@ -450,11 +475,6 @@
 
     }
 
-    /** Deativates this component, called by SCR to take out of service */
-    protected void deactivate(ComponentContext componentContext) {
-        this.componentContext = null;
-    }
-
     protected void bindResourceProvider(ServiceReference reference) {
         if (componentContext == null) {
 

Added: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java?rev=723408&view=auto
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java (added)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java Thu Dec  4 11:42:36 2008
@@ -0,0 +1,407 @@
+/*
+ * 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 org.apache.sling.jcr.resource.internal.helper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.JcrResourceResolver2;
+import org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MapEntries implements EventListener {
+
+    public static MapEntries EMPTY = new MapEntries();
+
+    private static final String ANY_SCHEME_HOST = "[^/]+/[^/]+";
+
+    private static final String MAP_ROOT = "/etc/map";
+
+    private static final String MAP_ROOT_PREFIX = MAP_ROOT + "/";
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private JcrResourceResolverFactoryImpl factory;
+
+    private JcrResourceResolver2 resolver;
+
+    private Session session;
+
+    private List<MapEntry> resolveMaps;
+
+    private List<MapEntry> mapMaps;
+
+    private Set<String> namespaces;
+
+    private boolean initializing = false;
+
+    private MapEntries() {
+        session = null; // not needed
+        factory = null;
+        resolver = null;
+
+        resolveMaps = Collections.<MapEntry> emptyList();
+        mapMaps = Collections.<MapEntry> emptyList();
+        namespaces = Collections.<String> emptySet();
+    }
+
+    public MapEntries(JcrResourceResolverFactoryImpl factory,
+            SlingRepository repository) throws RepositoryException {
+        this.factory = factory;
+        this.session = repository.loginAdministrative(null);
+        this.resolver = (JcrResourceResolver2) factory.getResourceResolver(session);
+
+        init();
+
+        try {
+            session.getWorkspace().getObservationManager().addEventListener(
+                this, 255, "/", true, null, null, false);
+        } catch (RepositoryException re) {
+            log.error(
+                "MapEntries<init>: Failed registering as observation listener",
+                re);
+        }
+    }
+
+    private void init() {
+        synchronized (this) {
+            // no initialization if the session has already been reset
+            if (session == null) {
+                return;
+            }
+
+            // set the flag
+            initializing = true;
+        }
+
+        try {
+
+            List<MapEntry> newResolveMaps = new ArrayList<MapEntry>();
+            List<MapEntry> newMapMaps = new ArrayList<MapEntry>();
+
+            // load the /etc/map entries into the maps
+            loadResolverMap(resolver, newResolveMaps, newMapMaps);
+
+            // load the configuration into the resolver map
+            loadVanityPaths(resolver, newResolveMaps);
+            loadConfiguration(factory, newResolveMaps);
+
+            // load the configuration into the mapper map 
+            loadMapConfiguration(factory, newMapMaps);
+
+            this.resolveMaps = newResolveMaps;
+            this.mapMaps = newMapMaps;
+
+        } finally {
+
+            // reset the flag and notify listeners
+            synchronized (this) {
+                initializing = false;
+                notifyAll();
+            }
+        }
+    }
+
+    public void dispose() {
+
+        Session oldSession;
+
+        // wait at most 10 seconds for a notifcation during initialization
+        synchronized (this) {
+            if (initializing) {
+                try {
+                    wait(10L * 1000L);
+                } catch (InterruptedException ie) {
+                    // ignore
+                }
+            }
+
+            // immediately set the session field to null to indicate
+            // that we have been disposed (this also signals to the
+            // event handler to stop working
+            oldSession = session;
+            session = null;
+        }
+
+        if (oldSession != null) {
+            try {
+                oldSession.getWorkspace().getObservationManager().removeEventListener(
+                    this);
+            } catch (RepositoryException re) {
+                log.error(
+                    "dispose: Failed unregistering as observation listener", re);
+            }
+
+            try {
+                oldSession.logout();
+            } catch (Exception e) {
+                log.error("dispose: Unexpected problem logging out", e);
+            }
+        }
+
+        // clear the rest of the fields
+        resolver = null;
+        factory = null;
+    }
+
+    public List<MapEntry> getResolveMaps() {
+        return resolveMaps;
+    }
+
+    public List<MapEntry> getMapMaps() {
+        return mapMaps;
+    }
+
+    public Set<String> getNamespacePrefixes() {
+        if (namespaces == null) {
+
+            // get the current set of namespaces, we cache throughout our
+            // life time..
+            String[] namespaceList;
+            try {
+                namespaceList = getSession().getNamespacePrefixes();
+            } catch (RepositoryException re) {
+                namespaceList = new String[0];
+            }
+
+            namespaces = new HashSet<String>(Arrays.asList(namespaceList));
+        }
+        return namespaces;
+    }
+
+    public Session getSession() {
+        return session;
+    }
+
+    // ---------- EventListener interface
+
+    public void onEvent(EventIterator events) {
+        boolean handleEvent = false;
+        while (!handleEvent && session != null && events.hasNext()) {
+            Event event = events.nextEvent();
+            try {
+                String path = event.getPath();
+                handleEvent = MAP_ROOT.equals(path)
+                    || path.startsWith(MAP_ROOT_PREFIX)
+                    || path.endsWith("/sling:vanityPath")
+                    || path.endsWith("/sling:vanityOrder")
+                    || path.endsWith("/sling:redirect");
+            } catch (Throwable t) {
+                log.warn("onEvent: Cannot complete event handling", t);
+            }
+        }
+
+        if (handleEvent) {
+            if (session != null) {
+                try {
+                    init();
+                } catch (Throwable t) {
+                    log.warn("onEvent: Failed initializing after changes", t);
+                }
+            } else {
+                log.info("onEvent: Already disposed, not reinitializing");
+            }
+        } else if (log.isDebugEnabled()) {
+            log.debug("onEvent: Ignoring irrelevant events");
+        }
+    }
+
+    // ---------- internal
+
+    private void loadResolverMap(JcrResourceResolver2 resolver,
+            List<MapEntry> resolveEntries, List<MapEntry> mapEntries) {
+        // the standard map configuration
+        Resource res = resolver.getResource(MAP_ROOT);
+        if (res != null) {
+            gather(resolver, resolveEntries, mapEntries, res, "");
+        }
+    }
+
+    private void gather(JcrResourceResolver2 resolver,
+            List<MapEntry> resolveEntries, List<MapEntry> mapEntries,
+            Resource parent, String parentPath) {
+        // scheme list
+        Iterator<Resource> children = ResourceUtil.listChildren(parent);
+        while (children.hasNext()) {
+            Resource child = children.next();
+            String name = resolver.getProperty(child,
+                JcrResourceResolver2.PROP_REG_EXP);
+            if (name == null) {
+                name = ResourceUtil.getName(child);
+            }
+            String childPath = parentPath + name;
+
+            MapEntry childResolveEntry = MapEntry.createResolveEntry(childPath,
+                child);
+            if (childResolveEntry != null) {
+                resolveEntries.add(childResolveEntry);
+            }
+
+            List<MapEntry> childMapEntries = MapEntry.createMapEntry(childPath,
+                child);
+            if (childMapEntries != null) {
+                mapEntries.addAll(childMapEntries);
+            }
+
+            // add trailing slash to child path to append the child
+            childPath += "/";
+
+            // gather the children of this entry
+            gather(resolver, resolveEntries, mapEntries, child, childPath);
+        }
+    }
+
+    private void loadVanityPaths(JcrResourceResolver2 resolver,
+            List<MapEntry> entries) {
+        // sling:VanityPath (uppercase V) is the mixin name
+        // sling:vanityPath (lowercase) is the property name
+        final String queryString = "SELECT sling:vanityPath, sling:redirect FROM sling:VanityPath WHERE sling:vanityPath IS NOT NULL ORDER BY sling:vanityOrder DESC";
+        final Iterator<Map<String, Object>> i = resolver.queryResources(
+            queryString, Query.SQL);
+        while (i.hasNext()) {
+            Map<String, Object> row = i.next();
+
+            // url is ignoring scheme and host.port and the path is
+            // what is stored in the sling:vanityPath property
+            Object pVanityPath = row.get("sling:vanityPath");
+            if (pVanityPath != null) {
+                String url = "^" + ANY_SCHEME_HOST
+                    + String.valueOf(pVanityPath);
+
+                // redirect target is the node providing the sling:vanityPath
+                // property (or its parent if the node is called jcr:content)
+                String redirect = String.valueOf(row.get("jcr:path"));
+                if (ResourceUtil.getName(redirect).equals("jcr:content")) {
+                    redirect = ResourceUtil.getParent(redirect);
+                }
+
+                // whether the target is attained by a 302/FOUND or by an
+                // internal redirect is defined by the sling:redirect property
+                int status = -1;
+                if (row.containsKey("sling:redirect")
+                    && Boolean.valueOf(String.valueOf(row.get("sling:redirect")))) {
+                    status = HttpServletResponse.SC_FOUND;
+                }
+
+                // 1. entry with exact match
+                entries.add(new MapEntry(url + "$", redirect + ".html", status));
+
+                // 2. entry with match supporting selectors and extension
+                entries.add(new MapEntry(url + "(\\..*)", redirect + "$1",
+                    status));
+            }
+        }
+    }
+
+    private void loadConfiguration(JcrResourceResolverFactoryImpl factory,
+            List<MapEntry> entries) {
+        // virtual uris
+        Map<?, ?> virtuals = factory.getVirtualURLMap();
+        if (virtuals != null) {
+            for (Entry<?, ?> virtualEntry : virtuals.entrySet()) {
+                String extPath = (String) virtualEntry.getKey();
+                String intPath = (String) virtualEntry.getValue();
+                if (!extPath.equals(intPath)) {
+                    // this regular expression must match the whole URL !!
+                    String url = "^" + ANY_SCHEME_HOST + extPath + "$";
+                    String redirect = intPath;
+                    entries.add(new MapEntry(url, redirect, -1));
+                }
+            }
+        }
+
+        // URL Mappings
+        Mapping[] mappings = factory.getMappings();
+        if (mappings != null) {
+            Map<String, List<String>> map = new HashMap<String, List<String>>();
+            for (Mapping mapping : mappings) {
+                if (mapping.mapsInbound()) {
+                    String url = mapping.getTo();
+                    String alias = mapping.getFrom();
+                    if (url.length() > 0) {
+                        List<String> aliasList = map.get(url);
+                        if (aliasList == null) {
+                            aliasList = new ArrayList<String>();
+                            map.put(url, aliasList);
+                        }
+                        aliasList.add(alias);
+                    }
+                }
+            }
+            for (Entry<String, List<String>> entry : map.entrySet()) {
+                entries.add(new MapEntry(ANY_SCHEME_HOST + entry.getKey(),
+                    entry.getValue().toArray(new String[0]), -1));
+            }
+        }
+    }
+
+    private void loadMapConfiguration(JcrResourceResolverFactoryImpl factory,
+            List<MapEntry> entries) {
+        // URL Mappings
+        Mapping[] mappings = factory.getMappings();
+        if (mappings != null) {
+            for (int i = mappings.length - 1; i >= 0; i--) {
+                Mapping mapping = mappings[i];
+                if (mapping.mapsOutbound()) {
+                    String url = mapping.getTo();
+                    String alias = mapping.getFrom();
+                    if (!url.equals(alias)) {
+                        entries.add(new MapEntry(alias, url, -1));
+                    }
+                }
+            }
+        }
+
+        // virtual uris
+        Map<?, ?> virtuals = factory.getVirtualURLMap();
+        if (virtuals != null) {
+            for (Entry<?, ?> virtualEntry : virtuals.entrySet()) {
+                String extPath = (String) virtualEntry.getKey();
+                String intPath = (String) virtualEntry.getValue();
+                if (!extPath.equals(intPath)) {
+                    // this regular expression must match the whole URL !!
+                    String path = "^" + intPath + "$";
+                    String url = extPath;
+                    entries.add(new MapEntry(path, url, -1));
+                }
+            }
+        }
+    }
+}

Modified: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java?rev=723408&r1=723407&r2=723408&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java (original)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java Thu Dec  4 11:42:36 2008
@@ -18,6 +18,11 @@
  */
 package org.apache.sling.jcr.resource.internal.helper;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -29,17 +34,42 @@
  * The <code>MapEntry</code> class represents a mapping entry in the mapping
  * configuration tree at <code>/etc/map</code>.
  * <p>
+ * 
  * @see http://cwiki.apache.org/SLING/flexible-resource-resolution.html
  */
 public class MapEntry {
 
+    private static final Pattern[] PATH_TO_URL_MATCH = {
+        Pattern.compile("http/([^/]+)\\.80(/.*)?$"),
+        Pattern.compile("https/([^/]+)\\.443(/.*)?$"),
+        Pattern.compile("([^/]+)/([^/]+)\\.(\\d+)(/.*)?$") };
+
+    private static final String[] PATH_TO_URL_REPLACEMENT = { "http://$1$2",
+        "https://$1$2", "$1://$2:$3$4" };
+
     private final Pattern urlPattern;
 
     private final String[] redirect;
 
     private final int status;
 
-    public static MapEntry create(String url, Resource resource) {
+    public static URI toURI(String uriPath) {
+        for (int i = 0; i < PATH_TO_URL_MATCH.length; i++) {
+            Matcher m = PATH_TO_URL_MATCH[i].matcher(uriPath);
+            if (m.find()) {
+                String newUriPath = m.replaceAll(PATH_TO_URL_REPLACEMENT[i]);
+                try {
+                    return new URI(newUriPath);
+                } catch (URISyntaxException use) {
+                    // ignore, just don't return the uri as such
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public static MapEntry createResolveEntry(String url, Resource resource) {
         ValueMap props = resource.adaptTo(ValueMap.class);
         if (props != null) {
             String redirect = props.get(
@@ -49,7 +79,7 @@
                     JcrResourceResolver2.PROP_REDIRECT_EXTERNAL_STATUS, 302);
                 return new MapEntry(url, redirect, status);
             }
-            
+
             String[] internalRedirect = props.get(
                 JcrResourceResolver2.PROP_REDIRECT_INTERNAL, String[].class);
             if (internalRedirect != null) {
@@ -60,10 +90,48 @@
         return null;
     }
 
+    public static List<MapEntry> createMapEntry(String url, Resource resource) {
+        ValueMap props = resource.adaptTo(ValueMap.class);
+        if (props != null) {
+            String redirect = props.get(
+                JcrResourceResolver2.PROP_REDIRECT_EXTERNAL, String.class);
+            if (redirect != null) {
+                // ignoring external redirects for mapping
+                return null;
+            }
+
+            String[] internalRedirect = props.get(
+                JcrResourceResolver2.PROP_REDIRECT_INTERNAL, String[].class);
+            if (internalRedirect != null) {
+
+                int status = -1;
+                URI extPathPrefix = toURI(url);
+                if (extPathPrefix != null) {
+                    url = extPathPrefix.toString();
+                    status = 302;
+                }
+
+                List<MapEntry> prepEntries = new ArrayList<MapEntry>(
+                    internalRedirect.length);
+                for (String redir : internalRedirect) {
+                    if (!redir.contains("$")) {
+                        prepEntries.add(new MapEntry(redir, url, status));
+                    }
+                }
+
+                if (prepEntries.size() > 0) {
+                    return prepEntries;
+                }
+            }
+        }
+
+        return null;
+    }
+
     public MapEntry(String url, String redirect, int status) {
-        this(url, new String[]{ redirect }, status);
+        this(url, new String[] { redirect }, status);
     }
-    
+
     public MapEntry(String url, String[] redirect, int status) {
         this.urlPattern = Pattern.compile(url);
         this.redirect = redirect;
@@ -73,19 +141,19 @@
     public Matcher getMatcher(String value) {
         return urlPattern.matcher(value);
     }
-    
+
     // Returns the replacement or null if the value does not match
     public String[] replace(String value) {
         Matcher m = urlPattern.matcher(value);
         if (m.find()) {
             String[] redirects = getRedirect();
             String[] results = new String[redirects.length];
-            for (int i=0; i < redirects.length; i++) {
+            for (int i = 0; i < redirects.length; i++) {
                 results[i] = m.replaceFirst(redirects[i]);
             }
             return results;
         }
-     
+
         return null;
     }
 
@@ -96,8 +164,28 @@
     public boolean isInternal() {
         return getStatus() < 0;
     }
-    
+
     public int getStatus() {
         return status;
     }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append("MapEntry: match:").append(urlPattern);
+
+        buf.append(", replacement:");
+        if (getRedirect().length == 1) {
+            buf.append(getRedirect()[0]);
+        } else {
+            buf.append(Arrays.asList(getRedirect()));
+        }
+
+        if (isInternal()) {
+            buf.append(", internal");
+        } else {
+            buf.append(", status:").append(getStatus());
+        }
+        return buf.toString();
+    }
 }

Modified: incubator/sling/trunk/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2Test.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2Test.java?rev=723408&r1=723407&r2=723408&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2Test.java (original)
+++ incubator/sling/trunk/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver2Test.java Thu Dec  4 11:42:36 2008
@@ -27,7 +27,6 @@
 
 import javax.jcr.NamespaceRegistry;
 import javax.jcr.Node;
-import javax.jcr.RepositoryException;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.Cookie;
@@ -43,6 +42,7 @@
 import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
 import org.apache.sling.commons.testing.jcr.RepositoryUtil;
 import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.apache.sling.jcr.resource.internal.helper.MapEntries;
 import org.apache.sling.jcr.resource.internal.helper.Mapping;
 import org.apache.sling.jcr.resource.internal.helper.RedirectResource;
 import org.apache.sling.jcr.resource.internal.helper.starresource.StarResource;
@@ -59,6 +59,8 @@
 
     private ResourceResolver resResolver;
 
+    private MapEntries mapEntries;
+
     protected void setUp() throws Exception {
         super.setUp();
         assertTrue(RepositoryUtil.registerNodeType(getSession(),
@@ -74,17 +76,44 @@
             this.getClass().getResourceAsStream(
                 "/SLING-INF/nodetypes/mapping.cnd")));
 
+        // test data
+        rootPath = "/test" + System.currentTimeMillis();
+        rootNode = getSession().getRootNode().addNode(rootPath.substring(1),
+            "nt:unstructured");
+
+        // test mappings
+        mapRoot = getSession().getRootNode().addNode("etc", "nt:folder");
+        Node map = mapRoot.addNode("map", "sling:Mapping");
+        Node https = map.addNode("https", "sling:Mapping");
+        https.addNode("localhost.443", "sling:Mapping");
+        Node http = map.addNode("http", "sling:Mapping");
+        http.addNode("localhost.80", "sling:Mapping");
+
+        session.save();
+
         resFac = new JcrResourceResolverFactoryImpl();
 
         Field repoField = resFac.getClass().getDeclaredField("repository");
         repoField.setAccessible(true);
         repoField.set(resFac, getRepository());
 
+        // setup mappings
+        Field mappingsField = resFac.getClass().getDeclaredField("mappings");
+        mappingsField.setAccessible(true);
+        mappingsField.set(resFac, new Mapping[] { new Mapping("/-/"),
+            new Mapping(rootPath + "/-/") });
+
         // ensure using JcrResourceResolver2
-        Field unrrField = resFac.getClass().getDeclaredField("useNewResourceResolver");
+        Field unrrField = resFac.getClass().getDeclaredField(
+            "useNewResourceResolver");
         unrrField.setAccessible(true);
         unrrField.set(resFac, true);
 
+        Field mapEntriesField = resFac.getClass().getDeclaredField("mapEntries");
+        mapEntriesField.setAccessible(true);
+        mapEntries = new MapEntries(resFac, getRepository());
+        mapEntriesField.set(resFac, mapEntries);
+
         try {
             NamespaceRegistry nsr = session.getWorkspace().getNamespaceRegistry();
             nsr.registerNamespace(SlingConstants.NAMESPACE_PREFIX,
@@ -93,26 +122,15 @@
             // don't care for now
         }
 
-        // test data
-        rootPath = "/test" + System.currentTimeMillis();
-        rootNode = getSession().getRootNode().addNode(rootPath.substring(1),
-            "nt:unstructured");
-
-        // test mappings
-        mapRoot = getSession().getRootNode().addNode("etc", "nt:folder");
-        Node map = mapRoot.addNode("map", "sling:Mapping");
-        Node https = map.addNode("https", "sling:Mapping");
-        https.addNode("localhost.443", "sling:Mapping");
-        Node http = map.addNode("http", "sling:Mapping");
-        http.addNode("localhost.80", "sling:Mapping");
-
-        session.save();
-
         resResolver = resFac.getResourceResolver(session);
     }
 
     @Override
     protected void tearDown() throws Exception {
+        if (mapEntries != null) {
+            mapEntries.dispose();
+        }
+
         if (rootNode != null) {
             rootNode.remove();
         }
@@ -124,6 +142,54 @@
         session.save();
     }
 
+    public void testBasicAPIAssumptions() throws Exception {
+
+        final String no_resource_path = "/no_resource/at/this/location";
+
+        try {
+            resResolver.resolve((String) null);
+            fail("Expected NullPointerException trying to resolve null path");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+
+        assertNull("Expecting no resource for relative path",
+            resResolver.resolve("relPath/relPath"));
+
+        assertNull("Expecting null if resource cannot be found",
+            resResolver.resolve(no_resource_path));
+
+        try {
+            resResolver.resolve((HttpServletRequest) null);
+            fail("Expected NullPointerException trying to resolve null request");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+
+        final Resource res0 = resResolver.resolve(null, no_resource_path);
+        assertNotNull("Expecting resource if resolution fails", res0);
+        assertTrue("Resource must be NonExistingResource",
+            res0 instanceof NonExistingResource);
+        assertEquals("Path must be the original path", no_resource_path,
+            res0.getPath());
+
+        final HttpServletRequest req1 = new ResourceResolverTestRequest(
+            no_resource_path);
+        final Resource res1 = resResolver.resolve(req1);
+        assertNotNull("Expecting resource if resolution fails", res1);
+        assertTrue("Resource must be NonExistingResource",
+            res1 instanceof NonExistingResource);
+        assertEquals("Path must be the original path", no_resource_path,
+            res1.getPath());
+
+        final HttpServletRequest req2 = new ResourceResolverTestRequest(null);
+        final Resource res2 = resResolver.resolve(req2);
+        assertNotNull("Expecting resource if resolution fails", res2);
+        assertTrue("Resource must not be NonExistingResource",
+            !(res2 instanceof NonExistingResource));
+        assertEquals("Path must be the the root path", "/", res2.getPath());
+    }
+
     public void testGetResource() throws Exception {
         // existing resource
         Resource res = resResolver.getResource(rootPath);
@@ -143,8 +209,8 @@
 
     public void testResolveResource() throws Exception {
         // existing resource
-        Resource res = resResolver.resolve(new ResourceResolverTestRequest(
-            rootPath), rootPath);
+        HttpServletRequest request = new ResourceResolverTestRequest(rootPath);
+        Resource res = resResolver.resolve(request, rootPath);
         assertNotNull(res);
         assertEquals(rootPath, res.getPath());
         assertEquals(rootNode.getPrimaryNodeType().getName(),
@@ -204,6 +270,9 @@
         localhost443.setProperty(JcrResourceResolver2.PROP_REDIRECT_EXTERNAL,
             "http://localhost");
         session.save();
+
+        Thread.sleep(1000L);
+
         resResolver = resFac.getResourceResolver(session);
 
         Resource res = resResolver.resolve(request, rootPath);
@@ -275,6 +344,9 @@
         toContent.setProperty(JcrResourceResolver2.PROP_REDIRECT_INTERNAL,
             "/content/$1");
         session.save();
+
+        Thread.sleep(1000L);
+
         resResolver = resFac.getResourceResolver(session);
 
         Resource res = resResolver.resolve(request, "/playground.html");
@@ -290,45 +362,214 @@
         assertEquals("/libs/nt/folder.html", res.getPath());
     }
 
+    public void testResolveVirtualHostHttp80() throws Exception {
+        HttpServletRequest request = new ResourceResolverTestRequest(rootPath) {
+            @Override
+            public String getScheme() {
+                return "http";
+            }
+
+            @Override
+            public String getServerName() {
+                return "virtual.host.com";
+            }
+
+            @Override
+            public int getServerPort() {
+                return -1;
+            }
+        };
+
+        Node virtualhost80 = mapRoot.getNode("map/http").addNode(
+            "virtual.host.com.80", "sling:Mapping");
+        virtualhost80.setProperty(JcrResourceResolver2.PROP_REDIRECT_INTERNAL,
+            "/content/virtual");
+        session.save();
+
+        Thread.sleep(1000L);
+
+        final Resource res0 = resResolver.resolve(request, "/playground.html");
+        assertNotNull(res0);
+        assertEquals("/content/virtual/playground.html", res0.getPath());
+
+        final Resource res1 = resResolver.resolve(request,
+            "/playground/en.html");
+        assertNotNull(res1);
+        assertEquals("/content/virtual/playground/en.html", res1.getPath());
+
+        final String mapped0 = resResolver.map(request, res0.getPath());
+        assertEquals("http://virtual.host.com/playground.html", mapped0);
+
+        final String mapped1 = resResolver.map(request, res1.getPath());
+        assertEquals("http://virtual.host.com/playground/en.html", mapped1);
+    }
+
+    public void testResolveVirtualHostHttp8080() throws Exception {
+        HttpServletRequest request = new ResourceResolverTestRequest(rootPath) {
+            @Override
+            public String getScheme() {
+                return "http";
+            }
+
+            @Override
+            public String getServerName() {
+                return "virtual.host.com";
+            }
+
+            @Override
+            public int getServerPort() {
+                return 8080;
+            }
+        };
+
+        Node virtualhost80 = mapRoot.getNode("map/http").addNode(
+            "virtual.host.com.8080", "sling:Mapping");
+        virtualhost80.setProperty(JcrResourceResolver2.PROP_REDIRECT_INTERNAL,
+            "/content/virtual");
+        session.save();
+
+        Thread.sleep(1000L);
+
+        final Resource res0 = resResolver.resolve(request, "/playground.html");
+        assertNotNull(res0);
+        assertEquals("/content/virtual/playground.html", res0.getPath());
+
+        final Resource res1 = resResolver.resolve(request,
+            "/playground/en.html");
+        assertNotNull(res1);
+        assertEquals("/content/virtual/playground/en.html", res1.getPath());
+
+        final String mapped0 = resResolver.map(request, res0.getPath());
+        assertEquals("http://virtual.host.com:8080/playground.html", mapped0);
+
+        final String mapped1 = resResolver.map(request, res1.getPath());
+        assertEquals("http://virtual.host.com:8080/playground/en.html", mapped1);
+    }
+
+    public void testResolveVirtualHostHttps443() throws Exception {
+        HttpServletRequest request = new ResourceResolverTestRequest(rootPath) {
+            @Override
+            public String getScheme() {
+                return "https";
+            }
+
+            @Override
+            public String getServerName() {
+                return "virtual.host.com";
+            }
+
+            @Override
+            public int getServerPort() {
+                return -1;
+            }
+        };
+
+        Node virtualhost443 = mapRoot.getNode("map/https").addNode(
+            "virtual.host.com.443", "sling:Mapping");
+        virtualhost443.setProperty(JcrResourceResolver2.PROP_REDIRECT_INTERNAL,
+            "/content/virtual");
+        session.save();
+
+        Thread.sleep(1000L);
+
+        final Resource res0 = resResolver.resolve(request, "/playground.html");
+        assertNotNull(res0);
+        assertEquals("/content/virtual/playground.html", res0.getPath());
+
+        final Resource res1 = resResolver.resolve(request,
+            "/playground/en.html");
+        assertNotNull(res1);
+        assertEquals("/content/virtual/playground/en.html", res1.getPath());
+
+        final String mapped0 = resResolver.map(request, res0.getPath());
+        assertEquals("https://virtual.host.com/playground.html", mapped0);
+
+        final String mapped1 = resResolver.map(request, res1.getPath());
+        assertEquals("https://virtual.host.com/playground/en.html", mapped1);
+    }
+
+    public void testResolveVirtualHostHttps4443() throws Exception {
+        HttpServletRequest request = new ResourceResolverTestRequest(rootPath) {
+            @Override
+            public String getScheme() {
+                return "http";
+            }
+
+            @Override
+            public String getServerName() {
+                return "virtual.host.com";
+            }
+
+            @Override
+            public int getServerPort() {
+                return 4443;
+            }
+        };
+
+        Node virtualhost4443 = mapRoot.getNode("map/https").addNode(
+            "virtual.host.com.4443", "sling:Mapping");
+        virtualhost4443.setProperty(
+            JcrResourceResolver2.PROP_REDIRECT_INTERNAL, "/content/virtual");
+        session.save();
+
+        Thread.sleep(1000L);
+
+        final Resource res0 = resResolver.resolve(request, "/playground.html");
+        assertNotNull(res0);
+        assertEquals("/content/virtual/playground.html", res0.getPath());
+
+        final Resource res1 = resResolver.resolve(request,
+            "/playground/en.html");
+        assertNotNull(res1);
+        assertEquals("/content/virtual/playground/en.html", res1.getPath());
+
+        final String mapped0 = resResolver.map(request, res0.getPath());
+        assertEquals("https://virtual.host.com:4443/playground.html", mapped0);
+
+        final String mapped1 = resResolver.map(request, res1.getPath());
+        assertEquals("https://virtual.host.com:4443/playground/en.html",
+            mapped1);
+    }
+
     public void testResolveResourceAlias() throws Exception {
         // define an alias for the rootPath
         String alias = "testAlias";
         rootNode.setProperty(JcrResourceResolver2.PROP_ALIAS, alias);
         session.save();
-        
+
         String path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath)
             + "/" + alias + ".print.html");
-        
+
         HttpServletRequest request = new ResourceResolverTestRequest(path);
         Resource res = resResolver.resolve(request, path);
         assertNotNull(res);
         assertEquals(rootPath, res.getPath());
         assertEquals(rootNode.getPrimaryNodeType().getName(),
             res.getResourceType());
-        
+
         assertEquals(".print.html",
             res.getResourceMetadata().getResolutionPathInfo());
-        
+
         assertNotNull(res.adaptTo(Node.class));
         assertTrue(rootNode.isSame(res.adaptTo(Node.class)));
-        
-        path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath)
-            + "/" + alias + ".print.html/suffix.pdf");
-        
+
+        path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath) + "/"
+            + alias + ".print.html/suffix.pdf");
+
         request = new ResourceResolverTestRequest(path);
         res = resResolver.resolve(request, path);
         assertNotNull(res);
         assertEquals(rootPath, res.getPath());
         assertEquals(rootNode.getPrimaryNodeType().getName(),
             res.getResourceType());
-        
+
         assertEquals(".print.html/suffix.pdf",
             res.getResourceMetadata().getResolutionPathInfo());
-        
+
         assertNotNull(res.adaptTo(Node.class));
         assertTrue(rootNode.isSame(res.adaptTo(Node.class)));
     }
-    
+
     public void testResolveResourceAliasJcrContent() throws Exception {
         // define an alias for the rootPath in the jcr:content child node
         String alias = "testAlias";
@@ -338,22 +579,22 @@
 
         String path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath)
             + "/" + alias + ".print.html");
-        
+
         HttpServletRequest request = new ResourceResolverTestRequest(path);
         Resource res = resResolver.resolve(request, path);
         assertNotNull(res);
         assertEquals(rootPath, res.getPath());
         assertEquals(rootNode.getPrimaryNodeType().getName(),
             res.getResourceType());
-        
+
         assertEquals(".print.html",
             res.getResourceMetadata().getResolutionPathInfo());
-        
+
         assertNotNull(res.adaptTo(Node.class));
         assertTrue(rootNode.isSame(res.adaptTo(Node.class)));
 
-        path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath)
-            + "/" + alias + ".print.html/suffix.pdf");
+        path = ResourceUtil.normalize(ResourceUtil.getParent(rootPath) + "/"
+            + alias + ".print.html/suffix.pdf");
 
         request = new ResourceResolverTestRequest(path);
         res = resResolver.resolve(request, path);
@@ -361,7 +602,7 @@
         assertEquals(rootPath, res.getPath());
         assertEquals(rootNode.getPrimaryNodeType().getName(),
             res.getResourceType());
-        
+
         assertEquals(".print.html/suffix.pdf",
             res.getResourceMetadata().getResolutionPathInfo());
 
@@ -454,6 +695,41 @@
         assertEquals("nt:unstructured", child.getPrimaryNodeType().getName());
     }
 
+    public void testMap() throws Exception {
+        String path = rootNode.getPath();
+        String mapped = resResolver.map(path);
+        assertEquals(path, mapped);
+
+        Node child = rootNode.addNode("child");
+        session.save();
+
+        // absolute path, expect rootPath segment to be
+        // cut off the mapped path because we map the rootPath
+        // onto root
+        path = "/child";
+        mapped = resResolver.map(child.getPath());
+        assertEquals(path, mapped);
+    }
+
+    public void testAlias() throws Exception {
+
+        Node child = rootNode.addNode("child");
+        child.setProperty(JcrResourceResolver2.PROP_ALIAS, "kind");
+        session.save();
+
+        // expect kind due to alias and no parent due to mapping
+        // the rootPath onto root
+        String path = "/kind";
+        String mapped = resResolver.map(child.getPath());
+        assertEquals(path, mapped);
+
+        Resource res = resResolver.resolve(null, path);
+        Node resNode = res.adaptTo(Node.class);
+        assertNotNull(resNode);
+
+        assertEquals(child.getPath(), resNode.getPath());
+    }
+
     // ---------- internal
 
     private void testStarResourceHelper(final String path, final String method) {