You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by vr...@apache.org on 2012/02/02 13:47:01 UTC
svn commit: r1239587 [4/9] - in
/sling/whiteboard/resourceresolverfactory/jcr-resource: ./ src/ src/main/
src/main/appended-resources/ src/main/appended-resources/META-INF/
src/main/java/ src/main/java/org/ src/main/java/org/apache/
src/main/java/org/a...
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,469 @@
+/*
+ * 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.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.jcr.resource.internal.JcrResourceResolver;
+import org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventHandler;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MapEntries implements EventHandler {
+
+ public static MapEntries EMPTY = new MapEntries();
+
+ public static final String DEFAULT_MAP_ROOT = "/etc/map";
+
+ static final String ANY_SCHEME_HOST = "[^/]+/[^/]+";
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private JcrResourceResolverFactoryImpl factory;
+
+ private ResourceResolver resolver;
+
+ private final String mapRoot;
+
+ private final String mapRootPrefix;
+
+ private List<MapEntry> resolveMaps;
+
+ private Collection<MapEntry> mapMaps;
+
+ private boolean initializing = false;
+
+ private final ServiceRegistration registration;
+
+ private final ServiceTracker eventAdminTracker;
+
+ private MapEntries() {
+ factory = null;
+ resolver = null;
+ mapRoot = DEFAULT_MAP_ROOT;
+ mapRootPrefix = mapRoot + "/";
+
+ resolveMaps = Collections.<MapEntry> emptyList();
+ mapMaps = Collections.<MapEntry> emptyList();
+ this.registration = null;
+ this.eventAdminTracker = null;
+ }
+
+ public MapEntries(final JcrResourceResolverFactoryImpl factory,
+ final BundleContext bundleContext,
+ final ServiceTracker eventAdminTracker)
+ throws LoginException {
+ this.resolver = factory.getAdministrativeResourceResolver(null);
+ this.factory = factory;
+ this.mapRoot = factory.getMapRoot();
+ this.mapRootPrefix = this.mapRoot + "/";
+ this.eventAdminTracker = eventAdminTracker;
+
+ init();
+ final Dictionary<String, String> props = new Hashtable<String, String>();
+ props.put("event.topics","org/apache/sling/api/resource/*");
+ props.put("service.description","Map Entries Observation");
+ props.put("service.vendor","The Apache Software Foundation");
+ this.registration = bundleContext.registerService(EventHandler.class.getName(), this, props);
+ }
+
+ private void init() {
+ synchronized (this) {
+ // no initialization if the session has already been reset
+ if (resolver == null) {
+ return;
+ }
+
+ // set the flag
+ initializing = true;
+ }
+
+ try {
+
+ List<MapEntry> newResolveMaps = new ArrayList<MapEntry>();
+ SortedMap<String, MapEntry> newMapMaps = new TreeMap<String, 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);
+
+ // sort List
+ Collections.sort(newResolveMaps);
+
+ this.resolveMaps = newResolveMaps;
+ this.mapMaps = new TreeSet<MapEntry>(newMapMaps.values());
+
+ sendChangeEvent();
+
+ } finally {
+
+ // reset the flag and notify listeners
+ synchronized (this) {
+ initializing = false;
+ notifyAll();
+ }
+ }
+ }
+
+ public void dispose() {
+ final ResourceResolver oldResolver;
+
+ // 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 resolver field to null to indicate
+ // that we have been disposed (this also signals to the
+ // event handler to stop working
+ oldResolver = resolver;
+ resolver = null;
+ }
+ if ( this.registration != null ) {
+ this.registration.unregister();
+ }
+
+ if (oldResolver != null) {
+ oldResolver.close();
+ }
+
+ // clear the rest of the fields
+ factory = null;
+ }
+
+ public List<MapEntry> getResolveMaps() {
+ return resolveMaps;
+ }
+
+ public Collection<MapEntry> getMapMaps() {
+ return mapMaps;
+ }
+
+ // ---------- EventListener interface
+
+ public void handleEvent(final Event event) {
+ boolean handleEvent = false;
+ final String path = (String) event.getProperty(SlingConstants.PROPERTY_PATH);
+ if ( this.resolver != null && path != null ) {
+ handleEvent = mapRoot.equals(path) || path.startsWith(mapRootPrefix);
+ if ( !handleEvent && !event.getTopic().equals(SlingConstants.TOPIC_RESOURCE_REMOVED) ) {
+ final Resource rsrc = this.resolver.getResource(path);
+ final ValueMap props = ResourceUtil.getValueMap(rsrc);
+ handleEvent = props.containsKey("sling:vanityPath")
+ || props.containsKey("sling:vanityOrder")
+ || props.containsKey("sling:redirect");
+ }
+ }
+ if (handleEvent) {
+ final Thread t = new Thread() {
+ public void run() {
+ init();
+ }
+ };
+ t.start();
+ }
+ }
+
+ // ---------- internal
+
+ /**
+ * Send an OSGi event
+ */
+ private void sendChangeEvent() {
+ final EventAdmin ea = (EventAdmin) this.eventAdminTracker.getService();
+ if ( ea != null ) {
+ // we hard code the topic here and don't use SlingConstants.TOPIC_RESOURCE_RESOLVER_MAPPING_CHANGED
+ // to avoid requiring the latest API version for this bundle to work
+ final Event event = new Event("org/apache/sling/api/resource/ResourceResolverMapping/CHANGED", (Dictionary<?,?>)null);
+ ea.postEvent(event);
+ }
+ }
+
+ private void loadResolverMap(final ResourceResolver resolver,
+ Collection<MapEntry> resolveEntries,
+ Map<String, MapEntry> mapEntries) {
+ // the standard map configuration
+ Resource res = resolver.getResource(mapRoot);
+ if (res != null) {
+ gather(resolver, resolveEntries, mapEntries, res, "");
+ }
+ }
+
+ private void gather(final ResourceResolver resolver,
+ Collection<MapEntry> resolveEntries,
+ Map<String, MapEntry> mapEntries, Resource parent, String parentPath) {
+ // scheme list
+ Iterator<Resource> children = ResourceUtil.listChildren(parent);
+ while (children.hasNext()) {
+ final Resource child = children.next();
+ final ValueMap vm = ResourceUtil.getValueMap(child);
+
+ String name = vm.get(JcrResourceResolver.PROP_REG_EXP, String.class);
+ boolean trailingSlash = false;
+ if (name == null) {
+ name = ResourceUtil.getName(child).concat("/");
+ trailingSlash = true;
+ }
+
+ String childPath = parentPath.concat(name);
+
+ // gather the children of this entry (only if child is not end hooked)
+ if (!childPath.endsWith("$")) {
+
+ // add trailing slash to child path to append the child
+ String childParent = childPath;
+ if (!trailingSlash) {
+ childParent = childParent.concat("/");
+ }
+
+ gather(resolver, resolveEntries, mapEntries, child, childParent);
+ }
+
+ // add resolution entries for this node
+ MapEntry childResolveEntry = MapEntry.createResolveEntry(childPath,
+ child, trailingSlash);
+ if (childResolveEntry != null) {
+ resolveEntries.add(childResolveEntry);
+ }
+
+ // add map entries for this node
+ List<MapEntry> childMapEntries = MapEntry.createMapEntry(childPath,
+ child, trailingSlash);
+ if (childMapEntries != null) {
+ for (MapEntry mapEntry : childMapEntries) {
+ addMapEntry(mapEntries, mapEntry.getPattern(),
+ mapEntry.getRedirect()[0], mapEntry.getStatus());
+ }
+ }
+
+ }
+ }
+
+ private void loadVanityPaths(final ResourceResolver 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, sling:redirectStatus FROM sling:VanityPath WHERE sling:vanityPath IS NOT NULL ORDER BY sling:vanityOrder DESC";
+ final Iterator<Resource> i = resolver.findResources(
+ queryString, "sql");
+ while (i.hasNext()) {
+ Resource resource = i.next();
+ ValueMap row = resource.adaptTo(ValueMap.class);
+ if (row == null) {
+ continue;
+ }
+
+ // url is ignoring scheme and host.port and the path is
+ // what is stored in the sling:vanityPath property
+ String[] pVanityPaths = row.get("sling:vanityPath", new String[0]);
+ for (String pVanityPath : pVanityPaths) {
+ final String url = getVanityPath(pVanityPath);
+ if ( url != null ) {
+ // redirect target is the node providing the sling:vanityPath
+ // property (or its parent if the node is called jcr:content)
+ String redirect = resource.getPath();
+ 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 = row.get("sling:redirect", false)
+ ? row.get("sling:redirectStatus", HttpServletResponse.SC_FOUND)
+ : -1;
+
+ // 1. entry with exact match
+ entries.add(new MapEntry(url + "$", status, false, redirect
+ + ".html"));
+
+ // 2. entry with match supporting selectors and extension
+ entries.add(new MapEntry(url + "(\\..*)", status, false,
+ redirect + "$1"));
+ }
+ }
+ }
+ }
+
+ private String getVanityPath(final String pVanityPath) {
+ String result = null;
+ if ( pVanityPath != null ) {
+ String path = pVanityPath.trim();
+ if ( path.length() > 0 ) {
+ // check for url
+ if ( path.indexOf(":/") > - 1 ) {
+ try {
+ final URL u = new URL(path);
+ path = u.getProtocol() + '/' + u.getHost() + '.' + u.getPort() + u.getPath();
+ } catch (MalformedURLException e) {
+ log.warn("Ignoring malformed vanity path {}", pVanityPath);
+ path = null;
+ }
+ } else {
+ if ( !path.startsWith("/") ) {
+ path = "/" + path;
+ }
+ path = "^" + ANY_SCHEME_HOST + path;
+ }
+
+ // remove extension
+ if ( path != null ) {
+ final int lastSlash = path.lastIndexOf('/');
+ final int firstDot = path.indexOf('.', lastSlash + 1);
+ if ( firstDot != -1 ) {
+ path = path.substring(0, firstDot);
+ log.warn("Removing extension from vanity path {}", pVanityPath);
+ }
+ result = path;
+ }
+ }
+ }
+ return result;
+ }
+
+ 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, -1, false, redirect));
+ }
+ }
+ }
+
+ // 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(),
+ -1, false, entry.getValue().toArray(new String[0])));
+ }
+ }
+ }
+
+ private void loadMapConfiguration(JcrResourceResolverFactoryImpl factory,
+ Map<String, 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)) {
+ addMapEntry(entries, 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;
+ addMapEntry(entries, path, url, -1);
+ }
+ }
+ }
+ }
+
+ private void addMapEntry(Map<String, MapEntry> entries, String path,
+ String url, int status) {
+ MapEntry entry = entries.get(path);
+ if (entry == null) {
+ entry = new MapEntry(path, status, false, url);
+ } else {
+ String[] redir = entry.getRedirect();
+ String[] newRedir = new String[redir.length + 1];
+ System.arraycopy(redir, 0, newRedir, 0, redir.length);
+ newRedir[redir.length] = url;
+ entry = new MapEntry(entry.getPattern(), entry.getStatus(),
+ false, newRedir);
+ }
+ entries.put(path, entry);
+ }
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntry.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,298 @@
+/*
+ * 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.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;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.jcr.resource.internal.JcrResourceResolver;
+
+/**
+ * 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 implements Comparable<MapEntry> {
+
+ private static final Pattern[] URL_WITH_PORT_MATCH = {
+ Pattern.compile("http/([^/]+)(\\.[^\\d/]+)(/.*)?$"),
+ Pattern.compile("https/([^/]+)(\\.[^\\d/]+)(/.*)?$") };
+
+ private static final String[] URL_WITH_PORT_REPLACEMENT = {
+ "http/$1$2.80$3", "https/$1$2.443$3" };
+
+ private static final Pattern[] PATH_TO_URL_MATCH = {
+ Pattern.compile("http/([^/]+)\\.80(/.*)?$"),
+ Pattern.compile("https/([^/]+)\\.443(/.*)?$"),
+ Pattern.compile("([^/]+)/([^/]+)\\.(\\d+)(/.*)?$"),
+ Pattern.compile("([^/]+)/([^/]+)(/.*)?$") };
+
+ private static final String[] PATH_TO_URL_REPLACEMENT = { "http://$1$2",
+ "https://$1$2", "$1://$2:$3$4", "$1://$2$3" };
+
+ private final Pattern urlPattern;
+
+ private final String[] redirect;
+
+ private final int status;
+
+ public static String appendSlash(String path) {
+ if (!path.endsWith("/")) {
+ path = path.concat("/");
+ }
+ return path;
+ }
+
+ /**
+ * Returns a string used for matching map entries against the given request
+ * or URI parts.
+ *
+ * @param scheme The URI scheme
+ * @param host The host name
+ * @param port The port number. If this is negative, the default value used
+ * is 80 unless the scheme is "https" in which case the default
+ * value is 443.
+ * @param path The (absolute) path
+ * @return The request path string {scheme}://{host}:{port}{path}.
+ */
+ public static String getURI(String scheme, String host, int port,
+ String path) {
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(scheme).append("://").append(host);
+ if (port > 0 && !(port == 80 && "http".equals(scheme))
+ && !(port == 443 && "https".equals(scheme))) {
+ sb.append(':').append(port);
+ }
+ sb.append(path);
+
+ return sb.toString();
+ }
+
+ public static String fixUriPath(String uriPath) {
+ for (int i = 0; i < URL_WITH_PORT_MATCH.length; i++) {
+ Matcher m = URL_WITH_PORT_MATCH[i].matcher(uriPath);
+ if (m.find()) {
+ return m.replaceAll(URL_WITH_PORT_REPLACEMENT[i]);
+ }
+ }
+
+ return uriPath;
+ }
+
+ 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,
+ boolean trailingSlash) {
+ ValueMap props = resource.adaptTo(ValueMap.class);
+ if (props != null) {
+
+ // ensure the url contains a port number (if possible)
+ url = fixUriPath(url);
+
+ String redirect = props.get(
+ JcrResourceResolver.PROP_REDIRECT_EXTERNAL, String.class);
+ if (redirect != null) {
+ int status = props.get(
+ JcrResourceResolver.PROP_REDIRECT_EXTERNAL_STATUS, 302);
+ return new MapEntry(url, status, trailingSlash, redirect);
+ }
+
+ String[] internalRedirect = props.get(
+ JcrResourceResolver.PROP_REDIRECT_INTERNAL, String[].class);
+ if (internalRedirect != null) {
+ return new MapEntry(url, -1, trailingSlash, internalRedirect);
+ }
+ }
+
+ return null;
+ }
+
+ public static List<MapEntry> createMapEntry(String url, Resource resource,
+ boolean trailingSlash) {
+ ValueMap props = resource.adaptTo(ValueMap.class);
+ if (props != null) {
+ String redirect = props.get(
+ JcrResourceResolver.PROP_REDIRECT_EXTERNAL, String.class);
+ if (redirect != null) {
+ // ignoring external redirects for mapping
+ return null;
+ }
+
+ // check whether the url is a match hooked to then string end
+ String endHook = "";
+ if (url.endsWith("$")) {
+ endHook = "$";
+ url = url.substring(0, url.length()-1);
+ }
+
+ // check whether the url is for ANY_SCHEME_HOST
+ if (url.startsWith(MapEntries.ANY_SCHEME_HOST)) {
+ url = url.substring(MapEntries.ANY_SCHEME_HOST.length());
+ }
+
+ String[] internalRedirect = props.get(
+ JcrResourceResolver.PROP_REDIRECT_INTERNAL, String[].class);
+ if (internalRedirect != null) {
+
+ int status = -1;
+ URI extPathPrefix = toURI(url);
+ if (extPathPrefix != null) {
+ url = getURI(extPathPrefix.getScheme(),
+ extPathPrefix.getHost(), extPathPrefix.getPort(),
+ extPathPrefix.getPath());
+ status = 302;
+ }
+
+ List<MapEntry> prepEntries = new ArrayList<MapEntry>(
+ internalRedirect.length);
+ for (String redir : internalRedirect) {
+ if (!redir.contains("$")) {
+ prepEntries.add(new MapEntry(redir.concat(endHook),
+ status, trailingSlash, url));
+ }
+ }
+
+ if (prepEntries.size() > 0) {
+ return prepEntries;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public MapEntry(String url, int status, boolean trailingSlash,
+ String... redirect) {
+
+ // ensure trailing slashes on redirects if the url
+ // ends with a trailing slash
+ if (trailingSlash) {
+ url = appendSlash(url);
+ for (int i = 0; i < redirect.length; i++) {
+ redirect[i] = appendSlash(redirect[i]);
+ }
+ }
+
+ // ensure pattern is hooked to the start of the string
+ if (!url.startsWith("^")) {
+ url = "^".concat(url);
+ }
+
+ this.urlPattern = Pattern.compile(url);
+ this.redirect = redirect;
+ this.status = status;
+ }
+
+ // 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++) {
+ results[i] = m.replaceFirst(redirects[i]);
+ }
+ return results;
+ }
+
+ return null;
+ }
+
+ public String getPattern() {
+ return urlPattern.toString();
+ }
+
+ public String[] getRedirect() {
+ return redirect;
+ }
+
+ public boolean isInternal() {
+ return getStatus() < 0;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ // ---------- Comparable
+
+ public int compareTo(MapEntry m) {
+ if (this == m) {
+ return 0;
+ }
+
+ int tlen = urlPattern.toString().length();
+ int mlen = m.urlPattern.toString().length();
+ if (tlen < mlen) {
+ return 1;
+ } else if (tlen > mlen) {
+ return -1;
+ }
+
+ // lentghs are equal, but the entries are not
+ // so order m after this
+ return 1;
+ }
+
+ // ---------- Object overwrite
+
+ @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();
+ }
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,207 @@
+/*
+ * 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.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The <code>Mapping</code> class conveys the mapping configuration used by
+ * the
+ * {@link org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl}.
+ */
+public class Mapping {
+
+ /**
+ * defines the 'inbound' direction, that is mapping request path to item
+ * path
+ */
+ public static final int INBOUND = 1;
+
+ /** defined the 'outbound' direction, that is mapping item path to URL path */
+ public static final int OUTBOUND = 2;
+
+ /** defines the 'both' direction */
+ public static final int BOTH = 3;
+
+ /** Simple mapper instance mapping path to URLs 1:1 in both directions */
+ public static final Mapping DIRECT = new Mapping("", "", BOTH) {
+
+ @Override
+ public String mapHandle(String handle) {
+ return handle;
+ }
+
+ @Override
+ public boolean mapsInbound() {
+ return true;
+ }
+
+ @Override
+ public boolean mapsOutbound() {
+ return true;
+ }
+
+ @Override
+ public String mapUri(String uriPath) {
+ return uriPath;
+ }
+ };
+
+ // Regular expression to split mapping configuration strings into three
+ // groups:
+ // 1 - external path prefix
+ // 2 - direction (Outbound (>), Bidirectional (:), Inbound (>))
+ // 3 - internap path prefix
+ private static final Pattern CONFIG_SPLITTER = Pattern.compile("(.+)([:<>])(.+)");
+
+ /** the 'from' (inside, repository) mapping */
+ private final String from;
+
+ /** the 'to' (outside, URL) mapping */
+ private final String to;
+
+ /** the length of the 'from' field */
+ private final int fromLength;
+
+ /** the length of the 'to' field */
+ private final int toLength;
+
+ /** the mapping direction */
+ private final int direction;
+
+ public Mapping(String config) {
+ this(split(config));
+ }
+
+ public Mapping(String[] parts) {
+ this.from = parts[0];
+ this.to = parts[2];
+ this.fromLength = this.from.length();
+ this.toLength = this.to.length();
+
+ this.direction = ">".equals(parts[1])
+ ? Mapping.INBOUND
+ : ("<".equals(parts[1]) ? Mapping.OUTBOUND : Mapping.BOTH);
+ }
+
+ @Override
+ public String toString() {
+ return "Mapping (from=" + from + ", to=" + to + ", direction=" + direction
+ + ", lengths=" + fromLength + "/" + toLength;
+ }
+
+ /**
+ * Replaces the prefix <em>to</em> by the new prefix <em>from</em>, if
+ * and only if <code>uriPath</code> starts with the <em>to</em> prefix.
+ * If <code>uriPath</code> does not start with the <em>to</em> prefix,
+ * or if this mapping is not defined as a 'inward' mapping,
+ * <code>null</code> is returned.
+ *
+ * @param uriPath The URI path for which to replace the <em>to</em> prefix
+ * by the <em>from</em> prefix.
+ * @return The string after replacement or <code>null</code> if the
+ * <code>uriPath</code> does not start with the <em>to</em>
+ * prefix, or {@link #mapsInbound()} returns <code>false</code>.
+ */
+ public String mapUri(String uriPath) {
+ return (this.mapsInbound() && uriPath.startsWith(this.to)) ? this.from
+ + uriPath.substring(this.toLength) : null;
+ }
+
+ /**
+ * Replaces the prefix <em>from</em> by the new prefix <em>to</em>, if
+ * and only if <code>handle</code> starts with the <em>from</em> prefix.
+ * If <code>uriPath</code> does not start with the <em>from</em> prefix,
+ * or if this mapping is not defined as a 'outward' mapping,
+ * <code>null</code> is returned.
+ *
+ * @param handle The URI path for which to replace the <em>from</em>
+ * prefix by the <em>to</em> prefix.
+ * @return The string after replacement or <code>null</code> if the
+ * <code>handle</code> does not start with the <em>from</em>
+ * prefix, or {@link #mapsOutbound()} returns <code>false</code>.
+ */
+ public String mapHandle(String handle) {
+ return (this.mapsOutbound() && handle.startsWith(this.from)) ? this.to
+ + handle.substring(this.fromLength) : null;
+ }
+
+ // TODO: temporary
+ public String getFrom() {
+ return from;
+ }
+
+ // TODO: temporary
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * Checks, if this mapping is defined for inbound mapping.
+ *
+ * @return <code>true</code> if this mapping is defined for inbound
+ * mapping; <code>false</code> otherwise
+ */
+ public boolean mapsInbound() {
+ return (this.direction & Mapping.INBOUND) > 0;
+ }
+
+ /**
+ * Checks, if this mapping is defined for outbound mapping.
+ *
+ * @return <code>true</code> if this mapping is defined for outbound
+ * mapping; <code>false</code> otherwise
+ */
+ public boolean mapsOutbound() {
+ return (this.direction & Mapping.OUTBOUND) > 0;
+ }
+
+ /**
+ * Constructs a new mapping with the given mapping string and the direction
+ */
+ private Mapping(String from, String to, int dir) {
+ this.from = from;
+ this.to = to;
+ this.fromLength = from.length();
+ this.toLength = to.length();
+ this.direction = dir;
+ }
+
+ public static String[] split(String map) {
+
+ // standard case of mapping <path>[<:>]<path>
+ Matcher mapMatch = CONFIG_SPLITTER.matcher(map);
+ if (mapMatch.matches()) {
+ return new String[] { mapMatch.group(1), mapMatch.group(2),
+ mapMatch.group(3) };
+ }
+
+ // backwards compatibility using "-" instead of ":"
+ int dash = map.indexOf('-');
+ if (dash > 0) {
+ return new String[] { map.substring(0, dash),
+ map.substring(dash, dash + 1),
+ map.substring(dash + 1, map.length()) };
+ }
+
+ return new String[] { map, "-", map };
+ }
+
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RedirectResource.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RedirectResource.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RedirectResource.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RedirectResource.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,72 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.adapter.annotations.Adaptable;
+import org.apache.sling.adapter.annotations.Adapter;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+@Adaptable(adaptableClass = Resource.class, adapters = @Adapter(value = { Map.class, ValueMap.class }))
+public final class RedirectResource extends SyntheticResource {
+
+ static final String RT_SLING_REDIRECT = "sling:redirect";
+
+ static final String PROP_SLING_TARGET = "sling:target";
+
+ static final String PROP_SLING_STATUS = "sling:status";
+
+ private final Map<String, Object> values;
+
+ public RedirectResource(final ResourceResolver resolver, final String path,
+ final String target, final int status) {
+ super(resolver, path, RT_SLING_REDIRECT);
+
+ HashMap<String, Object> props = new HashMap<String, Object>();
+ props.put(PROP_SLING_TARGET, target);
+ props.put(PROP_SLING_STATUS, status);
+ this.values = Collections.unmodifiableMap(props);
+ }
+
+ /**
+ * @see org.apache.sling.api.adapter.Adaptable#adaptTo(java.lang.Class)
+ */
+ @SuppressWarnings("unchecked")
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ if (type == ValueMap.class) {
+ return (AdapterType) new ValueMapDecorator(values);
+ } else if (type == Map.class) {
+ return (AdapterType) values;
+ }
+
+ return super.adaptTo(type);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", values=" + values;
+ }
+}
\ No newline at end of file
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceIterator.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceIterator.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceIterator.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceIterator.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,294 @@
+/*
+ * 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.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>ResourceIterator</code> implements the
+ * <code>Iterator<Resource></code> returned from the
+ * <code>ResourceResolver.listChidlren(Resource)</code> method.
+ * <p>
+ * Note: This iterator is created by the
+ * <code>JcrResourceResolver.listChildren(Resource)</code> and is not intended
+ * for general use by any other code. This class uses internal API of the
+ * {@link ResourceProviderEntry} class.
+ */
+public class ResourceIterator implements Iterator<Resource> {
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * The resource whose children are listed
+ */
+ private final Resource parentResource;
+
+ /**
+ * The root {@link ResourceProviderEntry} used to walk down the resource
+ * tree to collect entries which might provide children for the
+ * {@link #parentResource}.
+ */
+ private final ResourceProviderEntry rootProviderEntry;
+
+ /**
+ * <code>ResourceProvider</code> objects registered as nodes above the
+ * {@link #parentResource} up to the root of the resource tree
+ */
+ private final Iterator<ResourceProvider> providers;
+
+ /**
+ * The child {@link ResourceProviderEntry} registered at the node of the
+ * {@link #parentResource} in the resource tree. This may be
+ * <code>null</code> if there is no provider entry registered at that
+ * location and will be set to <code>null</code> once all entries have been
+ * processed.
+ */
+ private Iterator<ResourceProviderEntry> baseEntryValues;
+
+ /**
+ * An iterator of child resources provided by the current provider entry of
+ * the {@link #providers} iterator.
+ */
+ private Iterator<Resource> resources;
+
+ /**
+ * The next resource to be returned from the {@link #next()} method. If this
+ * is <code>null</code> the {@link #hasNext()} returns <code>false</code>.
+ */
+ private Resource nextResource;
+
+ /**
+ * Map of synthetic resources returned from resource providers while
+ * scanning for children of the {@link #parentResource}. These delayed
+ * entries are returned after all non-synthetic resources have been
+ * returned. Any delayed entry whose path matches the path of a
+ * non-synthetic resource will not returned.
+ */
+ private Map<String, Resource> delayed;
+
+ /**
+ * Set of paths of resources already returned. This is used to prevent
+ * duplicate return of resources.
+ */
+ private Set<String> visited;
+
+ /**
+ * The absolute path prefix of the {@link #parentResource} resource with a
+ * trailing slash to build the absolute path of child resources.
+ */
+ private String iteratorPath;
+
+ /**
+ * Iterator on the map of {@link #delayed} synthetic resources
+ */
+ private Iterator<Resource> delayedIter;
+
+ public ResourceIterator(final Resource parentResource,
+ final ResourceProviderEntry rootProviderEntry) {
+ this.parentResource = parentResource;
+ this.rootProviderEntry = rootProviderEntry;
+
+ log.debug("Child Iterator for {}", parentResource.getPath());
+
+ String path = parentResource.getPath();
+ if (!path.endsWith("/")) {
+ path += "/";
+ }
+
+ // gather the providers in linked set, such that we keep
+ // the order of addition and make sure we only get one entry
+ // for each resource provider
+ Set<ResourceProvider> providersSet = new LinkedHashSet<ResourceProvider>();
+ ResourceProviderEntry atPath = getResourceProviders(path, providersSet);
+
+ if (log.isDebugEnabled()) {
+ log.debug(" Provider Set for path {} {} ", path,
+ Arrays.toString(providersSet.toArray(new ResourceProvider[0])));
+ }
+ this.iteratorPath = path;
+ providers = providersSet.iterator();
+ baseEntryValues = (atPath != null) ? atPath.values().iterator() : null;
+ delayed = new HashMap<String, Resource>();
+ visited = new HashSet<String>();
+ nextResource = seek();
+ }
+
+ public boolean hasNext() {
+ return nextResource != null;
+ }
+
+ public Resource next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ Resource result = nextResource;
+ nextResource = seek();
+ log.debug(" Child resource [{}] [{}] ", iteratorPath, result.getPath());
+ return result;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+
+ private Resource seek() {
+ while (delayedIter == null) {
+ while ((resources == null || !resources.hasNext())
+ && providers.hasNext()) {
+ ResourceProvider provider = providers.next();
+ resources = provider.listChildren(parentResource);
+ log.debug(" Checking Provider {} ", provider);
+ }
+
+ if (resources != null && resources.hasNext()) {
+ Resource res = resources.next();
+ String resPath = res.getPath();
+
+ if (visited.contains(resPath)) {
+
+ // ignore a path, we have already visited and
+ // ensure it will not be listed as a delayed
+ // resource at the end
+ delayed.remove(resPath);
+
+ } else if (res instanceof SyntheticResource) {
+
+ // don't return synthetic resources right away,
+ // since a concrete resource for the same path
+ // may be provided later on
+ delayed.put(resPath, res);
+
+ } else {
+
+ // we use this concrete, unvisited resource but
+ // mark it as visited and remove from delayed
+ visited.add(resPath);
+ delayed.remove(resPath);
+ log.debug(" resource {} {}", resPath, res.getClass());
+ return res;
+
+ }
+
+ } else if (baseEntryValues != null) {
+
+ while (baseEntryValues.hasNext()) {
+ final ResourceProviderEntry rpw = baseEntryValues.next();
+ final String resPath = iteratorPath + rpw.getPath();
+ if (!visited.contains(resPath)) {
+ final ResourceResolver rr = parentResource.getResourceResolver();
+ final Resource res = rpw.getResourceFromProviders(rr,
+ resPath);
+ if (res == null) {
+ if (!delayed.containsKey(resPath)) {
+ delayed.put(resPath, new SyntheticResource(rr,
+ resPath,
+ ResourceProvider.RESOURCE_TYPE_SYNTHETIC));
+ }
+ } else {
+ // return the real resource immediately, add
+ // to the visited keys and ensure delayed
+ // does not contain it
+ delayed.remove(resPath);
+ visited.add(resPath);
+ log.debug(" B resource {} {}", resPath,
+ res.getClass());
+ return res;
+ }
+ }
+ }
+
+ baseEntryValues = null;
+
+ } else {
+
+ // all resource providers and baseEntryValues have
+ // exhausted, so we should continue returning the
+ // delayed (synthetic resources)
+ delayedIter = delayed.values().iterator();
+ }
+ }
+
+ // we exhausted all resource providers with their concrete
+ // resources. now lets do the delayed (synthetic) resources
+ Resource res = delayedIter.hasNext() ? delayedIter.next() : null;
+ if (res != null) {
+ log.debug(" D resource {} {}", res.getPath(), res.getClass());
+ }
+ return res;
+ }
+
+ /**
+ * Returns all resource providers which provider resources whose prefix is
+ * the given path.
+ *
+ * @param path The prefix path to match the resource provider roots against
+ * @param providers The set of already found resource providers to which any
+ * additional resource providers are added.
+ * @return The ResourceProviderEntry at the node identified with the path or
+ * <code>null</code> if there is no entry at the given location
+ */
+ private ResourceProviderEntry getResourceProviders(String path,
+ Set<ResourceProvider> providers) {
+
+ // collect providers along the ancestor path segements
+ String[] elements = ResourceProviderEntry.split(path, '/');
+ ResourceProviderEntry base = rootProviderEntry;
+ for (String element : elements) {
+ if (base.containsKey(element)) {
+ base = base.get(element);
+ if (log.isDebugEnabled()) {
+ log.debug("Loading from {} {} ", element,
+ base.getResourceProviders().length);
+ }
+ for (ResourceProvider rp : base.getResourceProviders()) {
+ log.debug("Adding {} for {} ", rp, path);
+ providers.add(rp);
+ }
+ } else {
+ log.debug("No container for {} ", element);
+ base = null;
+ break;
+ }
+ }
+
+ // add in providers at this node in the tree, ie the root provider
+ for (ResourceProvider rp : rootProviderEntry.getResourceProviders()) {
+ log.debug("Loading All at {} ", path);
+ providers.add(rp);
+ }
+ return base;
+ }
+
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourcePathIterator.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourcePathIterator.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourcePathIterator.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourcePathIterator.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,99 @@
+/*
+ * 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.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterate over the the HTTP request path by creating shorter segments of that
+ * path using "." as a separator.
+ * <p>
+ * For example, if path = /some/stuff.a4.html/xyz.ext the sequence is:
+ * <ol>
+ * <li> /some/stuff.a4.html/xyz.ext </li>
+ * <li> /some/stuff.a4.html/xyz </li>
+ * <li> /some/stuff.a4</li>
+ * <li> /some/stuff </li>
+ * </ol>
+ * <p>
+ * The root path (/) is never returned.
+ */
+public class ResourcePathIterator implements Iterator<String> {
+
+ // the next path to return, null if nothing more to return
+ private String nextPath;
+
+ /**
+ * Creates a new instance iterating over the given path
+ *
+ * @param path The path to iterate over. If this is empty or
+ * <code>null</code> this iterator will not return anything.
+ */
+ public ResourcePathIterator(String path) {
+
+ if (path == null || path.length() == 0) {
+
+ // null or empty path, there is nothing to return
+ nextPath = null;
+
+ } else {
+
+ // find last non-slash character
+ int i = path.length() - 1;
+ while (i >= 0 && path.charAt(i) == '/') {
+ i--;
+ }
+
+ if (i < 0) {
+ // only slashes, assume root node
+ nextPath = "/";
+
+ } else if (i < path.length() - 1) {
+ // cut off slash
+ nextPath = path.substring(0, i + 1);
+
+ } else {
+ // no trailing slash
+ nextPath = path;
+ }
+ }
+ }
+
+ public boolean hasNext() {
+ return nextPath != null;
+ }
+
+ public String next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ final String result = nextPath;
+
+ // find next path
+ int lastDot = nextPath.lastIndexOf('.');
+ nextPath = (lastDot > 0) ? nextPath.substring(0, lastDot) : null;
+
+ return result;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceProviderEntry.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceProviderEntry.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceProviderEntry.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/ResourceProviderEntry.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,464 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.FastTreeMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>ResourceProviderEntry</code> class represents a node in the tree of
+ * resource providers spanned by the root paths of the provider resources.
+ * <p>
+ * This class is comparable to itself to help keep the child entries list sorted
+ * by their prefix.
+ */
+public class ResourceProviderEntry implements
+ Comparable<ResourceProviderEntry> {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7420631325909144862L;
+
+ private static Logger LOGGER = LoggerFactory.getLogger(ResourceProviderEntry.class);
+
+ // the path to resources provided by the resource provider of this
+ // entry. this path is relative to the path of the parent resource
+ // provider entry and has no trailing slash.
+ private final String path;
+
+ // the path to resources provided by the resource provider of this
+ // entry. this is the same path as the path field but with a trailing
+ // slash to be used as a prefix match resource paths to resolve
+ private final String prefix;
+
+ // the resource provider kept in this entry supporting resources at and
+ // below the path of this entry.
+ private WrappedResourceProvider[] providers = new WrappedResourceProvider[0];
+
+ private long ttime = 0L;
+
+ private long nmiss = 0L;
+
+ private long nsynthetic = 0L;
+
+ private long nreal = 0L;
+
+ private FastTreeMap storageMap = new FastTreeMap();
+
+ private Collection<ResourceProviderEntry> storageMapValues = new ArrayList<ResourceProviderEntry>();
+
+ /**
+ * Creates an instance of this class with the given path relative to the
+ * parent resource provider entry, encapsulating the given ResourceProvider,
+ * and a number of inital child entries.
+ *
+ * @param path
+ * The relative path supported by the provider
+ * @param providerList
+ * The resource provider to encapsulate by this entry.
+ */
+ public ResourceProviderEntry(String path, ResourceProvider[] providerList) {
+ if (path.endsWith("/")) {
+ this.path = path.substring(0, path.length() - 1);
+ this.prefix = path;
+ } else {
+ this.path = path;
+ this.prefix = path + "/";
+ }
+ if ( providerList != null ) {
+ providers = new WrappedResourceProvider[providerList.length];
+ for ( int i = 0; i < providerList.length; i++ ) {
+ if ( providerList[i] instanceof WrappedResourceProvider ) {
+ providers[i] = (WrappedResourceProvider) providerList[i];
+ } else {
+ providers[i] = new WrappedResourceProvider(providerList[i], null);
+ }
+ }
+ }
+
+ // this will consume slightly more memory but ensures read is fast.
+ storageMap.setFast(true);
+
+ }
+
+ String getPath() {
+ return path;
+ }
+
+ /**
+ * Returns the resource provider contained in this entry
+ */
+ public ResourceProvider[] getResourceProviders() {
+ return providers;
+ }
+
+ /**
+ * Returns the resource with the given path or <code>null</code> if neither
+ * the resource provider of this entry nor the resource provider of any of
+ * the child entries can provide the resource.
+ *
+ * @param path
+ * The path to the resource to return.
+ * @return The resource for the path or <code>null</code> if no resource can
+ * be found.
+ * @throws org.apache.sling.api.SlingException
+ * if an error occurrs trying to access an existing resource.
+ */
+ public Resource getResource(ResourceResolver resourceResolver, String path) {
+ return getInternalResource(resourceResolver, path);
+ }
+
+ /**
+ * Adds the given resource provider into the tree for the given prefix.
+ *
+ * @return <code>true</code> if the provider could be entered into the
+ * subtree below this entry. Otherwise <code>false</code> is
+ * returned.
+ */
+ public boolean addResourceProvider(String prefix, ResourceProvider provider, Comparable<?> comparable) {
+ synchronized (this) {
+ String[] elements = split(prefix, '/');
+ List<ResourceProviderEntry> entryPath = new ArrayList<ResourceProviderEntry>();
+ entryPath.add(this); // add this the start so if the list is empty we have a position to add to
+ populateProviderPath(entryPath, elements);
+ for (int i = entryPath.size() - 1; i < elements.length; i++) {
+ String stubPrefix = elements[i];
+ ResourceProviderEntry rpe2 = new ResourceProviderEntry(
+ stubPrefix, new ResourceProvider[0]);
+ entryPath.get(i).put(elements[i], rpe2);
+ entryPath.add(rpe2);
+ }
+ return entryPath.get(elements.length).addInternalProvider(new WrappedResourceProvider(provider, comparable));
+
+ }
+ }
+
+
+ //------------------ Map methods, here so that we can delegate 2 maps together
+ @SuppressWarnings("unchecked")
+ public void put(String key, ResourceProviderEntry value) {
+ storageMap.put(key,value);
+ // get a thread safe copy, the ArrayList constructor does a toArray which is thread safe.
+ storageMapValues = new ArrayList<ResourceProviderEntry>(storageMap.values());
+ }
+
+ public boolean containsKey(String key) {
+ return storageMap.containsKey(key);
+ }
+
+ public ResourceProviderEntry get(String key) {
+ return (ResourceProviderEntry) storageMap.get(key);
+ }
+
+ public Collection<ResourceProviderEntry> values() {
+ return storageMapValues;
+ }
+
+ public boolean removeResourceProvider(String prefix,
+ ResourceProvider resourceProvider, Comparable<?> comparable) {
+ synchronized (this) {
+ String[] elements = split(prefix, '/');
+ List<ResourceProviderEntry> entryPath = new ArrayList<ResourceProviderEntry>();
+ populateProviderPath(entryPath, elements);
+ if (entryPath.size() > 0 && entryPath.size() == elements.length) {
+ // the last element is a perfect match;
+ return entryPath.get(entryPath.size()-1).removeInternalProvider(new WrappedResourceProvider(resourceProvider, comparable));
+ }
+ return false;
+ }
+ }
+
+ // ---------- Comparable<ResourceProviderEntry> interface ------------------
+
+ public int compareTo(ResourceProviderEntry o) {
+ return prefix.compareTo(o.prefix);
+ }
+
+ // ---------- internal -----------------------------------------------------
+
+ /**
+ * Adds a list of providers to this entry.
+ *
+ * @param provider
+ */
+ private boolean addInternalProvider(WrappedResourceProvider provider) {
+ synchronized (providers) {
+ int before = providers.length;
+ Set<WrappedResourceProvider> set = new HashSet<WrappedResourceProvider>();
+ if (providers != null) {
+ set.addAll(Arrays.asList(providers));
+ }
+ LOGGER.debug("Adding provider {} at {} ",provider,path);
+ set.add(provider);
+ providers = conditionalSort(set);
+ return providers.length > before;
+ }
+
+ }
+
+ /**
+ * @param provider
+ * @return
+ */
+ private boolean removeInternalProvider(WrappedResourceProvider provider) {
+ synchronized (providers) {
+ int before = providers.length;
+ Set<WrappedResourceProvider> set = new HashSet<WrappedResourceProvider>();
+ if (providers != null) {
+ set.addAll(Arrays.asList(providers));
+ }
+ set.remove(provider);
+ providers = conditionalSort(set);
+ return providers.length < before;
+ }
+ }
+
+ /**
+ * @param set
+ * @return
+ */
+ private WrappedResourceProvider[] conditionalSort(Set<WrappedResourceProvider> set) {
+
+ List<WrappedResourceProvider> providerList = new ArrayList<WrappedResourceProvider>(
+ set);
+
+ Collections.sort(providerList, new Comparator<WrappedResourceProvider>() {
+
+ @SuppressWarnings("unchecked")
+ public int compare(WrappedResourceProvider o1, WrappedResourceProvider o2) {
+ Comparable c1 = o1.getComparable();
+ Comparable c2 = o2.getComparable();
+ if ( c1 == null && c2 == null ) {
+ return 0;
+ }
+ if ( c1 == null ) {
+ return -1;
+ }
+ if ( c2 == null ) {
+ return 1;
+ }
+ return c1.compareTo(c2);
+ }
+ });
+
+ return set.toArray(new WrappedResourceProvider[set.size()]);
+ }
+
+ /**
+ * Get a of ResourceProvidersEntries leading to the fullPath in reverse
+ * order.
+ *
+ * @param fullPath
+ * the full path
+ */
+ private void populateProviderPath(
+ List<ResourceProviderEntry> providerEntryPath, String[] elements) {
+ ResourceProviderEntry base = this;
+ if (elements != null) {
+ for (String element : elements) {
+ if (element != null) {
+ if (base.containsKey(element)) {
+ base = base.get(element);
+ providerEntryPath.add(base);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Resolve a resource from a path into a Resource
+ *
+ * @param resolver
+ * the ResourceResolver.
+ * @param fullPath
+ * the Full path
+ * @return null if no resource was found, a resource if one was found.
+ */
+ private Resource getInternalResource(ResourceResolver resourceResolver,
+ String fullPath) {
+ long start = System.currentTimeMillis();
+ try {
+
+ if (fullPath == null || fullPath.length() == 0
+ || fullPath.charAt(0) != '/') {
+ nmiss++;
+ LOGGER.debug("Not absolute {} :{}",fullPath,(System.currentTimeMillis() - start));
+ return null; // fullpath must be absolute
+ }
+ String[] elements = split(fullPath, '/');
+
+ List<ResourceProviderEntry> list = new ArrayList<ResourceProviderEntry>();
+ populateProviderPath(list, elements);
+ // the path is in reverse order end first
+
+ for(int i = list.size()-1; i >= 0; i--) {
+ ResourceProvider[] rps = list.get(i).getResourceProviders();
+ for (ResourceProvider rp : rps) {
+
+ Resource resource = rp.getResource(resourceResolver,
+ fullPath);
+ if (resource != null) {
+ nreal++;
+ LOGGER.debug("Resolved Full {} using {} from {} ",new Object[]{
+ fullPath, rp, Arrays.toString(rps)});
+ return resource;
+ }
+ }
+ }
+
+ // resolve against this one
+ final Resource resource = getResourceFromProviders(
+ resourceResolver, fullPath);
+ if (resource != null) {
+ return resource;
+ }
+
+ // query: /libs/sling/servlet/default
+ // resource Provider: libs/sling/servlet/default/GET.servlet
+ // list will match libs, sling, servlet, default
+ // and there will be no resource provider at the end
+ if (list.size() > 0 && list.size() == elements.length ) {
+ if ( list.get(list.size()-1).getResourceProviders().length == 0 ) {
+ nsynthetic++;
+ LOGGER.debug("Resolved Synthetic {}", fullPath);
+ return new SyntheticResource(resourceResolver,
+ fullPath,
+ ResourceProvider.RESOURCE_TYPE_SYNTHETIC);
+ }
+ }
+
+
+
+ LOGGER.debug("Resource null {} ", fullPath);
+ nmiss++;
+ return null;
+ } catch (Exception ex) {
+ LOGGER.debug("Failed! ",ex);
+ return null;
+ } finally {
+ ttime += System.currentTimeMillis() - start;
+ }
+ }
+
+ Resource getResourceFromProviders(final ResourceResolver resourceResolver,
+ final String fullPath) {
+ ResourceProvider[] rps = getResourceProviders();
+ for (ResourceProvider rp : rps) {
+ Resource resource = rp.getResource(resourceResolver, fullPath);
+ if (resource != null) {
+ nreal++;
+ LOGGER.debug("Resolved Base {} using {} ", fullPath, rp);
+ return resource;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param st
+ * @param sep
+ * @return an array of the strings between the separator
+ */
+ static String[] split(String st, char sep) {
+
+ if (st == null) {
+ return new String[0];
+ }
+ char[] pn = st.toCharArray();
+ if (pn.length == 0) {
+ return new String[0];
+ }
+ if (pn.length == 1 && pn[0] == sep) {
+ return new String[0];
+ }
+ int n = 1;
+ int start = 0;
+ int end = pn.length;
+ while (start < end && sep == pn[start])
+ start++;
+ while (start < end && sep == pn[end - 1])
+ end--;
+ for (int i = start; i < end; i++) {
+ if (sep == pn[i]) {
+ n++;
+ }
+ }
+ String[] e = new String[n];
+ int s = start;
+ int j = 0;
+ for (int i = start; i < end; i++) {
+ if (pn[i] == sep) {
+ e[j++] = new String(pn, s, i - s);
+ s = i + 1;
+ }
+ }
+ if (s < end) {
+ e[j++] = new String(pn, s, end - s);
+ }
+ return e;
+ }
+
+ public String getResolutionStats() {
+ long tot = nreal + nsynthetic + nmiss;
+ if (tot == 0) {
+ return null;
+ }
+ float n = tot;
+ float t = ttime;
+ float persec = 1000 * n / t;
+ float avgtime = t / n;
+
+ String stat = "Resolved: Real(" + nreal + ") Synthetic(" + nsynthetic
+ + ") Missing(" + nmiss + ") Total(" + tot + ") at " + persec
+ + " ops/sec avg " + avgtime + " ms";
+ ttime = nmiss = nsynthetic = nreal = 0L;
+ return stat;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.AbstractMap#toString()
+ */
+ @Override
+ public String toString() {
+ return this.path;
+ //"{path:\"" + this.path + "\", providers:"+Arrays.toString(getResourceProviders())+", map:" + storageMap.toString() + "}";
+ }
+
+}
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RootResourceProviderEntry.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RootResourceProviderEntry.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RootResourceProviderEntry.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/RootResourceProviderEntry.java Thu Feb 2 12:46:58 2012
@@ -0,0 +1,141 @@
+/*
+ * 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.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.commons.osgi.OsgiUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the root resource provider entry which keeps track
+ * of the resource providers.
+ */
+public class RootResourceProviderEntry extends ResourceProviderEntry {
+
+ /** default logger */
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ public RootResourceProviderEntry() {
+ super("/", null);
+ }
+
+ public void bindResourceProvider(final ResourceProvider provider,
+ final Map<String, Object> props,
+ final ServiceTracker eventAdminTracker) {
+
+ final String serviceName = getServiceName(provider, props);
+
+ logger.debug("bindResourceProvider: Binding {}", serviceName);
+
+ String[] roots = OsgiUtil.toStringArray(props.get(ResourceProvider.ROOTS));
+ if (roots != null && roots.length > 0) {
+ final EventAdmin localEA = (EventAdmin) ( eventAdminTracker != null ? eventAdminTracker.getService() : null);
+
+ for (String root : roots) {
+ // cut off trailing slash
+ if (root.endsWith("/") && root.length() > 1) {
+ root = root.substring(0, root.length() - 1);
+ }
+
+ // synchronized insertion of new resource providers into
+ // the tree to not inadvertently loose an entry
+ synchronized (this) {
+
+ this.addResourceProvider(root,
+ provider, OsgiUtil.getComparableForServiceRanking(props));
+ }
+ logger.debug("bindResourceProvider: {}={} ({})",
+ new Object[] { root, provider, serviceName });
+ if ( localEA != null ) {
+ final Dictionary<String, Object> eventProps = new Hashtable<String, Object>();
+ eventProps.put(SlingConstants.PROPERTY_PATH, root);
+ localEA.postEvent(new Event(SlingConstants.TOPIC_RESOURCE_PROVIDER_ADDED,
+ eventProps));
+ }
+ }
+ }
+
+ logger.debug("bindResourceProvider: Bound {}", serviceName);
+ }
+
+ public void unbindResourceProvider(final ResourceProvider provider,
+ final Map<String, Object> props,
+ final ServiceTracker eventAdminTracker) {
+
+ final String serviceName = getServiceName(provider, props);
+
+ logger.debug("unbindResourceProvider: Unbinding {}", serviceName);
+
+ String[] roots = OsgiUtil.toStringArray(props.get(ResourceProvider.ROOTS));
+ if (roots != null && roots.length > 0) {
+
+ final EventAdmin localEA = (EventAdmin) ( eventAdminTracker != null ? eventAdminTracker.getService() : null);
+
+ for (String root : roots) {
+ // cut off trailing slash
+ if (root.endsWith("/") && root.length() > 1) {
+ root = root.substring(0, root.length() - 1);
+ }
+
+ // synchronized insertion of new resource providers into
+ // the tree to not inadvertently loose an entry
+ synchronized (this) {
+ // TODO: Do not remove this path, if another resource
+ // owns it. This may be the case if adding the provider
+ // yielded an ResourceProviderEntryException
+ this.removeResourceProvider(root, provider, OsgiUtil.getComparableForServiceRanking(props));
+ }
+ logger.debug("unbindResourceProvider: root={} ({})", root,
+ serviceName);
+ if ( localEA != null ) {
+ final Dictionary<String, Object> eventProps = new Hashtable<String, Object>();
+ eventProps.put(SlingConstants.PROPERTY_PATH, root);
+ localEA.postEvent(new Event(SlingConstants.TOPIC_RESOURCE_PROVIDER_REMOVED,
+ eventProps));
+ }
+ }
+ }
+
+ logger.debug("unbindResourceProvider: Unbound {}", serviceName);
+ }
+
+ private String getServiceName(final ResourceProvider provider, final Map<String, Object> props) {
+ if (logger.isDebugEnabled()) {
+ StringBuilder snBuilder = new StringBuilder(64);
+ snBuilder.append('{');
+ snBuilder.append(provider.toString());
+ snBuilder.append('/');
+ snBuilder.append(props.get(Constants.SERVICE_ID));
+ snBuilder.append('}');
+ return snBuilder.toString();
+ }
+
+ return null;
+ }
+}
\ No newline at end of file