You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2007/11/13 23:04:19 UTC

svn commit: r594664 - in /incubator/sling/trunk/jcr/resource/src/main: java/org/apache/sling/jcr/resource/internal/ java/org/apache/sling/jcr/resource/internal/helper/ resources/OSGI-INF/ resources/OSGI-INF/metatype/

Author: fmeschbe
Date: Tue Nov 13 14:04:18 2007
New Revision: 594664

URL: http://svn.apache.org/viewvc?rev=594664&view=rev
Log:
SLING-93 Add request URL mapping functionality to the JCRResourceManager from
the former sling/core ContentResolverFilter. Configuration for the mapping
is done on the JcrResourceManagerFactory.

Added:
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java
    incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/
    incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/
    incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/metatype.properties
Modified:
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManager.java
    incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManagerFactoryImpl.java

Modified: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManager.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManager.java?rev=594664&r1=594663&r2=594664&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManager.java (original)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManager.java Tue Nov 13 14:04:18 2007
@@ -18,6 +18,9 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.security.AccessControlException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -45,8 +48,10 @@
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.jcr.resource.DefaultMappedObject;
 import org.apache.sling.jcr.resource.JcrResourceUtil;
+import org.apache.sling.jcr.resource.PathResolver;
 import org.apache.sling.jcr.resource.internal.helper.JcrNodeResource;
 import org.apache.sling.jcr.resource.internal.helper.JcrNodeResourceIterator;
+import org.apache.sling.jcr.resource.internal.helper.Mapping;
 import org.apache.sling.jcr.resource.internal.helper.ResourcePathIterator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,7 +59,7 @@
 /**
  * The <code>JcrResourceManager</code> TODO
  */
-public class JcrResourceManager implements ResourceManager {
+public class JcrResourceManager implements ResourceManager, PathResolver {
 
     /** default log */
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -101,17 +106,8 @@
     // ---------- ResourceResolver interface ----------------------------------
 
     public Resource resolve(ServletRequest request) throws SlingException {
-        Resource result = null;
-        String path = null;
         String pathInfo = ((HttpServletRequest) request).getPathInfo();
-        try {
-            final ResourcePathIterator it = new ResourcePathIterator(pathInfo);
-            while (it.hasNext() && result == null) {
-                result = getResourceInternal(it.next(), null);
-            }
-        } catch (RepositoryException re) {
-            throw new SlingException("RepositoryException for path=" + path, re);
-        }
+        Resource result = resolve(pathInfo);
 
         if (result == null) {
             result = new NonExistingResource(pathInfo);
@@ -225,6 +221,88 @@
         }
     }
 
+    // ---------- PathResolver interface --------------------------------------
+
+    /**
+     * @throws AccessControlException If an item would exist but is not readable
+     *      to this manager's session.
+     */
+    public Resource resolve(String url) throws SlingException {
+
+        // decode the request URI (required as the servlet container does not
+        try {
+            url = URLDecoder.decode(url, "UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            log.error("Cannot decode request URI using UTF-8", uee);
+        } catch (Exception e) {
+            log.error("Failed to decode request URI " + url, e);
+        }
+
+
+        // convert fake urls
+        String realUrl = (String) factory.getVirtualURLMap().get(url);
+        if (realUrl != null) {
+            log.debug("resolve: Using real url '{}' for virtual url '{}'",
+                realUrl, url);
+            url = realUrl;
+        }
+
+        try {
+
+            // translate url to a mapped url structure
+            return transformURL(url);
+
+        } catch (AccessControlException ace) {
+            // rethrow AccessControlExceptions to be handled
+            throw ace;
+
+        } catch (SlingException se) {
+            // rethrow SlingException as it is declared
+            throw se;
+
+        } catch (Throwable t) {
+            // wrap any other issue into a SlingException
+            throw new SlingException("Problem resolving " + url, t);
+        }
+    }
+
+    public String pathToURL(String path) {
+        return pathToURL(null, path, null);
+    }
+
+    public String pathToURL(String prefix, String path, String suffix) {
+        String href = null;
+
+        // get first map
+        Mapping[] mappings = factory.getMappings();
+        for (int i = 0; i < mappings.length && href == null; i++) {
+            href = mappings[i].mapHandle(path);
+        }
+
+        // if no mapping's to prefix matches the handle, use the handle itself
+        if (href == null) {
+            href = path;
+        }
+
+        // check virtual mappings
+        String virtual = (String) factory.getVirtualURLMap().getKey(href);
+        if (virtual != null) {
+            href = virtual;
+        }
+
+        // handle prefix and suffix
+        if (prefix != null && !prefix.equals("") && !prefix.equals("/")) {
+            href = prefix + href;
+        }
+        if (suffix != null) {
+            href += suffix;
+        }
+
+        log.debug("MapHandle: {} + {} + {} -> {}", new Object[] { prefix, path,
+            suffix, href });
+        return href;
+    }
+
     // ---------- ResourceManager interface -----------------------------------
 
     public Resource getResource(String path, Class<?> type)
@@ -408,6 +486,46 @@
     }
 
     // ---------- implementation helper ----------------------------------------
+
+    private Resource transformURL(String url) throws SlingException {
+        Mapping[] mappings = factory.getMappings();
+        for (int i = 0; i < mappings.length; i++) {
+            // exchange the 'to'-portion with the 'from' portion and check
+            String href = mappings[i].mapUri(url);
+            if (href == null) {
+                log.debug("Mapping {} cannot map {}", mappings[i], url);
+                continue;
+            }
+
+            Resource resource = scanPath(href);
+            if (resource != null) {
+                return resource;
+            }
+
+            log.debug("Cannot resolve {} to resource", href);
+        }
+
+        log.error("Could not resolve URL {} to a Content object", url);
+        return null;
+
+    }
+
+    private Resource scanPath(String uriPath) throws SlingException {
+        Resource resource = null;
+        String curPath = uriPath;
+        try {
+            final ResourcePathIterator it = new ResourcePathIterator(uriPath);
+            while (it.hasNext() && resource == null) {
+                curPath = it.next();
+                resource = getResourceInternal(curPath, null);
+            }
+        } catch (RepositoryException re) {
+            throw new SlingException("Problem trying " + curPath
+                + " for request path " + uriPath, re);
+        }
+
+        return resource;
+    }
 
     /** Creates a JcrNodeResource with the given path if existing */
     protected Resource getResourceInternal(String path, Class<?> type)

Modified: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManagerFactoryImpl.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManagerFactoryImpl.java?rev=594664&r1=594663&r2=594664&view=diff
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManagerFactoryImpl.java (original)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceManagerFactoryImpl.java Tue Nov 13 14:04:18 2007
@@ -18,19 +18,24 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+import org.apache.commons.collections.BidiMap;
+import org.apache.commons.collections.bidimap.TreeBidiMap;
 import org.apache.jackrabbit.ocm.manager.ObjectContentManager;
 import org.apache.sling.api.resource.ResourceManager;
 import org.apache.sling.commons.mime.MimeTypeService;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.resource.JcrResourceManagerFactory;
+import org.apache.sling.jcr.resource.internal.helper.Mapping;
 import org.apache.sling.jcr.resource.internal.loader.Loader;
 import org.apache.sling.jcr.resource.internal.mapping.ObjectContentManagerFactory;
 import org.osgi.framework.Bundle;
@@ -53,10 +58,10 @@
  * <li>Fires OSGi EventAdmin events on behalf of internal helper objects
  * </ul>
  *
- * @scr.component immediate="true" label="%content.name"
- *                description="%content.description" metatype="false"
- * @scr.property name="service.description" value="Sling
- *               JcrResourceManagerFactory Implementation"
+ * @scr.component immediate="true" label="%resource.resolver.name"
+ *                description="%resource.resolver.description"
+ * @scr.property name="service.description"
+ *                value="Sling JcrResourceManagerFactory Implementation"
  * @scr.property name="service.vendor" value="The Apache Software Foundation"
  * @scr.service interface="org.apache.sling.jcr.resource.JcrResourceManagerFactory"
  */
@@ -64,6 +69,28 @@
         JcrResourceManagerFactory, SynchronousBundleListener {
 
     /**
+     * @scr.property value="true" type="Boolean"
+     */
+     private static final String PROP_ALLOW_DIRECT = "resource.resolver.allowDirect";
+
+     /**
+      * The resolver.virtual property has no default configuration. But the sling
+      * maven plugin and the sling management console cannot handle empty
+      * multivalue properties at the moment. So we just add a dummy direct
+      * mapping.
+      * @scr.property values.1="/-/"
+      */
+     private static final String PROP_VIRTUAL = "resource.resolver.virtual";
+
+     /**
+      * @scr.property values.1="/-/" values.2="/content/-/"
+      *               Cvalues.3="/apps/&times;/docroot/-/"
+      *               Cvalues.4="/libs/&times;/docroot/-/"
+      *               values.5="/system/docroot/-/"
+      */
+     private static final String PROP_MAPPING = "resource.resolver.mapping";
+
+     /**
      * The JCR Repository we access to resolve resources
      *
      * @scr.reference
@@ -88,6 +115,15 @@
     /** The OSGi Component Context */
     private ComponentContext componentContext;
 
+    /** all mappings */
+    private Mapping[] mappings;
+
+    /** The fake urls */
+    private BidiMap virtualURLMap;
+
+    /** <code>true</code>, if direct mappings from URI to handle are allowed */
+    private boolean allowDirect = false;
+
     /**
      * Map of administrative sessions used to check item existence. Indexed by
      * workspace name. The map is filled on-demand. The sessions are closed when
@@ -258,6 +294,15 @@
         return (mts != null) ? mts.getMimeType(name) : null;
     }
 
+    /** If url is a virtual URL returns the real URL, otherwise returns url */
+    BidiMap getVirtualURLMap() {
+        return virtualURLMap;
+    }
+
+    Mapping[] getMappings() {
+        return mappings;
+    }
+
     // ---------- SCR Integration
 
     protected void activate(ComponentContext componentContext) {
@@ -287,6 +332,36 @@
                 objectContentManagerFactory.registerMapperClient(bundles[i]);
             }
         }
+
+        Dictionary<?, ?> properties = componentContext.getProperties();
+
+        BidiMap virtuals = new TreeBidiMap();
+        String[] virtualList = (String[]) properties.get(PROP_VIRTUAL);
+        for (int i=0; virtualList != null && i < virtualList.length; i++) {
+            String[] parts = Mapping.split(virtualList[i]);
+            virtuals.put(parts[0], parts[2]);
+        }
+        virtualURLMap = virtuals;
+
+        List<Mapping> maps = new ArrayList<Mapping>();
+        String[] mappingList = (String[]) properties.get(PROP_MAPPING);
+        for (int i=0; mappingList != null && i < mappingList.length; i++) {
+            maps.add(new Mapping(mappingList[i]));
+        }
+        Mapping[] tmp = maps.toArray(new Mapping[maps.size()]);
+
+        // check whether direct mappings are allowed
+        Boolean directProp = (Boolean) properties.get(PROP_ALLOW_DIRECT);
+        allowDirect = (directProp != null) ? directProp.booleanValue() : true;
+        if (allowDirect) {
+            Mapping[] tmp2 = new Mapping[tmp.length + 1];
+            tmp2[0] = Mapping.DIRECT;
+            System.arraycopy(tmp, 0, tmp2, 1, tmp.length);
+            mappings = tmp2;
+        } else {
+            mappings = tmp;
+        }
+
     }
 
     protected void deactivate(ComponentContext oldContext) {

Added: incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java?rev=594664&view=auto
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java (added)
+++ incubator/sling/trunk/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/Mapping.java Tue Nov 13 14:04:18 2007
@@ -0,0 +1,204 @@
+/*
+ * 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.StringTokenizer;
+
+/**
+ * The <code>Mapping</code> class conveys the mapping configuration used by
+ * the
+ * {@link org.apache.sling.jcr.resource.internal.JcrResourceManagerFactoryImpl}.
+ */
+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;
+        }
+    };
+
+    /** 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;
+
+    /**
+     * Creates a new instance of a mapping.
+     *
+     * @param from Handle prefix possible valid in the ContentBus.
+     * @param to URI path prefix to be replaced by from to get a possibly valid
+     *            handle.
+     * @param dir the direction of the mapping. either "inwards", "outwards" or
+     *            "both".
+     * @throws NullPointerException if either <code>from</code> or
+     *             <code>to</code> is <code>null</code>.
+     */
+    public Mapping(String from, String to, String dir) {
+        this(from, to, "in".equals(dir) ? Mapping.INBOUND : ("out".equals(dir)
+                ? Mapping.OUTBOUND
+                : Mapping.BOTH));
+    }
+
+    /**
+     * Creates a new instance of a mapping.
+     *
+     * @param from Handle prefix possible valid in the ContentBus.
+     * @param to URI path prefix to be replaced by from to get a possibly valid
+     *            handle.
+     * @throws NullPointerException if either <code>from</code> or
+     *             <code>to</code> is <code>null</code>.
+     */
+    public Mapping(String from, String to) {
+        this(from, to, Mapping.BOTH);
+    }
+
+    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);
+    }
+
+    /**
+     * 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 #mapsInwards} 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 #mapsOutwards} returns <code>false</code>.
+     */
+    public String mapHandle(String handle) {
+        return (this.mapsOutbound() && handle.startsWith(this.from)) ? this.to
+            + handle.substring(this.fromLength) : null;
+    }
+
+    /**
+     * 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) {
+        String[] res = new String[3]; // src, op, dst
+        StringTokenizer st = new StringTokenizer(map, "-<>", true);
+
+        for (int i = 0; i < res.length; i++) {
+            res[i] = st.hasMoreTokens() ? st.nextToken() : "";
+        }
+
+        return res;
+    }
+
+}

Added: incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/metatype.properties
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/metatype.properties?rev=594664&view=auto
==============================================================================
--- incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/metatype.properties (added)
+++ incubator/sling/trunk/jcr/resource/src/main/resources/OSGI-INF/metatype/metatype.properties Tue Nov 13 14:04:18 2007
@@ -0,0 +1,45 @@
+#
+#  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.
+#
+
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the SCR plugin
+
+#
+# Localizations for JcrResourceManagerFactoryImpl configuration
+resource.resolver.name = Content Resolver
+resource.resolver.description =  Configures the Content Resolver for request \
+ URL and content path rewriting.
+resource.resolver.allowDirect.name = Allow Direct Mapping
+resource.resolver.allowDirect.description = Whether to add a direct URL \
+ mapping to the front of the mapping list.
+resource.resolver.virtual.name = Virtual URLs
+resource.resolver.virtual.description = List of virtual URLs and there \
+ mappings to real URLs. Format is <externalURL>-<internalURL>. Mappings are \
+ applied on the complete request URL only.
+resource.resolver.mapping.name = URL Mappings
+resource.resolver.mapping.description = List of mappings to apply to URLs. \
+ Incoming mappings are applied to request URLs to map to Content paths, \
+ outgoing mappings are applied to map Content paths to URLs used on subsequent \
+ requests. Form ist <externalURLPrefix><op><internalURLPrefix> where <op> is \
+ ">" for incoming mappings, "<" for outgoing mappings and "-" for mappings \
+ applied in both directions. Mappings are applied in configuration order by \
+ comparing and replacing URL prefixes.