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) {