You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by to...@apache.org on 2015/02/09 17:00:53 UTC

svn commit: r1658445 [1/2] - in /sling/trunk: ./ bundles/api/src/main/java/org/apache/sling/api/resource/ bundles/jcr/it-resource-versioning/ bundles/jcr/it-resource-versioning/src/ bundles/jcr/it-resource-versioning/src/test/ bundles/jcr/it-resource-v...

Author: tomekr
Date: Mon Feb  9 16:00:52 2015
New Revision: 1658445

URL: http://svn.apache.org/r1658445
Log:
SLING-848 Support getting versioned resources by using uri path parameters

Added:
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ParametrizableResourceProvider.java
    sling/trunk/bundles/jcr/it-resource-versioning/
    sling/trunk/bundles/jcr/it-resource-versioning/.gitignore
    sling/trunk/bundles/jcr/it-resource-versioning/pom.xml
    sling/trunk/bundles/jcr/it-resource-versioning/src/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/
    sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/ResourceVersioningTest.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParametersParser.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParsedParameters.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/PathParser.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/params/
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/params/PathParametersParserTest.java
Modified:
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/package-info.java
    sling/trunk/bundles/jcr/resource/pom.xml
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResource.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceIterator.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResource.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResourceTest.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrTestNodeResource.java
    sling/trunk/bundles/resourceresolver/pom.xml
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceIterator.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntry.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderFactoryHandler.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderHandler.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandlerTest.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java
    sling/trunk/pom.xml
    sling/trunk/testing/mocks/sling-mock/pom.xml
    sling/trunk/testing/mocks/sling-mock/src/main/java/org/apache/sling/testing/mock/sling/MockJcrResourceResolverFactory.java

Added: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ParametrizableResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ParametrizableResourceProvider.java?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ParametrizableResourceProvider.java (added)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ParametrizableResourceProvider.java Mon Feb  9 16:00:52 2015
@@ -0,0 +1,38 @@
+/*
+ * 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.api.resource;
+
+import java.util.Map;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * This extension allows resource provider implementations to support
+ * semicolon-separated parameters added to the URI, eg.: {@code /content/test;v='1.0'}.
+ * 
+ * @since 2.8.0
+ */
+@ConsumerType
+public interface ParametrizableResourceProvider {
+
+    /**
+     * @see ResourceProvider#getResource(ResourceResolver, String)
+     */
+    Resource getResource(ResourceResolver resourceResolver, String path, Map<String, String> parameters);
+}

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java Mon Feb  9 16:00:52 2015
@@ -21,6 +21,7 @@ package org.apache.sling.api.resource;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -126,6 +127,13 @@ public class ResourceMetadata extends Ha
      */
     public static final String INTERNAL_CONTINUE_RESOLVING = ":org.apache.sling.resource.internal.continue.resolving";
 
+    /**
+     * Returns a map containing parameters added to path after semicolon.
+     * For instance, map for path <code>/content/test;v='1.2.3'.html</code>
+     * will contain one entry key <code>v</code> and value <code>1.2.3</code>.
+     */
+    public static final String PARAMETER_MAP = "sling.parameterMap";
+
     private boolean isReadOnly = false;
 
     /**
@@ -294,6 +302,32 @@ public class ResourceMetadata extends Ha
     }
 
     /**
+     * Sets the {@link #PARAMETER_MAP} property to
+     * <code>parameterMap</code> if not <code>null</code>.
+     */
+    public void setParameterMap(Map<String, String> parameterMap) {
+        if (parameterMap != null) {
+            put(PARAMETER_MAP, new LinkedHashMap<String, String>(parameterMap));
+        }
+    }
+
+    /**
+     * Returns the {@link #PARAMETER_MAP} property if not
+     * <code>null</code> and a <code>Map</code> instance. Otherwise
+     * <code>null</code> is returned.
+     */
+    @SuppressWarnings("unchecked")
+    public Map<String, String> getParameterMap() {
+        Object value = get(PARAMETER_MAP);
+        if (value instanceof Map) {
+            return (Map<String, String>) value;
+        }
+
+        return null;
+    }
+
+    
+    /**
      * Make this object read-only. All method calls trying to modify this object
      * result in an exception!
      * @since 2.3

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/package-info.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/package-info.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/package-info.java Mon Feb  9 16:00:52 2015
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("2.7.0")
+@Version("2.8.0")
 package org.apache.sling.api.resource;
 
 import aQute.bnd.annotation.Version;

Added: sling/trunk/bundles/jcr/it-resource-versioning/.gitignore
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/it-resource-versioning/.gitignore?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/it-resource-versioning/.gitignore (added)
+++ sling/trunk/bundles/jcr/it-resource-versioning/.gitignore Mon Feb  9 16:00:52 2015
@@ -0,0 +1,3 @@
+/jackrabbit
+/oak
+/sling

Added: sling/trunk/bundles/jcr/it-resource-versioning/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/it-resource-versioning/pom.xml?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/it-resource-versioning/pom.xml (added)
+++ sling/trunk/bundles/jcr/it-resource-versioning/pom.xml Mon Feb  9 16:00:52 2015
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>22</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.jcr.repository.it-resource-versioning</artifactId>
+    <packaging>jar</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling Versioning Integration Tests</name>
+    <description>Tests versioning API implementation in JcrResourceProvider</description>
+    <inceptionYear>2015</inceptionYear>
+
+    <properties>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+            <version>1.1.3-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock-jackrabbit</artifactId>
+            <version>0.1.3-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

Added: sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/ResourceVersioningTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/ResourceVersioningTest.java?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/ResourceVersioningTest.java (added)
+++ sling/trunk/bundles/jcr/it-resource-versioning/src/test/java/org/apache/sling/jcr/resource/it/ResourceVersioningTest.java Mon Feb  9 16:00:52 2015
@@ -0,0 +1,160 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.version.VersionManager;
+import javax.naming.NamingException;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ResourceVersioningTest {
+
+    private VersionManager versionManager;
+
+    private Node testNode;
+
+    private ResourceResolver resolver;
+
+    private Session session;
+
+    @Before
+    public void setUp() throws Exception {
+        resolver = MockSling.newResourceResolver(ResourceResolverType.JCR_JACKRABBIT);
+        session = resolver.adaptTo(Session.class);
+        versionManager = session.getWorkspace().getVersionManager();
+        registerNamespace("sling", "http://sling.apache.org/jcr/sling/1.0");
+
+        Node testRoot = session.getRootNode().addNode("content");
+        testNode = testRoot.addNode("test");
+        testNode.addMixin(JcrConstants.MIX_VERSIONABLE);
+        session.save();
+
+        versionManager.checkout(testNode.getPath());
+        testNode.setProperty("prop", "oldvalue");
+        testNode.addNode("x").addNode("y").setProperty("child_prop", "child_old_value");
+        session.save();
+        versionManager.checkin(testNode.getPath());
+
+        versionManager.checkout(testNode.getPath());
+        testNode.setProperty("prop", "newvalue");
+        testNode.getProperty("x/y/child_prop").setValue("child_new_value");
+        session.save();
+        versionManager.checkin(testNode.getPath());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        session.removeItem("/content");
+        session.save();
+        resolver.close();
+    }
+
+    @Test
+    public void getResourceOnVersionableNode() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test;v='1.0'");
+        String prop = resource.adaptTo(ValueMap.class).get("prop", String.class);
+        assertEquals("/content/test;v='1.0'", resource.getPath());
+        assertEquals("oldvalue", prop);
+    }
+
+    @Test
+    public void getResourceOnVersionableProperty() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test/prop;v='1.0'");
+        String prop = resource.adaptTo(String.class);
+        assertEquals("/content/test/prop;v='1.0'", resource.getPath());
+        assertEquals("oldvalue", prop);
+    }
+
+    @Test
+    public void resolveOnVersionableNode() throws RepositoryException, NamingException {
+        for (String path : Arrays.asList("/content/test;v='1.0'.html", "/content/test.html;v=1.0",
+                "/content/test;v='1.0'.html/some/suffix", "/content/test.html;v=1.0/some/suffix")) {
+            Resource resource = resolver.resolve(path);
+            String prop = resource.adaptTo(ValueMap.class).get("prop", String.class);
+            assertEquals("/content/test;v='1.0'", resource.getPath());
+            assertEquals("oldvalue", prop);
+        }
+    }
+
+    @Test
+    public void getResourceOnVersionableDescendant() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test/x/y;v='1.0'");
+        String prop = resource.adaptTo(ValueMap.class).get("child_prop", String.class);
+        assertEquals("/content/test/x/y;v='1.0'", resource.getPath());
+        assertEquals("child_old_value", prop);
+    }
+
+    @Test
+    public void getResourceOnVersionableDescendantProperty() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test/x/y/child_prop;v='1.0'");
+        String prop = resource.adaptTo(String.class);
+        assertEquals("/content/test/x/y/child_prop;v='1.0'", resource.getPath());
+        assertEquals("child_old_value", prop);
+    }
+
+    @Test
+    public void getChildOnVersionableResource() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test;v='1.0'").getChild("x/y");
+        String prop = resource.adaptTo(ValueMap.class).get("child_prop", String.class);
+        assertEquals("/content/test/x/y;v='1.0'", resource.getPath());
+        assertEquals("child_old_value", prop);
+    }
+
+    @Test
+    public void listChildrenOnVersionableResource() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test/x;v='1.0'").listChildren().next();
+        String prop = resource.adaptTo(ValueMap.class).get("child_prop", String.class);
+        assertEquals("/content/test/x/y;v='1.0'", resource.getPath());
+        assertEquals("child_old_value", prop);
+    }
+
+    @Test
+    public void getParentOnVersionableResource() throws RepositoryException, NamingException {
+        Resource resource = resolver.getResource("/content/test/x;v='1.0'").getParent();
+        String prop = resource.adaptTo(ValueMap.class).get("prop", String.class);
+        assertEquals("/content/test", resource.getPath());
+        assertEquals("newvalue", prop);
+    }
+
+    private void registerNamespace(String prefix, String uri) throws RepositoryException {
+        NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry();
+        if (!ArrayUtils.contains(registry.getPrefixes(), prefix)) {
+            session.getWorkspace().getNamespaceRegistry().registerNamespace(prefix, uri);
+        }
+
+    }
+}

Modified: sling/trunk/bundles/jcr/resource/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/pom.xml?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/pom.xml (original)
+++ sling/trunk/bundles/jcr/resource/pom.xml Mon Feb  9 16:00:52 2015
@@ -142,7 +142,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.8.0</version>
+            <version>2.8.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResource.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResource.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResource.java Mon Feb  9 16:00:52 2015
@@ -47,7 +47,9 @@ abstract class JcrItemResource<T extends
 
     private final ResourceResolver resourceResolver;
 
-    private String path;
+    protected final String path;
+
+    protected final String version;
 
     private final T item;
 
@@ -57,12 +59,14 @@ abstract class JcrItemResource<T extends
 
     protected JcrItemResource(final ResourceResolver resourceResolver,
                               final String path,
+                              final String version,
                               final T item,
                               final ResourceMetadata metadata,
                               final PathMapper pathMapper) {
 
         this.resourceResolver = resourceResolver;
         this.path = path;
+        this.version = version;
         this.item = item;
         this.metadata = metadata;
         this.pathMapper = pathMapper;
@@ -79,7 +83,13 @@ abstract class JcrItemResource<T extends
      * @see org.apache.sling.api.resource.Resource#getPath()
      */
     public String getPath() {
-        return path;
+        if (version == null) {
+            return path;
+        } else if (version.contains(".")) {
+            return String.format("%s;v='%s'", path, version);
+        } else {
+            return String.format("%s;v=%s", path, version);
+        }
     }
 
     /**

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java Mon Feb  9 16:00:52 2015
@@ -77,10 +77,11 @@ class JcrNodeResource extends JcrItemRes
      */
     public JcrNodeResource(final ResourceResolver resourceResolver,
                            final String path,
+                           final String version,
                            final Node node,
                            final ClassLoader dynamicClassLoader,
                            final PathMapper pathMapper) {
-        super(resourceResolver, path, node, new JcrNodeResourceMetadata(node), pathMapper);
+        super(resourceResolver, path, version, node, new JcrNodeResourceMetadata(node), pathMapper);
         this.pathMapper = pathMapper;
         this.dynamicClassLoader = dynamicClassLoader;
         this.resourceSuperType = UNSET_RESOURCE_SUPER_TYPE;
@@ -241,7 +242,7 @@ class JcrNodeResource extends JcrItemRes
     Iterator<Resource> listJcrChildren() {
         try {
             if (getNode().hasNodes()) {
-                return new JcrNodeResourceIterator(getResourceResolver(),
+                return new JcrNodeResourceIterator(getResourceResolver(), path, version,
                     getNode().getNodes(), this.dynamicClassLoader, pathMapper);
             }
         } catch (final RepositoryException re) {

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceIterator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceIterator.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceIterator.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceIterator.java Mon Feb  9 16:00:52 2015
@@ -23,6 +23,7 @@ import java.util.NoSuchElementException;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -53,6 +54,10 @@ public class JcrNodeResourceIterator imp
 
     private final PathMapper pathMapper;
 
+    private final String parentPath;
+
+    private final String parentVersion;
+
     /**
      * Creates an instance using the given resource manager and the nodes
      * provided as a node iterator.
@@ -61,7 +66,23 @@ public class JcrNodeResourceIterator imp
                                    final NodeIterator nodes,
                                    final ClassLoader dynamicClassLoader,
                                    final PathMapper pathMapper) {
+        this(resourceResolver, null, null, nodes, dynamicClassLoader, pathMapper);
+    }
+
+    /**
+     * Creates an instance using the given resource manager and the nodes
+     * provided as a node iterator. Paths of the iterated resources will be
+     * concatenated from the parent path, node name and the version number.
+     */
+    public JcrNodeResourceIterator(final ResourceResolver resourceResolver,
+                                   final String parentPath,
+                                   final String parentVersion,
+                                   final NodeIterator nodes,
+                                   final ClassLoader dynamicClassLoader,
+                                   final PathMapper pathMapper) {
         this.resourceResolver = resourceResolver;
+        this.parentPath = parentPath;
+        this.parentVersion = parentVersion;
         this.nodes = nodes;
         this.dynamicClassLoader = dynamicClassLoader;
         this.pathMapper = pathMapper;
@@ -94,11 +115,10 @@ public class JcrNodeResourceIterator imp
         while (nodes.hasNext()) {
             try {
                 final Node n = nodes.nextNode();
-                final String path = pathMapper.mapJCRPathToResourcePath(n.getPath());
+                final String path = getPath(n);
                 if ( path != null ) {
                     final Resource resource = new JcrNodeResource(resourceResolver,
-                        path,
-                        n, dynamicClassLoader, pathMapper);
+                        path, parentVersion, n, dynamicClassLoader, pathMapper);
                     LOGGER.debug("seek: Returning Resource {}", resource);
                     return resource;
                 }
@@ -113,4 +133,14 @@ public class JcrNodeResourceIterator imp
         LOGGER.debug("seek: No more nodes, iterator exhausted");
         return null;
     }
+
+    private String getPath(Node node) throws RepositoryException {
+        final String path;
+        if (parentPath == null) {
+            path = node.getPath();
+        } else {
+            path = String.format("%s/%s", parentPath, node.getName());
+        }
+        return pathMapper.mapJCRPathToResourcePath(path);
+    }
 }

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResource.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResource.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResource.java Mon Feb  9 16:00:52 2015
@@ -53,10 +53,11 @@ class JcrPropertyResource extends JcrIte
 
     public JcrPropertyResource(final ResourceResolver resourceResolver,
                                final String path,
+                               final String version,
                                final Property property,
                                final PathMapper pathMapper)
     throws RepositoryException {
-        super(resourceResolver, path, property, new ResourceMetadata(), pathMapper);
+        super(resourceResolver, path, version, property, new ResourceMetadata(), pathMapper);
         this.resourceType = getResourceTypeForNode(property.getParent())
                 + "/" + property.getName();
         if (PropertyType.BINARY != getProperty().getType()) {

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java Mon Feb  9 16:00:52 2015
@@ -20,11 +20,14 @@ package org.apache.sling.jcr.resource.in
 
 import java.security.Principal;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Queue;
 import java.util.Set;
 
 import javax.jcr.Item;
@@ -37,8 +40,12 @@ import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionManager;
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.UserManager;
@@ -47,6 +54,7 @@ import org.apache.sling.api.adapter.Slin
 import org.apache.sling.api.resource.AttributableResourceProvider;
 import org.apache.sling.api.resource.DynamicResourceProvider;
 import org.apache.sling.api.resource.ModifyingResourceProvider;
+import org.apache.sling.api.resource.ParametrizableResourceProvider;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.QueriableResourceProvider;
 import org.apache.sling.api.resource.QuerySyntaxException;
@@ -77,7 +85,8 @@ public class JcrResourceProvider
                AttributableResourceProvider,
                QueriableResourceProvider,
                RefreshableResourceProvider,
-               ModifyingResourceProvider {
+               ModifyingResourceProvider,
+               ParametrizableResourceProvider {
 
     /** column name for node path */
     private static final String QUERY_COLUMN_PATH = "jcr:path";
@@ -125,7 +134,7 @@ public class JcrResourceProvider
     @SuppressWarnings("javadoc")
     public Resource getResource(ResourceResolver resourceResolver,
             HttpServletRequest request, String path) throws SlingException {
-        return getResource(resourceResolver, path);
+        return getResource(resourceResolver, path, Collections.<String, String> emptyMap());
     }
 
     /**
@@ -133,9 +142,18 @@ public class JcrResourceProvider
      */
     public Resource getResource(ResourceResolver resourceResolver, String path)
     throws SlingException {
+        return getResource(resourceResolver, path, Collections.<String, String> emptyMap());
+    }
+
+    
+    /**
+     * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, java.lang.String)
+     */
+    public Resource getResource(ResourceResolver resourceResolver, String path, Map<String, String> parameters)
+    throws SlingException {
         this.checkClosed();
         try {
-            return createResource(resourceResolver, path);
+            return createResource(resourceResolver, path, parameters);
         } catch (RepositoryException re) {
             throw new SlingException("Problem retrieving node based resource "
                 + path, re);
@@ -162,7 +180,7 @@ public class JcrResourceProvider
             // children
             try {
                 parentItemResource = createResource(
-                    parent.getResourceResolver(), parent.getPath());
+                    parent.getResourceResolver(), parent.getPath(), Collections.<String, String> emptyMap());
             } catch (RepositoryException re) {
                 parentItemResource = null;
             }
@@ -189,28 +207,86 @@ public class JcrResourceProvider
      *             item in the repository.
      */
     private JcrItemResource createResource(final ResourceResolver resourceResolver,
-            final String resourcePath) throws RepositoryException {
+            final String resourcePath, final Map<String, String> parameters) throws RepositoryException {
         final String jcrPath = pathMapper.mapResourcePathToJCRPath(resourcePath);
         if (jcrPath != null && itemExists(jcrPath)) {
             Item item = session.getItem(jcrPath);
+            final String version;
+            if (parameters != null && parameters.containsKey("v")) {
+                version = parameters.get("v");
+                item = getHistoricItem(item, version);
+            } else {
+                version = null;
+            }
             if (item.isNode()) {
                 log.debug(
                     "createResource: Found JCR Node Resource at path '{}'",
                     resourcePath);
-                return new JcrNodeResource(resourceResolver, resourcePath, (Node) item, dynamicClassLoader, pathMapper);
+                final JcrNodeResource resource = new JcrNodeResource(resourceResolver, resourcePath, version, (Node) item, dynamicClassLoader, pathMapper);
+                resource.getResourceMetadata().setParameterMap(parameters);
+                return resource;
             }
 
             log.debug(
                 "createResource: Found JCR Property Resource at path '{}'",
                 resourcePath);
-            return new JcrPropertyResource(resourceResolver, resourcePath,
+            final JcrPropertyResource resource = new JcrPropertyResource(resourceResolver, resourcePath, version,
                 (Property) item, pathMapper);
+            resource.getResourceMetadata().setParameterMap(parameters);
+            return resource;
         }
 
         log.debug("createResource: No JCR Item exists at path '{}'", jcrPath);
         return null;
     }
 
+    private Item getHistoricItem(Item item, String versionSpecifier) throws RepositoryException {
+        Item currentItem = item;
+        LinkedList<String> relPath = new LinkedList<String>();
+        Node version = null;
+        while (!"/".equals(currentItem.getPath())) {
+            if (isVersionable(currentItem)) {
+                version = getFrozenNode((Node) currentItem, versionSpecifier);
+                break;
+            } else {
+                relPath.addFirst(currentItem.getName());
+                currentItem = currentItem.getParent();
+            }
+        }
+        if (version != null) {
+            return getSubitem(version, StringUtils.join(relPath.iterator(), '/'));
+        }
+        return null;
+    }
+
+    private static Item getSubitem(Node node, String relPath) throws RepositoryException {
+        if (relPath.length() == 0) { // not using isEmpty() due to 1.5 compatibility
+            return node;
+        } else if (node.hasNode(relPath)) {
+            return node.getNode(relPath);
+        } else if (node.hasProperty(relPath)) {
+            return node.getProperty(relPath);
+        } else {
+            return null;
+        }
+    }
+
+    private Node getFrozenNode(Node node, String versionSpecifier) throws RepositoryException {
+        final VersionManager versionManager = session.getWorkspace().getVersionManager();
+        final VersionHistory history = versionManager.getVersionHistory(node.getPath());
+        if (history.hasVersionLabel(versionSpecifier)) {
+            return history.getVersionByLabel(versionSpecifier).getFrozenNode();
+        } else if (history.hasNode(versionSpecifier)) {
+            return history.getVersion(versionSpecifier).getFrozenNode();
+        } else {
+            return null;
+        }
+    }
+
+    private static boolean isVersionable(Item item) throws RepositoryException {
+        return item.isNode() && ((Node) item).isNodeType(JcrConstants.MIX_VERSIONABLE);
+    }
+
     /**
      * Checks whether the item exists and this content manager's session has
      * read access to the item. If the item does not exist, access control is
@@ -515,7 +591,7 @@ public class JcrResourceProvider
                 }
             }
 
-            return new JcrNodeResource(resolver, resourcePath, node, this.dynamicClassLoader, pathMapper);
+            return new JcrNodeResource(resolver, resourcePath, null, node, this.dynamicClassLoader, pathMapper);
         } catch (final RepositoryException e) {
             throw new PersistenceException("Unable to create node at " + jcrPath, e, resourcePath, null);
         }

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java Mon Feb  9 16:00:52 2015
@@ -45,7 +45,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         file = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), file, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), null, file, null, new PathMapperImpl());
 
         assertEquals(file.getPath(), jnr.getPath());
 
@@ -63,7 +63,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         file = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), file, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), null, file, null, new PathMapperImpl());
 
         assertEquals(file.getPath(), jnr.getPath());
 
@@ -79,7 +79,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         res = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), res, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), null, res, null, new PathMapperImpl());
 
         assertEquals(res.getPath(), jnr.getPath());
 
@@ -95,7 +95,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         res = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), res, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), null, res, null, new PathMapperImpl());
 
         assertEquals(res.getPath(), jnr.getPath());
 
@@ -108,14 +108,14 @@ public class JcrNodeResourceTest extends
         Node node = rootNode.addNode(name, JcrConstants.NT_UNSTRUCTURED);
         getSession().save();
 
-        JcrNodeResource jnr = new JcrNodeResource(null, node.getPath(), node, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, node.getPath(), null, node, null, new PathMapperImpl());
         assertEquals(JcrConstants.NT_UNSTRUCTURED, jnr.getResourceType());
 
         String typeName = "some/resource/type";
         node.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY, typeName);
         getSession().save();
 
-        jnr = new JcrNodeResource(null, node.getPath(), node, null, new PathMapperImpl());
+        jnr = new JcrNodeResource(null, node.getPath(), null, node, null, new PathMapperImpl());
         assertEquals(typeName, jnr.getResourceType());
     }
 
@@ -127,7 +127,7 @@ public class JcrNodeResourceTest extends
         node.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY, typeName);
         getSession().save();
 
-        Resource jnr = new JcrNodeResource(null, node.getPath(), node, null, new PathMapperImpl());
+        Resource jnr = new JcrNodeResource(null, node.getPath(), null, node, null, new PathMapperImpl());
         assertEquals(typeName, jnr.getResourceType());
 
         // default super type is null
@@ -138,7 +138,7 @@ public class JcrNodeResourceTest extends
         typeNode.setProperty(JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY, superTypeName);
         getSession().save();
 
-        jnr = new JcrNodeResource(null, typeNode.getPath(), typeNode, null, new PathMapperImpl());
+        jnr = new JcrNodeResource(null, typeNode.getPath(), null, typeNode, null, new PathMapperImpl());
         assertEquals(JcrConstants.NT_UNSTRUCTURED, jnr.getResourceType());
         assertEquals(superTypeName, jnr.getResourceSuperType());
 
@@ -147,7 +147,7 @@ public class JcrNodeResourceTest extends
         node.setProperty(JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY, otherSuperTypeName);
         getSession().save();
 
-        jnr = new JcrNodeResource(null, node.getPath(), node, null, new PathMapperImpl());
+        jnr = new JcrNodeResource(null, node.getPath(), null, node, null, new PathMapperImpl());
         assertEquals(typeName, jnr.getResourceType());
         assertEquals(otherSuperTypeName, jnr.getResourceSuperType());
 
@@ -155,7 +155,7 @@ public class JcrNodeResourceTest extends
         node.getProperty(JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY).remove();
         getSession().save();
 
-        jnr = new JcrNodeResource(null, node.getPath(), node, null, new PathMapperImpl());
+        jnr = new JcrNodeResource(null, node.getPath(), null, node, null, new PathMapperImpl());
         assertEquals(typeName, jnr.getResourceType());
         assertNull(jnr.getResourceSuperType());
     }
@@ -168,7 +168,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         res = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), res, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, res.getPath(), null, res, null, new PathMapperImpl());
 
         final Map<?, ?> props = jnr.adaptTo(Map.class);
 
@@ -235,7 +235,7 @@ public class JcrNodeResourceTest extends
         getSession().save();
 
         file = rootNode.getNode(name);
-        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), file, null, new PathMapperImpl());
+        JcrNodeResource jnr = new JcrNodeResource(null, file.getPath(), null, file, null, new PathMapperImpl());
 
         assertEquals(utf8bytes, jnr.adaptTo(InputStream.class));
         assertEquals(utf8bytes.length, jnr.getResourceMetadata().getContentLength());

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResourceTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResourceTest.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResourceTest.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrPropertyResourceTest.java Mon Feb  9 16:00:52 2015
@@ -73,7 +73,7 @@ public class JcrPropertyResourceTest {
                 allowing(property).getType(); will(returnValue(data.getValue()));
                 allowing(property).getString(); will(returnValue(stringValue));
             }});
-            final JcrPropertyResource propResource = new JcrPropertyResource(resolver, "/path/to/string-property", property, new PathMapperImpl());
+            final JcrPropertyResource propResource = new JcrPropertyResource(resolver, "/path/to/string-property", null, property, new PathMapperImpl());
             assertEquals("Byte length of " +  stringValue, stringByteLength, propResource.getResourceMetadata().getContentLength());
         }
     }

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrTestNodeResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrTestNodeResource.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrTestNodeResource.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrTestNodeResource.java Mon Feb  9 16:00:52 2015
@@ -28,7 +28,7 @@ public class JcrTestNodeResource extends
 
     public JcrTestNodeResource(ResourceResolver resourceResolver, Node node,
             ClassLoader dynamicClassLoader) throws RepositoryException {
-        super(resourceResolver, node.getPath(), node, dynamicClassLoader, new PathMapperImpl());
+        super(resourceResolver, node.getPath(), null, node, dynamicClassLoader, new PathMapperImpl());
     }
 
 }

Modified: sling/trunk/bundles/resourceresolver/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/pom.xml?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/pom.xml (original)
+++ sling/trunk/bundles/resourceresolver/pom.xml Mon Feb  9 16:00:52 2015
@@ -110,7 +110,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.8.0</version>
+            <version>2.8.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java Mon Feb  9 16:00:52 2015
@@ -19,6 +19,7 @@
 package org.apache.sling.resourceresolver.impl;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -54,6 +55,7 @@ import org.apache.sling.resourceresolver
 import org.apache.sling.resourceresolver.impl.helper.URI;
 import org.apache.sling.resourceresolver.impl.helper.URIException;
 import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
+import org.apache.sling.resourceresolver.impl.tree.params.ParsedParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,6 +65,8 @@ public class ResourceResolverImpl extend
     /** Default logger */
     private final Logger logger = LoggerFactory.getLogger(ResourceResolverImpl.class);
 
+    private static final Map<String, String> EMPTY_PARAMETERS = Collections.emptyMap();
+    
     private static final String MANGLE_NAMESPACE_IN_SUFFIX = "_";
 
     private static final String MANGLE_NAMESPACE_IN_PREFIX = "/_";
@@ -311,8 +315,10 @@ public class ResourceResolverImpl extend
 
         Resource res = null;
         for (int i = 0; res == null && i < realPathList.length; i++) {
-            final String realPath = realPathList[i];
-
+            final ParsedParameters parsedPath = new ParsedParameters(realPathList[i]);
+            final String realPath = parsedPath.getRawPath();
+            
+            
             // first check whether the requested resource is a StarResource
             if (StarResource.appliesTo(realPath)) {
                 logger.debug("resolve: Mapped path {} is a Star Resource", realPath);
@@ -324,24 +330,28 @@ public class ResourceResolverImpl extend
 
                     // let's check it with a direct access first
                     logger.debug("resolve: Try absolute mapped path {}", realPath);
-                    res = resolveInternal(realPath);
+                    res = resolveInternal(realPath, parsedPath.getParameters());
 
                 } else {
 
                     final String[] searchPath = getSearchPath();
                     for (int spi = 0; res == null && spi < searchPath.length; spi++) {
                         logger.debug("resolve: Try relative mapped path with search path entry {}", searchPath[spi]);
-                        res = resolveInternal(searchPath[spi] + realPath);
+                        res = resolveInternal(searchPath[spi] + realPath, parsedPath.getParameters());
                     }
 
                 }
             }
+            if (res != null) {
+                res.getResourceMetadata().setParameterMap(parsedPath.getParameters());
+            }
 
         }
 
         // if no resource has been found, use a NonExistingResource
         if (res == null) {
-            final String resourcePath = ensureAbsPath(realPathList[0]);
+            final ParsedParameters parsedPath = new ParsedParameters(realPathList[0]);
+            final String resourcePath = ensureAbsPath(parsedPath.getRawPath());
             logger.debug("resolve: Path {} does not resolve, returning NonExistingResource at {}", absPath, resourcePath);
 
             res = new NonExistingResource(this, resourcePath);
@@ -354,6 +364,7 @@ public class ResourceResolverImpl extend
             if (index != -1) {
                 res.getResourceMetadata().setResolutionPathInfo(resourcePath.substring(index));
             }
+            res.getResourceMetadata().setParameterMap(parsedPath.getParameters());
         } else {
             logger.debug("resolve: Path {} resolves to Resource {}", absPath, res);
         }
@@ -418,7 +429,8 @@ public class ResourceResolverImpl extend
 
         }
 
-        final Resource res = resolveInternal(mappedPath);
+        ParsedParameters parsed = new ParsedParameters(mappedPath);
+        final Resource res = resolveInternal(parsed.getRawPath(), parsed.getParameters());
 
         if (res != null) {
 
@@ -602,21 +614,39 @@ public class ResourceResolverImpl extend
 
         String absolutePath = path;
         if (absolutePath != null && !absolutePath.startsWith("/") && base != null) {
-            absolutePath = base.getPath() + "/" + absolutePath;
+            absolutePath = appendToPath(base.getPath(), absolutePath);
         }
 
         final Resource result = getResourceInternal(absolutePath);
         return result;
     }
 
+    /**
+     * Methods concatenates two paths. If the first path contains parameters separated semicolon, they are
+     * moved at the end of the result.
+     * 
+     * @param pathWithParameters
+     * @param segmentToAppend
+     * @return
+     */
+    private static String appendToPath(final String pathWithParameters, final String segmentToAppend) {
+        final ParsedParameters parsed = new ParsedParameters(pathWithParameters);
+        if (parsed.getParametersString() == null) {
+            return String.format("%s/%s", parsed.getRawPath(), segmentToAppend);
+        } else {
+            return String.format("%s/%s%s", parsed.getRawPath(), segmentToAppend, parsed.getParametersString());
+        }
+    }
+
     private Resource getResourceInternal(String path) {
 
         Resource result = null;
         if ( path != null ) {
             // if the path is absolute, normalize . and .. segments and get res
             if (path.startsWith("/")) {
-                path = ResourceUtil.normalize(path);
-                result = (path != null) ? getAbsoluteResourceInternal(path, false) : null;
+                ParsedParameters parsedPath = new ParsedParameters(path);
+                path = ResourceUtil.normalize(parsedPath.getRawPath());
+                result = (path != null) ? getAbsoluteResourceInternal(path, parsedPath.getParameters(), false) : null;
                 if (result != null) {
                     result = this.factory.getResourceDecoratorTracker().decorate(result);
                 }
@@ -817,14 +847,14 @@ public class ResourceResolverImpl extend
      *         the part of the <code>absPath</code> which has been cut off by
      *         the {@link ResourcePathIterator} to resolve the resource.
      */
-    private Resource resolveInternal(final String absPath) {
+    private Resource resolveInternal(final String absPath, final Map<String, String> parameters) {
         Resource resource = null;
         String curPath = absPath;
         try {
             final ResourcePathIterator it = new ResourcePathIterator(absPath);
             while (it.hasNext() && resource == null) {
                 curPath = it.next();
-                resource = getAbsoluteResourceInternal(curPath, true);
+                resource = getAbsoluteResourceInternal(curPath, parameters, true);
             }
         } catch (final Exception ex) {
             throw new SlingException("Problem trying " + curPath + " for request path " + absPath, ex);
@@ -845,7 +875,7 @@ public class ResourceResolverImpl extend
 
             // no direct resource found, so we have to drill down into the
             // resource tree to find a match
-            resource = getAbsoluteResourceInternal("/", true);
+            resource = getAbsoluteResourceInternal("/", parameters, true);
             final StringBuilder resolutionPath = new StringBuilder();
             final StringTokenizer tokener = new StringTokenizer(absPath, "/");
             while (resource != null && tokener.hasMoreTokens()) {
@@ -904,7 +934,7 @@ public class ResourceResolverImpl extend
         } else {
             path = parent.getPath() + '/' + childName;
         }
-        Resource child = getAbsoluteResourceInternal( ResourceUtil.normalize(path), true );
+        Resource child = getAbsoluteResourceInternal( ResourceUtil.normalize(path), EMPTY_PARAMETERS, true );
         if (child != null) {
             final String alias = ResourceResolverContext.getProperty(child, PROP_REDIRECT_INTERNAL);
             if (alias != null) {
@@ -932,7 +962,7 @@ public class ResourceResolverImpl extend
                     } else {
                         aliasPath = parent.getPath() + '/' + aliasName;
                     }
-                    final Resource aliasedChild = getAbsoluteResourceInternal( ResourceUtil.normalize(aliasPath), true );
+                    final Resource aliasedChild = getAbsoluteResourceInternal( ResourceUtil.normalize(aliasPath), EMPTY_PARAMETERS, true );
                     logger.debug("getChildInternal: Found Resource {} with alias {} to use", aliasedChild, childName);
                     return aliasedChild;
                 }
@@ -948,7 +978,7 @@ public class ResourceResolverImpl extend
                         for (final String alias : aliases) {
                             if (childName.equals(alias)) {
                                 logger.debug("getChildInternal: Found Resource {} with alias {} to use", child, childName);
-                                final Resource aliasedChild = getAbsoluteResourceInternal( ResourceUtil.normalize(child.getPath()) , true);
+                                final Resource aliasedChild = getAbsoluteResourceInternal( ResourceUtil.normalize(child.getPath()) , EMPTY_PARAMETERS, true);
                                 return aliasedChild;
                             }
                         }
@@ -965,9 +995,9 @@ public class ResourceResolverImpl extend
     /**
      * Creates a resource with the given path if existing
      */
-    private Resource getAbsoluteResourceInternal(final String path, final boolean isResolve) {
+    private Resource getAbsoluteResourceInternal(final String path, final Map<String, String> parameters, final boolean isResolve) {
 
-        final Resource resource = this.factory.getRootProviderEntry().getResource(this.context, this, path ,isResolve);
+        final Resource resource = this.factory.getRootProviderEntry().getResource(this.context, this, path, parameters, isResolve);
         if (resource != null) {
             resource.getResourceMetadata().setResolutionPath(path);
             return resource;

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceIterator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceIterator.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceIterator.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceIterator.java Mon Feb  9 16:00:52 2015
@@ -19,6 +19,7 @@
 package org.apache.sling.resourceresolver.impl.helper;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -218,7 +219,7 @@ public class ResourceIterator implements
                     if (!visited.contains(resPath)) {
                         final ResourceResolver rr = parentResource.getResourceResolver();
                         final Resource res = rpw.getResourceFromProviders(this.resourceResolverContext, rr,
-                                resPath);
+                                resPath, Collections.<String,String>emptyMap());
                         if (res == null) {
                             if (!delayed.containsKey(resPath)) {
                                 delayed.put(

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java Mon Feb  9 16:00:52 2015
@@ -341,7 +341,7 @@ public abstract class ProviderHandler im
     /**
      * @see ResourceProvider#getResource(ResourceResolver, String)
      */
-    public abstract Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path);
+    public abstract Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path, final Map<String, String> parameters);
 
     /**
      * @see ResourceProvider#listChildren(Resource)

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntry.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntry.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntry.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntry.java Mon Feb  9 16:00:52 2015
@@ -37,6 +37,7 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.SyntheticResource;
 import org.apache.sling.resourceresolver.impl.helper.ResourceResolverContext;
+import org.apache.sling.resourceresolver.impl.tree.params.ParsedParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -123,6 +124,7 @@ public class ResourceProviderEntry imple
      *
      * @param path
      *            The path to the resource to return.
+     * @param parameters 
      * @return The resource for the path or <code>null</code> if no resource can
      *         be found.
      * @throws org.apache.sling.api.SlingException
@@ -131,8 +133,9 @@ public class ResourceProviderEntry imple
     public Resource getResource(final ResourceResolverContext ctx,
             final ResourceResolver resourceResolver,
             final String path,
+            final Map<String, String> parameters,
             final boolean isResolve) {
-        return getInternalResource(ctx, resourceResolver, path, isResolve);
+        return getInternalResource(ctx, resourceResolver, path, parameters, isResolve);
     }
 
     // ------------------ Map methods, here so that we can delegate 2 maps
@@ -299,11 +302,13 @@ public class ResourceProviderEntry imple
      * @param ctx The resource resolver context
      * @param resourceResolver the ResourceResolver.
      * @param fullPath the Full path
+     * @param parameters 
      * @return null if no resource was found, a resource if one was found.
      */
     private Resource getInternalResource(final ResourceResolverContext ctx,
             final ResourceResolver resourceResolver,
             final String fullPath,
+            final Map<String, String> parameters,
             final boolean isResolve) {
         try {
 
@@ -323,7 +328,7 @@ public class ResourceProviderEntry imple
                 for (final ProviderHandler rp : rps) {
 
                     boolean foundFallback = false;
-                    final Resource resource = rp.getResource(ctx, resourceResolver, fullPath);
+                    final Resource resource = rp.getResource(ctx, resourceResolver, fullPath, parameters);
                     if (resource != null) {
                         if ( resource.getResourceMetadata() != null && resource.getResourceMetadata().get(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING) != null ) {
                             if ( logger.isDebugEnabled() ) {
@@ -347,7 +352,7 @@ public class ResourceProviderEntry imple
             }
 
             // resolve against this one
-            final Resource resource = getResourceFromProviders(ctx, resourceResolver, fullPath);
+            final Resource resource = getResourceFromProviders(ctx, resourceResolver, fullPath, parameters);
             if (resource != null) {
                 return resource;
             }
@@ -383,13 +388,14 @@ public class ResourceProviderEntry imple
 
     public Resource getResourceFromProviders(final ResourceResolverContext ctx,
             final ResourceResolver resourceResolver,
-            final String fullPath) {
+            final String fullPath,
+            final Map<String, String> parameters) {
         Resource fallbackResource = null;
         final ProviderHandler[] rps = getResourceProviders();
         for (final ProviderHandler rp : rps) {
             boolean foundFallback = false;
 
-            final Resource resource = rp.getResource(ctx, resourceResolver, fullPath);
+            final Resource resource = rp.getResource(ctx, resourceResolver, fullPath, parameters);
             if (resource != null) {
                 if ( resource.getResourceMetadata() != null && resource.getResourceMetadata().get(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING) != null ) {
                     logger.debug("Resolved Base {} using {} - continue resolving flag is set!", fullPath, rp);

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderFactoryHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderFactoryHandler.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderFactoryHandler.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderFactoryHandler.java Mon Feb  9 16:00:52 2015
@@ -22,7 +22,9 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.commons.collections.MapUtils;
 import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ParametrizableResourceProvider;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceProviderFactory;
@@ -101,10 +103,16 @@ public class ResourceProviderFactoryHand
     /**
      * @see ResourceProvider#getResource(ResourceResolver, String)
      */
-    public Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path) {
+    public Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path, final Map<String, String> parameters) {
         final ResourceProvider rp = this.getResourceProvider(ctx);
         if ( rp != null ) {
-            return getReadableResource(ctx, rp.getResource(resourceResolver, path) );
+            final Resource resource;
+            if (MapUtils.isNotEmpty(parameters) && rp instanceof ParametrizableResourceProvider) {
+                resource = ((ParametrizableResourceProvider) rp).getResource(resourceResolver, path, parameters);
+            } else {
+                resource = rp.getResource(resourceResolver, path);
+            }
+            return getReadableResource(ctx, resource);
         }
         return null;
     }

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderHandler.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderHandler.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderHandler.java Mon Feb  9 16:00:52 2015
@@ -20,6 +20,8 @@ package org.apache.sling.resourceresolve
 import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.commons.collections.MapUtils;
+import org.apache.sling.api.resource.ParametrizableResourceProvider;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -46,8 +48,14 @@ public class ResourceProviderHandler ext
     /**
      * @see ResourceProvider#getResource(ResourceResolver, String)
      */
-    public Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path) {
-        return getReadableResource(ctx, this.resourceProvider.getResource(resourceResolver, path) );
+    public Resource getResource(final ResourceResolverContext ctx, final ResourceResolver resourceResolver, final String path, final Map<String, String> parameters) {
+        final Resource resource;
+        if (MapUtils.isNotEmpty(parameters) && this.resourceProvider instanceof ParametrizableResourceProvider) {
+            resource = ((ParametrizableResourceProvider)this.resourceProvider).getResource(resourceResolver, path, parameters);
+        } else {
+            resource = this.resourceProvider.getResource(resourceResolver, path);
+        }
+        return getReadableResource(ctx, resource);
     }
 
     /**

Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParametersParser.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParametersParser.java?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParametersParser.java (added)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParametersParser.java Mon Feb  9 16:00:52 2015
@@ -0,0 +1,159 @@
+/*
+ * 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.resourceresolver.impl.tree.params;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+class ParametersParser {
+
+    private enum ParamsState {
+        INIT, NAME, EQUALS, VALUE, QUOTED_VALUE, QUOTE_END
+    }
+
+    private StringBuilder name;
+
+    private StringBuilder value;
+
+    private Map<String, String> parameters = new LinkedHashMap<String, String>();
+
+    private boolean invalid;
+
+    /**
+     * Parses parameters string, eg.: {@code ;x=123;a='1.0'}. The result of the method is available in
+     * {@link #parameters} and {@link #invalid}.
+     * 
+     * @param chars Array containing path with parameters.
+     * @param from Index of the first character of the parameters substring (it must be a semicolon).
+     * @param dotAllowed If true, the dot in parameter value won't stop parsing.
+     * @return Index of the first character not related to parameters.
+     */
+    public int parseParameters(final char[] chars, final int from, final boolean dotAllowed) {
+        resetCurrentParameter();
+        parameters.clear();
+        invalid = false;
+
+        ParamsState state = ParamsState.INIT;
+        for (int i = from; i <= chars.length; i++) {
+            final char c;
+            if (i == chars.length) {
+                c = 0;
+            } else {
+                c = chars[i];
+            }
+            switch (state) {
+                case INIT:
+                    if (c == ';') {
+                        state = ParamsState.NAME;
+                    } else if (c == '.' || c == '/' || c == 0) {
+                        invalid = true;
+                        return i;
+                    }
+                    break;
+
+                case NAME:
+                    if (c == '=') {
+                        state = ParamsState.EQUALS;
+                    } else if (c == '.' || c == '/' || c == 0) {
+                        invalid = true;
+                        return i;
+                    } else if (c == ';') {
+                        resetCurrentParameter();
+                    } else {
+                        name.append(c);
+                    }
+                    break;
+
+                case EQUALS:
+                    if (c == '\'') {
+                        state = ParamsState.QUOTED_VALUE;
+                    } else if (c == '.' || c == '/' || c == 0) {
+                        addParameter(); // empty one
+                        return i;
+                    } else if (c == ';') {
+                        state = ParamsState.NAME; // empty one
+                        addParameter();
+                    } else {
+                        state = ParamsState.VALUE;
+                        value.append(c);
+                    }
+                    break;
+
+                case QUOTED_VALUE:
+                    if (c == '\'') {
+                        state = ParamsState.QUOTE_END;
+                        addParameter();
+                    } else if (c == 0) {
+                        invalid = true;
+                        return i;
+                    } else {
+                        value.append(c);
+                    }
+                    break;
+
+                case VALUE:
+                    if (c == ';') {
+                        state = ParamsState.NAME;
+                        addParameter();
+                    } else if ((c == '.' && !dotAllowed) || c == '/' || c == 0) {
+                        addParameter();
+                        return i;
+                    } else {
+                        value.append(c);
+                    }
+                    break;
+
+                case QUOTE_END:
+                    if (c == ';') {
+                        state = ParamsState.NAME;
+                    } else {
+                        return i;
+                    }
+            }
+        }
+
+        return chars.length;
+    }
+
+    /**
+     * @return Parsed parameters.
+     */
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    /**
+     * @return True if the {@link #parseParameters(char[], int, boolean)} method failed.
+     */
+    public boolean isInvalid() {
+        return invalid;
+    }
+
+    private void resetCurrentParameter() {
+        name = new StringBuilder();
+        value = new StringBuilder();
+    }
+
+    private void addParameter() {
+        parameters.put(name.toString(), value.toString());
+        name = new StringBuilder();
+        value = new StringBuilder();
+    }
+}

Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParsedParameters.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParsedParameters.java?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParsedParameters.java (added)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/ParsedParameters.java Mon Feb  9 16:00:52 2015
@@ -0,0 +1,77 @@
+/*
+ * 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 SF 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.resourceresolver.impl.tree.params;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Parses path looking for semicolon-separated parameters. Parameters are extracted and exposed as an
+ * immutable map. The path without parameters is available as raw path.
+ * 
+ * Parameters should be added immedietaly before or after selectors and extension:
+ * {@code /content/test;v='1.0'.html} or {@code /content/test.html;v=1.0}. Quotes can be used to escape the
+ * parameter value (it is necessary if the value contains dot and parameter is added before extension).
+ * 
+ * @author Tomasz Rekawek
+ */
+public class ParsedParameters {
+
+    private final Map<String, String> parameters;
+
+    private final String parametersString;
+
+    private final String path;
+
+    /**
+     * Parse path and create parameters object.
+     * 
+     * @param fullPath Path to parse.
+     */
+    public ParsedParameters(final String fullPath) {
+        final PathParser parser = new PathParser();
+        parser.parse(fullPath);
+
+        parametersString = parser.getParametersString();
+        parameters = Collections.unmodifiableMap(parser.getParameters());
+        path = parser.getPath();
+    }
+
+    /**
+     * @return Path with no parameters.
+     */
+    public String getRawPath() {
+        return path;
+    }
+    
+    /**
+     * @return Path's substring containing parameters
+     */
+    public String getParametersString() {
+        return parametersString;
+    }
+
+    /**
+     * @return Map of the parameters.
+     */
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+}

Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/PathParser.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/PathParser.java?rev=1658445&view=auto
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/PathParser.java (added)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/params/PathParser.java Mon Feb  9 16:00:52 2015
@@ -0,0 +1,163 @@
+/*
+ * 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.resourceresolver.impl.tree.params;
+
+import java.util.Collections;
+import java.util.Map;
+
+class PathParser {
+
+    /**
+     * List of states. V1 and V2 prefixes means variant 1 and 2. In V1, the parameters are added after
+     * selectors and extension: {@code /content/test.sel.html;v=1.0}. In V2 parameters are added before
+     * selectors and extension: {@code /content/test;v='1.0'.sel.html}
+     */
+    private enum ParserState {
+        INIT, V1_EXTENSION, V1_PARAMS, V2_PARAMS, V2_EXTENSION, SUFFIX, INVALID
+    }
+
+    private String rawPath;
+
+    private String parametersString;
+
+    private Map<String, String> parameters;
+
+    /**
+     * @return Path with no parameters.
+     */
+    public String getPath() {
+        return rawPath;
+    }
+
+    /**
+     * @return Path's substring containing parameters
+     */
+    public String getParametersString() {
+        return parametersString;
+    }
+
+    /**
+     * @return Parsed parameters.
+     */
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    /**
+     * Parses path containing parameters. Results will be available in {@link #rawPath} and {@link parameters}.
+     * 
+     * @param path
+     */
+    public void parse(String path) {
+        this.rawPath = path;
+        this.parameters = Collections.emptyMap();
+
+        if (path == null) {
+            return;
+        }
+
+        final char[] chars = path.toCharArray();
+        final ParametersParser parametersParser = new ParametersParser();
+
+        ParserState state = ParserState.INIT;
+        int paramsStart = -1, paramsEnd = -1;
+
+        for (int i = 0; i <= chars.length; i++) {
+            final char c;
+            if (i == chars.length) {
+                c = 0;
+            } else {
+                c = chars[i];
+            }
+
+            switch (state) {
+                case INIT:
+                    if (c == '.') {
+                        state = ParserState.V1_EXTENSION;
+                    } else if (c == ';') {
+                        paramsStart = i;
+                        i = parametersParser.parseParameters(chars, i, false);
+                        paramsEnd = i--;
+                        state = parametersParser.isInvalid() ? ParserState.INVALID : ParserState.V2_PARAMS;
+                    }
+                    break;
+
+                case V1_EXTENSION:
+                    if (c == '/') {
+                        state = ParserState.SUFFIX;
+                    } else if (c == ';') {
+                        paramsStart = i;
+                        i = parametersParser.parseParameters(chars, i, true);
+                        paramsEnd = i--;
+                        state = parametersParser.isInvalid() ? ParserState.INVALID : ParserState.V1_PARAMS;
+                    }
+                    break;
+
+                case V1_PARAMS:
+                    if (c == '/') {
+                        state = ParserState.SUFFIX;
+                    } else if (c == '.') {
+                        state = ParserState.INVALID; // no dots after params
+                    }
+                    break;
+
+                case V2_PARAMS:
+                    if (c == '/') {
+                        state = ParserState.INVALID; // there was no extension, so no suffix is allowed
+                    } else if (c == '.') {
+                        state = ParserState.V2_EXTENSION;
+                    }
+                    break;
+
+                case V2_EXTENSION:
+                    if (c == '/') {
+                        state = ParserState.SUFFIX;
+                    }
+                    break;
+
+                case SUFFIX:
+                case INVALID:
+                    break;
+            }
+        }
+
+        if (state == ParserState.INVALID) {
+            paramsStart = paramsEnd = -1;
+        } else {
+            cutPath(path, paramsStart, paramsEnd);
+            parameters = parametersParser.getParameters();
+        }
+    }
+
+    private void cutPath(String path, int from, int to) {
+        if (from == -1) {
+            rawPath = path;
+            parametersString = null;
+        } else if (to == -1) {
+            rawPath = path.substring(0, from);
+            parametersString = path.substring(from);
+        } else {
+            rawPath = path.substring(0, from) + path.substring(to);
+            parametersString = path.substring(from, to);
+        }
+    }
+
+
+}

Modified: sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java?rev=1658445&r1=1658444&r2=1658445&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java (original)
+++ sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java Mon Feb  9 16:00:52 2015
@@ -17,9 +17,12 @@
  */
 package org.apache.sling.resourceresolver.impl;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -593,13 +596,13 @@ public class MockedResourceResolverImplT
         }
         Assert.assertEquals(5,i);
     }
-    
+
     @Test
     public void testQueryResources() throws LoginException {
         final int n = 3;
         Mockito.when(queriableResourceProviderA.queryResources(Mockito.any(ResourceResolver.class), Mockito.any(String.class), Mockito.any(String.class)))
         .thenReturn(buildValueMapCollection(n, "A_").iterator());
-        
+
         final ResourceResolver rr = resourceResolverFactory.getResourceResolver(null);
         buildResource("/search/test/withchildren", buildChildResources("/search/test/withchildren"), rr, resourceProvider);
         final Iterator<Map<String, Object>> it = rr.queryResources("/search", FAKE_QUERY_LANGUAGE);
@@ -607,7 +610,7 @@ public class MockedResourceResolverImplT
         for(int i=0; i < n; i++) {
             toFind.add("A_" + i);
         }
-        
+
         assertTrue("Expecting non-empty result (" + n + ")", it.hasNext());
         while(it.hasNext()) {
             final Map<String, Object> m = it.next();
@@ -616,4 +619,25 @@ public class MockedResourceResolverImplT
         assertTrue("Expecting no leftovers (" + n + ") in" + toFind, toFind.isEmpty());
     }
 
+    @Test public void test_versions() throws LoginException {
+        ResourceResolver resourceResolver = resourceResolverFactory.getResourceResolver(null);
+
+        Resource resource = resourceResolver.resolve("/content/test.html;v=1.0");
+        Map<String, String> parameters = resource.getResourceMetadata().getParameterMap();
+        assertEquals("/content/test.html", resource.getPath());
+        assertEquals("test.html", resource.getName());
+        assertEquals(Collections.singletonMap("v", "1.0"), parameters);
+
+        resource = resourceResolver.resolve("/content/test;v='1.0'.html");
+        parameters = resource.getResourceMetadata().getParameterMap();
+        assertEquals("/content/test.html", resource.getPath());
+        assertEquals("test.html", resource.getName());
+        assertEquals(Collections.singletonMap("v", "1.0"), parameters);
+
+        buildResource("/single/test/withchildren", buildChildResources("/single/test/withchildren"), resourceResolver, resourceProvider);
+        resource = resourceResolver.getResource("/single/test/withchildren;v='1.0'");
+        assertNotNull(resource);
+        assertEquals("/single/test/withchildren", resource.getPath());
+        assertEquals("withchildren", resource.getName());
+    }
 }