SLING-7975 support ordering of resources (#18)

diff --git a/pom.xml b/pom.xml
index ef4c393..feebd5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -145,7 +145,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.18.2</version>
+            <version>2.23.7-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
index e40afb6..163ccba 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
@@ -34,6 +34,7 @@
 import org.jetbrains.annotations.NotNull;
 import javax.jcr.Item;
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
@@ -479,6 +480,44 @@
     }
 
     @Override
+    public boolean orderBefore(@NotNull ResolveContext<JcrProviderState> ctx, @NotNull Resource parent, @NotNull String name,
+            @Nullable String followingSiblingName) throws PersistenceException {
+        Node node = parent.adaptTo(Node.class);
+        if (node == null) {
+            throw new PersistenceException("The resource " + parent.getPath() + " cannot be adapted to Node. It is probably not provided by the JcrResourceProvider");
+        }
+        try {
+            // check if reordering necessary
+            NodeIterator nodeIterator = node.getNodes();
+            long existingNodePosition = -1;
+            long index = 0;
+            while (nodeIterator.hasNext()) {
+                Node childNode = nodeIterator.nextNode();
+                if (childNode.getName().equals(name)) {
+                    existingNodePosition = index;
+                }
+                if (existingNodePosition >= 0) {
+                    // is existing resource already at the desired position?
+                    if (childNode.getName().equals(followingSiblingName)) {
+                        if (existingNodePosition == index-1) {
+                            return false;
+                        }
+                    }
+                    // is the existing node already the last one in the list?
+                    else if (followingSiblingName == null && existingNodePosition == nodeIterator.getSize()-1) {
+                        return false;
+                    }
+                }
+                index++;
+            }
+            node.orderBefore(name, followingSiblingName);
+            return true;
+        } catch (final RepositoryException e) {
+            throw new PersistenceException("Unable to reorder children below " + parent.getPath(), e, parent.getPath(), null);
+        }
+    }
+
+    @Override
     public void delete(final @NotNull ResolveContext<JcrProviderState> ctx, final @NotNull Resource resource)
     throws PersistenceException {
         // try to adapt to Item
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
index b7641bc..684ce80 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
@@ -20,10 +20,17 @@
 
 import java.security.Principal;
 
+import javax.jcr.Node;
 import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
 
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.junit.Assert;
 import org.mockito.Mockito;
 import org.osgi.framework.ServiceReference;
@@ -57,6 +64,42 @@
         Mockito.when(ctx.getProviderState()).thenReturn(new JcrProviderState(session, null, false));
         Assert.assertNotNull(jcrResourceProvider.adaptTo(ctx, Principal.class));
     }
+
+    public void testOrderBefore() throws RepositoryException, PersistenceException {
+        Node parentNode = session.getRootNode().addNode("parent", NodeType.NT_UNSTRUCTURED);
+        parentNode.addNode("child1", NodeType.NT_UNSTRUCTURED);
+        parentNode.addNode("child2", NodeType.NT_UNSTRUCTURED);
+        parentNode.addNode("child3", NodeType.NT_UNSTRUCTURED);
+        session.save();
+
+        ResolveContext ctx = Mockito.mock(ResolveContext.class);
+        JcrProviderState state = new JcrProviderState(session, null, false);
+        Mockito.when(ctx.getProviderState()).thenReturn(state);
+        Resource parent = jcrResourceProvider.getResource(ctx, "/parent", ResourceContext.EMPTY_CONTEXT, null);
+        Assert.assertNotNull(parent);
+        // order with invalid names
+        try {
+            jcrResourceProvider.orderBefore(ctx, parent, "child3", "child4");
+        } catch (PersistenceException e) {
+            // expected
+        }
+        // order successfully
+        Assert.assertTrue(jcrResourceProvider.orderBefore(ctx, parent, "child2", "child1"));
+        
+        // order already established
+        Assert.assertFalse(jcrResourceProvider.orderBefore(ctx, parent, "child2", "child1"));
+        
+        // order child2 at end
+        Assert.assertTrue(jcrResourceProvider.orderBefore(ctx, parent, "child2", null));
+        
+        // order child2 at end again
+        Assert.assertFalse(jcrResourceProvider.orderBefore(ctx, parent, "child2", null));
+        
+        // make sure nothing is persisted until save
+        jcrResourceProvider.revert(ctx);
+        
+        Assert.assertTrue(jcrResourceProvider.orderBefore(ctx, parent, "child2", null));
+    }
 }