You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by md...@apache.org on 2010/11/09 18:57:11 UTC

svn commit: r1033124 - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/ jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/...

Author: mduerig
Date: Tue Nov  9 17:57:11 2010
New Revision: 1033124

URL: http://svn.apache.org/viewvc?rev=1033124&view=rev
Log:
JCR-2800: Implement search facility for users and groups
implemented limits, bounds and offsets
work in progress

Modified:
    jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java

Modified: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java?rev=1033124&r1=1033123&r2=1033124&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java Tue Nov  9 17:57:11 2010
@@ -25,18 +25,24 @@ public interface QueryBuilder<T> {
      * The sort order of the result set of a query.
      */
     enum Direction {
-        ASCENDING("ascending"),
-        DESCENDING("descending");
+        ASCENDING("ascending", RelationOp.GT),
+        DESCENDING("descending", RelationOp.LT);
 
         private final String direction;
+        private RelationOp relOp;
 
-        Direction(String direction) {
+        Direction(String direction, RelationOp relOp) {
             this.direction = direction;
+            this.relOp = relOp;
         }
 
         public String getDirection() {
             return direction;
         }
+
+        public RelationOp getRelOp() {
+            return relOp;
+        }
     }
 
     /**
@@ -120,21 +126,31 @@ public interface QueryBuilder<T> {
     void setSortOrder(String propertyName, Direction direction);
 
     /**
-     * Set the limit of the query. A limit consists of an offset, a maximal number of results
-     * to include and a direction. The offset refers to the value of the sort order property.
-     * If <code>forward</code> is <code>true</code> (<code>false</code>) the query returns
-     * at most the <code>maxCount</code> {@link Authorizable}s whose value of the sort order
-     * property follows (precedes)the specified <code>offset</code> in the given sort
-     * {@link Direction direction}. This method has no effect if the
-     * {@link #setSortOrder(String, Direction) sort order} is not also specified.
-     * @see #setSortOrder(String, Direction)
-     *
-     * @param offset  Offset from where to start returning results. <code>null</code> for no offset
-     * @param maxCount  Maximal number of results to return. -1 for all.
-     * @param forward  If <code>true</code> return the following <code>count</code> records.
-     * Otherwise return the preceding <code>count</code> records wrt. <code>offset</code>.
+     * Set limits for the query. The limits consists of a bound and a maximal
+     * number of results. The bound refers to the value of the
+     * {@link #setSortOrder(String, Direction) sort order} property. The
+     * query returns at most <code>maxCount</code> {@link Authorizable}s whose
+     * values of the sort order property follow <code>bound</code> in the sort
+     * direction. This method has no effect if the sort order is not specified.
+     *
+     * @param bound  Bound from where to start returning results. <code>null</code>
+     * for no bound
+     * @param maxCount  Maximal number of results to return. -1 for no limit.
+     */
+    void setLimit(Value bound, long maxCount);
+
+    /**
+     * Set limits for the query. The limits consists of an offset and a maximal
+     * number of results. <code>offset</code> refers to the offset within the full
+     * result set at which the returned result set should start expressed in terms 
+     * of the number of {@link Authorizable}s to skip. <code>limit</code> sets the
+     * maximum size of the result set expressed in terms of the number of authorizables
+     * to return.
+     *
+     * @param offset  Offset from where to start returning results. <code>0</code> for no offset.
+     * @param maxCount  Maximal number of results to return. -1 for no limit.
      */
-    void setLimit(String offset, int maxCount, boolean forward);
+    void setLimit(long offset, long maxCount);
 
     /**
      * Create a condition which holds iff the node of an {@link Authorizable} has a

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java?rev=1033124&r1=1033123&r2=1033124&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java Tue Nov  9 17:57:11 2010
@@ -46,9 +46,9 @@ public class XPathQueryBuilder implement
     private Condition condition;
     private String sortProperty;
     private Direction sortDirection = Direction.ASCENDING;
-    private String offset;
-    private int maxCount = -1;
-    private boolean forward = true;
+    private Value bound;
+    private long offset;
+    private long maxCount = -1;
 
     Selector getSelector() {
         return selector;
@@ -74,16 +74,16 @@ public class XPathQueryBuilder implement
         return sortDirection;
     }
 
-    String getOffset() {
-        return offset;
+    Value getBound() {
+        return bound;
     }
 
-    int getMaxCount() {
-        return maxCount;
+    long getOffset() {
+        return offset;
     }
 
-    boolean isForward() {
-        return forward;
+    long getMaxCount() {
+        return maxCount;
     }
     
     //------------------------------------------< QueryBuilder >---
@@ -106,11 +106,16 @@ public class XPathQueryBuilder implement
         sortDirection = direction;
     }
 
-    public void setLimit(String offset, int maxCount, boolean forward) {
+    public void setLimit(Value bound, long maxCount) {
+        offset = 0;   // Unset any previously set offset
+        this.bound = bound;
+        this.maxCount = maxCount;
+    }
+
+    public void setLimit(long offset, long maxCount) {
+        bound = null; // Unset any previously set bound
         this.offset = offset;
         this.maxCount = maxCount;
-        this.forward = true;
-        throw new UnsupportedOperationException("limit is not yet supported");     // todo implement: limit
     }
 
     public Condition property(String relPath, RelationOp op, Value value) {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java?rev=1033124&r1=1033123&r2=1033124&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java Tue Nov  9 17:57:11 2010
@@ -19,6 +19,8 @@ package org.apache.jackrabbit.core.secur
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.core.security.user.XPathQueryBuilder.Condition;
+import org.apache.jackrabbit.api.security.user.QueryBuilder.Direction;
 import org.apache.jackrabbit.api.security.user.QueryBuilder.RelationOp;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.spi.commons.iterator.Iterators;
@@ -59,29 +61,53 @@ public class XPathQueryEvaluator impleme
              .append(builder.getSelector().getNtName())
              .append(')');
 
-        XPathQueryBuilder.Condition condition = builder.getCondition();
+        Value bound = builder.getBound();
+        long offset = builder.getOffset();
+        if (bound != null && offset > 0) {
+            log.warn("Found bound {} and offset {} in limit. Discarding offset.", bound, offset);
+            offset = 0;
+        }
+
+        Condition condition = builder.getCondition();
+        String sortCol = builder.getSortProperty();
+        Direction sortDir = builder.getSortDirection();
+        if (bound != null) {
+            if (sortCol == null) {
+                log.warn("Ignoring bound {} since no sort order is specified");
+            }
+            else {
+                Condition boundCondition = builder.property(sortCol, sortDir.getRelOp(), bound);
+                condition = condition == null 
+                        ? boundCondition
+                        : builder.and(condition, boundCondition);
+            }
+        }
+
         if (condition != null) {
             xPath.append('[');
             condition.accept(this);
             xPath.append(']');
         }
 
-        String sortCol = builder.getSortProperty();
         if (sortCol != null) {
             xPath.append(" order by ")
                  .append(sortCol)
                  .append(' ')
-                 .append(builder.getSortDirection().getDirection());
+                 .append(sortDir.getDirection());
         }
 
         Query query = queryManager.createQuery(xPath.toString(), Query.XPATH);
-        int count = builder.getMaxCount();
-        if (count == 0) {
+        long maxCount = builder.getMaxCount();
+        if (maxCount == 0) {
             return Iterators.empty();
         }
 
-        if (count > 0) {
-            query.setLimit(count);
+        if (maxCount > 0) {
+            query.setLimit(maxCount);
+        }
+
+        if (offset > 0) {
+            query.setOffset(offset);
         }
 
         return filter(toAuthorizables(execute(query)), builder.getGroupName(), builder.isDeclaredMembersOnly());
@@ -123,7 +149,7 @@ public class XPathQueryEvaluator impleme
 
     public void visit(XPathQueryBuilder.AndCondition condition) throws RepositoryException {
         int count = 0;
-        for (XPathQueryBuilder.Condition c : condition) {
+        for (Condition c : condition) {
             xPath.append(count++ > 0 ? " and " : "");
             c.accept(this);
         }
@@ -133,7 +159,7 @@ public class XPathQueryEvaluator impleme
         int pos = xPath.length();
 
         int count = 0;
-        for (XPathQueryBuilder.Condition c : condition) {
+        for (Condition c : condition) {
             xPath.append(count++ > 0 ? " or " : "");
             c.accept(this);
         }

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java?rev=1033124&r1=1033123&r2=1033124&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java Tue Nov  9 17:57:11 2010
@@ -17,17 +17,15 @@
 
 package org.apache.jackrabbit.api.security.user;
 
+import org.apache.jackrabbit.api.security.user.QueryBuilder.Direction;
 import org.apache.jackrabbit.api.security.user.QueryBuilder.RelationOp;
 import org.apache.jackrabbit.api.security.user.QueryBuilder.Selector;
-import org.apache.jackrabbit.api.security.user.QueryBuilder.Direction;
 import org.apache.jackrabbit.spi.commons.iterator.Iterators;
 import org.apache.jackrabbit.spi.commons.iterator.Predicate;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
+import java.util.*;
 
 public class UserManagerSearchTest extends AbstractUserTest {
 
@@ -112,7 +110,7 @@ public class UserManagerSearchTest exten
         jackrabbit = createUser("jackrabbit", "carrots", 2500, true);
         deer = createUser("deer", "leaves", 120000, true);
         opposum = createUser("opposum", "fruit", 1200, true);
-        kangaroo = createUser("kangaroo", "grass", 80000, true);
+        kangaroo = createUser("kangaroo", "grass", 90000, true);
         elephant = createUser("elephant", "leaves", 5000000, true);
         addMembers(mammals, jackrabbit, deer, opposum, kangaroo, elephant);
 
@@ -122,11 +120,11 @@ public class UserManagerSearchTest exten
 
         crocodile = createUser("crocodile", "meat", 80000, false);
         turtle = createUser("turtle", "leaves", 10000, true);
-        lizard = createUser("lizard", "leaves", 2000, false);
+        lizard = createUser("lizard", "leaves", 1900, false);
         addMembers(reptiles, crocodile, turtle, lizard);
 
         kestrel = createUser("kestrel", "mice", 2000, false);
-        goose = createUser("goose", "snails", 10000, true);
+        goose = createUser("goose", "snails", 13000, true);
         pelican = createUser("pelican", "fish", 15000, true);
         dove = createUser("dove", "insects", 1600, false);
         addMembers(birds, kestrel, goose, pelican, dove);
@@ -557,6 +555,85 @@ public class UserManagerSearchTest exten
         }
     }
 
+    public void testOffset() throws RepositoryException {
+        long[] offsets = {2, 0, 3, 0,      100000};
+        long[] counts =  {4, 4, 0, 100000, 100000};
+
+        for (int k = 0; k < offsets.length; k++) {
+            final long offset = offsets[k];
+            final long count = counts[k];
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    builder.setSortOrder("profile/@weight", Direction.ASCENDING);
+                    builder.setLimit(offset, count);
+                }
+            });
+
+            Iterator<Authorizable> expected = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    builder.setSortOrder("profile/@weight", Direction.ASCENDING);
+                }
+            });
+
+            skip(expected, offset);
+            assertSame(expected, result, count);
+            assertFalse(result.hasNext());
+        }
+    }
+
+    public void testSetBound() throws RepositoryException {
+        List<User> sortedUsers = new ArrayList<User>(users);
+        Comparator<? super User> comp = new Comparator<User>() {
+            public int compare(User user1, User user2) {
+                try {
+                    Value[] weight1 = user1.getProperty("profile/weight");
+                    assertNotNull(weight1);
+                    assertEquals(1, weight1.length);
+
+                    Value[] weight2 = user2.getProperty("profile/weight");
+                    assertNotNull(weight2);
+                    assertEquals(1, weight2.length);
+
+                    return weight1[0].getDouble() < weight2[0].getDouble() ? -1 : 1;
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                    return 0;  // Make the compiler happy
+                }
+            }
+        };
+        Collections.sort(sortedUsers, comp);
+
+        long[] counts = {4, 0, 100000};
+        for (final long count : counts) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    builder.setCondition(builder.
+                            property("profile/@cute", RelationOp.EQ, vf.createValue(true)));
+                    builder.setSortOrder("profile/@weight", Direction.ASCENDING);
+                    builder.setLimit(vf.createValue(1000.0), count);
+                }
+            });
+
+            Iterator<User> expected = Iterators.filterIterator(sortedUsers.iterator(), new Predicate<User>() {
+                public boolean evaluate(User user) {
+                    try {
+                        Value[] cute = user.getProperty("profile/cute");
+                        Value[] weight = user.getProperty("profile/weight");
+                        return cute != null && cute.length == 1 && cute[0].getBoolean() &&
+                                weight != null && weight.length == 1 && weight[0].getDouble() > 1000.0;
+
+                    } catch (RepositoryException e) {
+                        fail(e.getMessage());
+                    }
+                    return false;
+                }
+            });
+
+            assertSame(expected, result, count);
+            assertFalse(result.hasNext());
+        }
+    }
+
     //------------------------------------------< private >---
 
     private static void addMembers(Group group, Authorizable... authorizables) throws RepositoryException {
@@ -624,5 +701,18 @@ public class UserManagerSearchTest exten
         return set;
     }
 
+    private static <T> void assertSame(Iterator<? extends T> expected, Iterator<? extends T> actual, long count) {
+        for (int k = 0; k < count && actual.hasNext(); k++) {
+            assertTrue(expected.hasNext());
+            assertEquals(expected.next(), actual.next());
+        }
+    }
+
+    private static <T> void skip(Iterator<T> iterator, long count) {
+        for (int k = 0; k < count && iterator.hasNext(); k++) {
+            iterator.next();
+        }
+    }
+
 
 }