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/05 18:53:57 UTC

svn commit: r1031680 - 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: Fri Nov  5 17:53:57 2010
New Revision: 1031680

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

Added:
    jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Query.java
    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
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Predicates.java
Modified:
    jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java

Added: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Query.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Query.java?rev=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Query.java (added)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Query.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,50 @@
+/*
+ * 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.jackrabbit.api.security.user;
+
+/**
+ * A query to match {@link Authorizable}s. Pass an instance of this interface to
+ * {@link UserManager#findAuthorizables(Query)}.
+ *
+ * The following query finds all users named 'Bob' which have the word
+ * 'engineer' in its description and returns them in ascending order wrt. to
+ * the name.
+ *
+ * <pre>
+ *  Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+ *      public <T> void build(QueryBuilder<T> builder) {
+ *          builder.setCondition(builder.
+ *              and(builder.
+ *                  property("@name", RelationOp.EQ, valueFactory.createValue("Bob")), builder.
+ *                  contains("@description", "engineer")));
+ *
+ *          builder.setSortOrder("@name", Direction.ASCENDING);
+ *          builder.setSelector(Selector.USER);
+ *      }
+ *  });
+ * </pre>
+ */
+public interface Query {
+
+    /**
+     * Build the query using a {@link QueryBuilder}.
+     * @param builder  A query builder for building the query.
+     * @param <T>  Opaque type of the query builder.
+     */
+    <T> void build(QueryBuilder<T> builder);
+}

Added: 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=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java (added)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/QueryBuilder.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.api.security.user;
+
+import javax.jcr.Value;
+
+public interface QueryBuilder<T> {
+
+    /**
+     * The sort order of the result set of a query.
+     */
+    enum Direction {
+        ASCENDING("ascending"),
+        DESCENDING("descending");
+
+        private final String direction;
+
+        Direction(String direction) {
+            this.direction = direction;
+        }
+
+        public String getDirection() {
+            return direction;
+        }
+    }
+
+    /**
+     * The selectors for a query. 
+     */
+    enum Selector { 
+        AUTHORIZABLE("rep:Authorizable"), 
+        USER("rep:User"), 
+        GROUP("rep:Group");
+
+        private final String ntName;
+
+        Selector(String ntName) {
+            this.ntName = ntName;
+        }
+
+        public String getNtName() {
+            return ntName;   
+        }
+    }
+
+    /**
+     * Relational operators for comparing a property to a value. Correspond
+     * to the general comparison operators as define in JSR-170.
+     * The {@link #EX} tests for existence of a property.   
+     */
+    enum RelationOp {
+        NE("!="),
+        EQ("="),
+        LT("<"),
+        LE("<="),
+        GT(">"),
+        GE("=>"),
+        EX("");
+
+        private final String op;
+
+        RelationOp(String op) {
+            this.op = op;
+        }
+
+        public String getOp() {
+            return op;
+        }
+    }
+
+    /**
+     * Set the selector for the query.
+     *
+     * @param selector  One of {@link Selector#AUTHORIZABLE}, {@link Selector#USER} or {@link Selector#GROUP} 
+     */
+    void setSelector(Selector selector);
+
+    /**
+     * Set the scope for the query. If set, the query will only return members of a specific group.
+     *
+     * @param groupName  Name of the group to restrict the query to.
+     * @param declaredOnly  If <code>true</code> only declared members of the groups are returned.
+     * Otherwise indirect memberships are also considered. 
+     */
+    void setScope(String groupName, boolean declaredOnly);
+
+    /**
+     * Set the condition for the query. The query only includes {@link Authorizable}s
+     * for which this condition holds.
+     * 
+     * @param condition  Condition upon which <code>Authorizables</code> are included in the query result
+     */
+    void setCondition(T condition);
+
+    /**
+     * Set the sort order of the {@link Authorizable}s returned by the query.
+     * The format of the <code>propertyName</code> is the same as in XPath:
+     * <code>@propertyName</code> sorts on a property of the current node.
+     * <code>relative/path/@propertyName</code> sorts on a property of a
+     * descendant node.
+     *
+     * @param propertyName  The name of the property to sort on
+     * @param direction  Direction to sort. Either {@link Direction#ASCENDING} or {@link Direction#DESCENDING}
+     */
+    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>.
+     */
+    void setLimit(String offset, int maxCount, boolean forward);
+
+    /**
+     * Create a condition which holds iff the node of an {@link Authorizable} has a
+     * property at <code>relPath</code> which relates to <code>value</code> through
+     * <code>op</<code>. The format of the <code>relPath</code> argument is the same
+     * as in XPath: <code>@attributeName</code> for an attribute on this node and
+     * <code>relative/path/@attributeName</code> for an attribute of a descendant node.
+     * {@link RelationOp#EX} tests for existence of a property. In this case the
+     * <code>value</code> argument is ignored. 
+     *
+     * @param relPath  Relative path from the authorizable's node to the property
+     * @param op  Comparison operator
+     * @param value  Value to compare the property at <code>relPath</code> to
+     * @return  A condition
+     */
+    T property(String relPath, RelationOp op, Value value); 
+
+    /**
+     * Create a full text search condition. The condition holds iff the node of an
+     * {@link Authorizable} has a property at <code>relPath</code> for which
+     * <code>searchExpr</code> yields results.
+     * The format of the <code>relPath</code> argument is the same as in XPath:
+     * <code>.</code> searches all properties of the current node, <code>@attributeName</code>
+     * searches the attributeName property of the current node, <code>relative/path/.</code>
+     * searches all properties of the descendant node at relative/path and
+     * <code>relative/path/@attributeName</code> searches the attributeName property
+     * of the descendant node at relative/path.
+     * The syntax of <code>searchExpr</code> is <pre>[-]value { [OR] [-]value }</pre>.
+     *
+     * @param relPath  Relative path from the authorizable's node to the property
+     * @param searchExpr  A full text search expression
+     * @return  A condition
+     */
+    T contains(String relPath, String searchExpr);
+
+    /**
+     * Create a condition which holds for {@link Authorizable}s which can impersonate as
+     * <code>name</code>.
+     *
+     * @param name  Name of an authorizable
+     * @return  A condition
+     */
+    T impersonates(String name);
+
+    /**
+     * Return a condition which holds iff <code>condition</code> does not hold.
+     *
+     * @param condition  Condition to negate
+     * @return  A condition
+     */
+    T not(T condition);
+
+    /**
+     * Return a condition which holds iff both sub conditions hold.
+     *
+     * @param condition1  first sub condition
+     * @param condition2  second sub condition
+     * @return  A condition
+     */
+    T and(T condition1, T condition2);
+
+    /**
+     * Return a condition which holds iff any of the two sub conditions hold.
+     *
+     * @param condition1  first sub condition
+     * @param condition2  second sub condition
+     * @return  A condition
+     */
+    T or(T condition1, T condition2);
+}

Modified: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java?rev=1031680&r1=1031679&r2=1031680&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java (original)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java Fri Nov  5 17:53:57 2010
@@ -111,6 +111,15 @@ public interface UserManager {
     Iterator<Authorizable> findAuthorizables(String relPath, String value, int searchType) throws RepositoryException;
 
     /**
+     * Return {@link Authorizable}s that match a specific {@link Query}.
+     *
+     * @param query  A query
+     * @return  Iterator of authorizables witch match the <code>query</code>.
+     * @throws RepositoryException  If an error occurs.
+     */
+    Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException;
+
+    /**
      * Creates an User for the given userID / password pair; neither of the
      * specified parameters can be <code>null</code>.<br>
      * Same as {@link #createUser(String,String,Principal,String)} where

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=1031680&r1=1031679&r2=1031680&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Fri Nov  5 17:53:57 2010
@@ -17,16 +17,8 @@
 package org.apache.jackrabbit.core.security.user;
 
 import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.ItemImpl;
-import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.ProtectedItemModifier;
-import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.SessionListener;
+import org.apache.jackrabbit.api.security.user.*;
+import org.apache.jackrabbit.core.*;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.SystemPrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
@@ -37,26 +29,13 @@ import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.AccessDeniedException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Value;
+import javax.jcr.*;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
-
 import java.io.UnsupportedEncodingException;
 import java.security.Principal;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * Default implementation of the <code>UserManager</code> interface with the
@@ -487,6 +466,12 @@ public class UserManagerImpl extends Pro
         return new AuthorizableIterator(nodes);
     }
 
+    public Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException {
+        XPathQueryBuilder builder = new XPathQueryBuilder();
+        query.build(builder);
+        return new XPathQueryEvaluator(builder, this, session.getWorkspace().getQueryManager()).eval();
+    }
+
     /**
      * @see UserManager#createUser(String,String)
      */

Added: 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=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryBuilder.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,264 @@
+/*
+ * 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.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.api.security.user.QueryBuilder;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class XPathQueryBuilder implements QueryBuilder<XPathQueryBuilder.Condition> {
+
+    interface Condition {
+        void accept(ConditionVisitor visitor) throws RepositoryException;
+    }
+
+    interface ConditionVisitor {
+        void visit(PropertyCondition condition) throws RepositoryException;
+        void visit(ContainsCondition condition);
+        void visit(ImpersonationCondition condition);
+        void visit(NotCondition condition) throws RepositoryException;
+        void visit(AndCondition condition) throws RepositoryException;
+        void visit(OrCondition condition) throws RepositoryException;
+    }
+
+    private Selector selector = Selector.AUTHORIZABLE;
+    private String groupName;
+    private boolean declaredMembersOnly;
+    private Condition condition;
+    private String sortProperty;
+    private Direction sortDirection = Direction.ASCENDING;
+    private String offset;
+    private int maxCount = -1;
+    private boolean forward = true;
+
+    Selector getSelector() {
+        return selector;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public boolean isDeclaredMembersOnly() {
+        return declaredMembersOnly;
+    }
+    
+    Condition getCondition() {
+        return condition;
+    }
+
+    String getSortProperty() {
+        return sortProperty;
+    }
+
+    Direction getSortDirection() {
+        return sortDirection;
+    }
+
+    String getOffset() {
+        return offset;
+    }
+
+    int getMaxCount() {
+        return maxCount;
+    }
+
+    boolean isForward() {
+        return forward;
+    }
+    
+    //------------------------------------------< QueryBuilder >---
+
+    public void setSelector(Selector selector) {
+        this.selector = selector;
+    }
+
+    public void setScope(String groupName, boolean declaredOnly) {
+        this.groupName = groupName;
+        declaredMembersOnly = declaredOnly;
+    }
+
+    public void setCondition(Condition condition) {
+        this.condition = condition;
+    }
+
+    public void setSortOrder(String propertyName, Direction direction) {
+        sortProperty = propertyName;
+        sortDirection = direction;
+    }
+
+    public void setLimit(String offset, int maxCount, boolean forward) {
+        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) {
+        return new PropertyCondition(relPath, op, value);
+    }
+
+    public Condition contains(String relPath, String searchExpr) {
+        return new ContainsCondition(relPath, searchExpr);
+    }
+
+    public Condition impersonates(String name) {
+        return new ImpersonationCondition(name);
+    }
+
+    public Condition not(Condition condition) {
+        return new NotCondition(condition);
+    }
+
+    public Condition and(Condition condition1, Condition condition2) {
+        return new AndCondition(condition1, condition2);
+    }
+
+    public Condition or(Condition condition1, Condition condition2) {
+        return new OrCondition(condition1, condition2);
+    }
+
+    //------------------------------------------< private >---
+
+    static class PropertyCondition implements Condition {
+        private final String relPath;
+        private final RelationOp op;
+        private final Value value;
+
+        public PropertyCondition(String relPath, RelationOp op, Value value) {
+            this.relPath = relPath;
+            this.op = op;
+            this.value = value;
+        }
+
+        public String getRelPath() {
+            return relPath;
+        }
+
+        public RelationOp getOp() {
+            return op;
+        }
+
+        public Value getValue() {
+            return value;
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    static class ContainsCondition implements Condition {
+        private final String relPath;
+        private final String searchExpr;
+
+        public ContainsCondition(String relPath, String searchExpr) {
+            this.relPath = relPath;
+            this.searchExpr = searchExpr;
+        }
+
+        public String getRelPath() {
+            return relPath;
+        }
+
+        public String getSearchExpr() {
+            return searchExpr;
+        }
+
+        public void accept(ConditionVisitor visitor) {
+            visitor.visit(this);
+        }
+    }
+
+    static class ImpersonationCondition implements Condition {
+        private final String name;
+
+        public ImpersonationCondition(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void accept(ConditionVisitor visitor) {
+            visitor.visit(this);
+        }
+    }
+
+    static class NotCondition implements Condition {
+        private final Condition condition;
+
+        public NotCondition(Condition condition) {
+            this.condition = condition;
+        }
+
+        public Condition getCondition() {
+            return condition;
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    abstract static class CompoundCondition implements Condition, Iterable<Condition> {
+        private final List<Condition> conditions = new ArrayList<Condition>();
+
+        public CompoundCondition() {
+            super();
+        }
+
+        public CompoundCondition(Condition condition1, Condition condition2) {
+            conditions.add(condition1);
+            conditions.add(condition2);
+        }
+
+        public void addCondition(Condition condition) {
+            conditions.add(condition);
+        }
+
+        public Iterator<Condition> iterator() {
+            return conditions.iterator();
+        }
+    }
+
+    static class AndCondition extends CompoundCondition {
+        public AndCondition(Condition condition1, Condition condition2) {
+            super(condition1, condition2);
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    static class OrCondition extends CompoundCondition {
+        public OrCondition(Condition condition1, Condition condition2) {
+            super(condition1, condition2);
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+}

Added: 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=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,237 @@
+/*
+ * 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.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.QueryBuilder.RelationOp;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.spi.commons.iterator.Iterators;
+import org.apache.jackrabbit.spi.commons.iterator.Predicate;
+import org.apache.jackrabbit.spi.commons.iterator.Predicates;
+import org.apache.jackrabbit.spi.commons.iterator.Transformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import java.util.Iterator;
+
+/**
+ * This evaluator for {@link org.apache.jackrabbit.api.security.user.Query}s use XPath
+ * and some minimal client side filtering.  
+ */
+public class XPathQueryEvaluator implements XPathQueryBuilder.ConditionVisitor {
+    static final Logger log = LoggerFactory.getLogger(XPathQueryEvaluator.class);
+
+    private final XPathQueryBuilder builder;
+    private final UserManagerImpl userManager;
+    private final QueryManager queryManager;
+    private final StringBuilder xPath = new StringBuilder();
+
+    public XPathQueryEvaluator(XPathQueryBuilder builder, UserManagerImpl userManager, QueryManager queryManager) {
+        this.builder = builder;
+        this.userManager = userManager;
+        this.queryManager = queryManager;
+    }
+
+    public Iterator<Authorizable> eval() throws RepositoryException {
+        xPath.append("//element(*,")
+             .append(builder.getSelector().getNtName())
+             .append(')');
+
+        XPathQueryBuilder.Condition condition = builder.getCondition();
+        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());
+        }
+
+        Query query = queryManager.createQuery(xPath.toString(), Query.XPATH);
+        int count = builder.getMaxCount();
+        if (count == 0) {
+            return Iterators.empty();
+        }
+
+        if (count > 0) {
+            query.setLimit(count);
+        }
+
+        return filter(toAuthorizables(execute(query)), builder.getGroupName(), builder.isDeclaredMembersOnly());
+    }
+
+    //------------------------------------------< ConditionVisitor >---
+
+    public void visit(XPathQueryBuilder.PropertyCondition condition) throws RepositoryException {
+        RelationOp relOp = condition.getOp();
+        if (relOp == RelationOp.EX) {
+            xPath.append(condition.getRelPath());
+        }
+        else {
+            xPath.append(condition.getRelPath())    
+                 .append(condition.getOp().getOp())
+                 .append(format(condition.getValue()));
+        }
+    }
+
+    public void visit(XPathQueryBuilder.ContainsCondition condition) {
+        xPath.append("jcr:contains(")
+             .append(condition.getRelPath())     
+             .append(",'")
+             .append(condition.getSearchExpr())
+             .append("')");
+    }
+
+    public void visit(XPathQueryBuilder.ImpersonationCondition condition) {
+        xPath.append("@rep:impersonators='")
+             .append(condition.getName())
+             .append('\'');
+    }
+
+    public void visit(XPathQueryBuilder.NotCondition condition) throws RepositoryException {
+        xPath.append("not(");
+        condition.getCondition().accept(this);
+        xPath.append(')');
+    }
+
+    public void visit(XPathQueryBuilder.AndCondition condition) throws RepositoryException {
+        int count = 0;
+        for (XPathQueryBuilder.Condition c : condition) {
+            xPath.append(count++ > 0 ? " and " : "");
+            c.accept(this);
+        }
+    }
+
+    public void visit(XPathQueryBuilder.OrCondition condition) throws RepositoryException {
+        int pos = xPath.length();
+
+        int count = 0;
+        for (XPathQueryBuilder.Condition c : condition) {
+            xPath.append(count++ > 0 ? " or " : "");
+            c.accept(this);
+        }
+
+        // Surround or clause with parentheses if it contains more than one term
+        if (count > 1) {
+            xPath.insert(pos, '(');
+            xPath.append(')');
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static String format(Value value) throws RepositoryException {
+        switch (value.getType()) {
+            case PropertyType.STRING:
+            case PropertyType.BOOLEAN:
+                return '\'' + value.getString() + '\'';
+
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return value.getString();
+
+            case PropertyType.DATE:
+                return "xs:dateTime('" + value.getString() + "')";
+
+            default:
+                throw new RepositoryException("Property of type " + PropertyType.nameFromValue(value.getType()) +
+                        " not supported");
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Iterator<Node> execute(Query query) throws RepositoryException {
+        return query.execute().getNodes();
+    }
+
+    private Iterator<Authorizable> toAuthorizables(Iterator<Node> nodes) {
+        Transformer<Node, Authorizable> transformer = new Transformer<Node, Authorizable>() {
+            public Authorizable transform(Node node) {
+                try {
+                    return userManager.getAuthorizable((NodeImpl) node);
+                } catch (RepositoryException e) {
+                    log.warn("Cannot create authorizable from node {}", node);
+                    log.debug(e.getMessage(), e);
+                    return null;
+                }
+            }
+        };
+
+        return Iterators.transformIterator(nodes, transformer);
+    }
+
+    private Iterator<Authorizable> filter(Iterator<Authorizable> authorizables, String groupName,
+                                          boolean declaredMembersOnly) throws RepositoryException {
+
+        Predicate<Authorizable> predicate;
+        if (groupName == null) {
+            predicate = Predicates.TRUE();
+        }
+        else {
+            Authorizable groupAuth = userManager.getAuthorizable(groupName);
+            if (groupAuth == null || !groupAuth.isGroup()) {
+                predicate = Predicates.FALSE();
+            }
+            else {
+                final Group group = (Group) groupAuth;
+                if (declaredMembersOnly) {
+                    predicate = new Predicate<Authorizable>() {
+                        public boolean evaluate(Authorizable authorizable) {
+                            try {
+                                return authorizable != null && group.isDeclaredMember(authorizable);
+                            } catch (RepositoryException e) {
+                                log.warn("Cannot determine whether {} is member of group {}", authorizable, group);
+                                log.debug(e.getMessage(), e);
+                                return false;
+                            }
+                        }
+                    };
+
+                }
+                else {
+                    predicate = new Predicate<Authorizable>() {
+                        public boolean evaluate(Authorizable authorizable) {
+                            try {
+                                return authorizable != null && group.isMember(authorizable);
+                            } catch (RepositoryException e) {
+                                log.warn("Cannot determine whether {} is member of group {}", authorizable, group);
+                                log.debug(e.getMessage(), e);
+                                return false;
+                            }
+                        }
+                    };
+                }
+            }
+        }
+
+        return Iterators.filterIterator(authorizables, predicate);
+    }
+
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java?rev=1031680&r1=1031679&r2=1031680&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java Fri Nov  5 17:53:57 2010
@@ -41,6 +41,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(GroupTest.class);
         suite.addTestSuite(NestedGroupTest.class);
         suite.addTestSuite(ImpersonationTest.class);
+        suite.addTestSuite(UserManagerSearchTest.class);
 
         return suite;
     }

Added: 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=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerSearchTest.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,628 @@
+/*
+ * 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.jackrabbit.api.security.user;
+
+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;
+
+public class UserManagerSearchTest extends AbstractUserTest {
+
+    // users
+    private User blackWidow;
+    private User gardenSpider;
+    private User jumpingSpider;
+    private User ant;
+    private User bee;
+    private User fly;
+    private User jackrabbit;
+    private User deer;
+    private User opposum;
+    private User kangaroo;
+    private User elephant;
+    private User lemur;
+    private User gibbon;
+    private User crocodile;
+    private User turtle;
+    private User lizard;
+    private User kestrel;
+    private User goose;
+    private User pelican;
+    private User dove;
+    private User salamander;
+    private User goldenToad;
+    private User poisonDartFrog;
+
+    private final Set<User> users = new HashSet<User>();
+
+    // groups
+    private Group animals;
+    private Group invertebrates;
+    private Group arachnids;
+    private Group insects;
+    private Group vertebrates;
+    private Group mammals;
+    private Group apes;
+    private Group reptiles;
+    private Group birds;
+    private Group amphibians;
+
+    private final Set<Group> groups = new HashSet<Group>();
+    private final Set<Authorizable> authorizables = new HashSet<Authorizable>();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Create a zoo. Please excuse my ignorance in zoology ;-)
+        animals = createGroup("animals");
+            invertebrates = createGroup("invertebrates");
+                arachnids = createGroup("arachnids");
+                insects = createGroup("insects");
+            vertebrates = createGroup("vertebrates");
+                mammals = createGroup("mammals");
+                    apes = createGroup("apes");
+                reptiles = createGroup("reptiles");
+                birds = createGroup("birds");
+                amphibians = createGroup("amphibians");
+
+        animals.addMember(invertebrates);
+        animals.addMember(vertebrates);
+        invertebrates.addMember(arachnids);
+        invertebrates.addMember(insects);
+        vertebrates.addMember(mammals);
+        vertebrates.addMember(reptiles);
+        vertebrates.addMember(birds);
+        vertebrates.addMember(amphibians);
+        mammals.addMember(apes);
+
+        blackWidow = createUser("black widow", "flies", 2, false);
+        gardenSpider = createUser("garden spider", "flies", 2, false);
+        jumpingSpider = createUser("jumping spider", "insects", 1, false);
+        addMembers(arachnids, blackWidow, gardenSpider, jumpingSpider);
+
+        ant = createUser("ant", "leaves", 0.5, false);
+        bee = createUser("bee", "honey", 2.5, true);
+        fly = createUser("fly", "dirt", 1.3, false);
+        addMembers(insects, ant, bee, fly);
+
+        jackrabbit = createUser("jackrabbit", "carrots", 2500, true);
+        deer = createUser("deer", "leaves", 120000, true);
+        opposum = createUser("opposum", "fruit", 1200, true);
+        kangaroo = createUser("kangaroo", "grass", 80000, true);
+        elephant = createUser("elephant", "leaves", 5000000, true);
+        addMembers(mammals, jackrabbit, deer, opposum, kangaroo, elephant);
+
+        lemur = createUser("lemur", "nectar", 1100, true);
+        gibbon = createUser("gibbon", "meat", 20000, true);
+        addMembers(apes, lemur, gibbon);
+
+        crocodile = createUser("crocodile", "meat", 80000, false);
+        turtle = createUser("turtle", "leaves", 10000, true);
+        lizard = createUser("lizard", "leaves", 2000, false);
+        addMembers(reptiles, crocodile, turtle, lizard);
+
+        kestrel = createUser("kestrel", "mice", 2000, false);
+        goose = createUser("goose", "snails", 10000, true);
+        pelican = createUser("pelican", "fish", 15000, true);
+        dove = createUser("dove", "insects", 1600, false);
+        addMembers(birds, kestrel, goose, pelican, dove);
+
+        salamander = createUser("salamander", "insects", 800, true);
+        goldenToad = createUser("golden toad", "insects", 700, false);
+        poisonDartFrog = createUser("poison dart frog", "insects", 40, false);
+        addMembers(amphibians, salamander, goldenToad, poisonDartFrog);
+
+        setProperty("canFly", vf.createValue(true), bee, fly, kestrel, goose, pelican, dove);
+        setProperty("poisonous",vf.createValue(true), blackWidow, bee, poisonDartFrog );
+        setProperty("poisonous", vf.createValue(false), turtle, lemur);
+        setProperty("hasWings", vf.createValue(false), blackWidow, gardenSpider, jumpingSpider, ant,
+                jackrabbit, deer, opposum, kangaroo, elephant, lemur, gibbon, crocodile, turtle, lizard,
+                salamander, goldenToad, poisonDartFrog);
+        setProperty("color", vf.createValue("black"), blackWidow, gardenSpider, ant, fly, lizard, salamander);
+        setProperty("color", vf.createValue("white"), opposum, goose, pelican, dove);
+        setProperty("color", vf.createValue("gold"), goldenToad);
+        setProperty("numberOfLegs", vf.createValue(2), kangaroo, gibbon, kestrel, goose, dove);
+        setProperty("numberOfLegs", vf.createValue(4), jackrabbit, deer, opposum, elephant, lemur, crocodile,
+                turtle, lizard, salamander, goldenToad, poisonDartFrog);
+        setProperty("numberOfLegs", vf.createValue(6), ant, bee, fly);
+        setProperty("numberOfLegs", vf.createValue(8), blackWidow, gardenSpider, jumpingSpider);
+
+        elephant.getImpersonation().grantImpersonation(jackrabbit.getPrincipal());
+
+        authorizables.addAll(users);
+        authorizables.addAll(groups);
+
+        if (!userMgr.isAutoSave()) {
+            superuser.save();
+        }
+
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        for (Authorizable authorizable : authorizables) {
+            authorizable.remove();
+        }
+        authorizables.clear();
+        groups.clear();
+        users.clear();
+
+        if (!userMgr.isAutoSave()) {
+            superuser.save();
+        }
+
+        super.tearDown();
+    }
+
+    public void testAny() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) { /* any */ }
+        });
+
+        assertContainsAll(result, authorizables.iterator());
+    }
+
+    public void testSelector() throws RepositoryException {
+        for (final Selector s : Selector.values()) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    builder.setSelector(s);
+                }
+            });
+
+            switch (s) {
+                case AUTHORIZABLE:
+                    assertContainsAll(result, authorizables.iterator());
+                    break;
+
+                case USER:
+                    assertContainsAll(result, users.iterator());
+                    break;
+
+                case GROUP:
+                    assertContainsAll(result, groups.iterator());
+                    break;
+
+                default:
+                    fail("Fall through in switch");
+            }
+
+        }
+    }
+
+    public void testDirectScope() throws RepositoryException {
+        Group[] groups = new Group[]{mammals, vertebrates, apes};
+        for (final Group g : groups) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    try {
+                        builder.setScope(g.getID(), true);
+                    } catch (RepositoryException e) {
+                        fail(e.getMessage());
+                    }
+                }
+            });
+
+            Iterator<Authorizable> members = g.getDeclaredMembers();
+            assertSameElements(result, members);
+        }
+    }
+
+    public void testIndirectScope() throws RepositoryException {
+        Group[] groups = new Group[]{mammals, vertebrates, apes};
+        for (final Group g : groups) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    try {
+                        builder.setScope(g.getID(), false);
+                    } catch (RepositoryException e) {
+                        fail(e.getMessage());
+                    }
+                }
+            });
+
+            Iterator<Authorizable> members = g.getMembers();
+            assertSameElements(result, members);
+        }
+    }
+
+    public void testFindUsersInGroup() throws RepositoryException {
+        Group[] groups = new Group[]{mammals, vertebrates, apes};
+        for (final Group g : groups) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    try {
+                        builder.setSelector(Selector.USER);
+                        builder.setScope(g.getID(), false);
+                    } catch (RepositoryException e) {
+                        fail(e.getMessage());
+                    }
+                }
+            });
+
+            Iterator<Authorizable> members = g.getMembers();
+            Iterator<Authorizable> users = Iterators.filterIterator(members, new Predicate<Authorizable>() {
+                public boolean evaluate(Authorizable authorizable) {
+                    return !authorizable.isGroup();
+                }
+            });
+            assertSameElements(result, users);
+        }
+    }
+
+    public void testFindGroupsInGroup() throws RepositoryException {
+        Group[] groups = new Group[]{mammals, vertebrates, apes};
+        for (final Group g : groups) {
+            Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+                public <T> void build(QueryBuilder<T> builder) {
+                    try {
+                        builder.setSelector(Selector.GROUP);
+                        builder.setScope(g.getID(), true);
+                    } catch (RepositoryException e) {
+                        fail(e.getMessage());
+                    }
+                }
+            });
+
+            Iterator<Authorizable> members = g.getDeclaredMembers();
+            Iterator<Authorizable> users = Iterators.filterIterator(members, new Predicate<Authorizable>() {
+                public boolean evaluate(Authorizable authorizable) {
+                    return authorizable.isGroup();
+                }
+            });
+            assertSameElements(result, users);
+        }
+    }
+
+    public void testFindProperty1() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        property("@canFly", RelationOp.EQ, vf.createValue(true)));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] canFly = user.getProperty("canFly");
+                    return canFly != null && canFly.length == 1 && canFly[0].getBoolean();
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testFindProperty2() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        property("profile/@weight", RelationOp.GT, vf.createValue(2000.0)));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] weight = user.getProperty("profile/weight");
+                    return weight != null && weight.length == 1 && weight[0].getDouble() > 2000.0;
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testFindProperty3() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        property("@numberOfLegs", RelationOp.EQ, vf.createValue(8)));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] numberOfLegs = user.getProperty("numberOfLegs");
+                    return numberOfLegs != null && numberOfLegs.length == 1 && numberOfLegs[0].getLong() == 8;
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testPropertyExistence() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        property("@poisonous", RelationOp.EX, null));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] poisonous = user.getProperty("poisonous");
+                    return poisonous != null && poisonous.length == 1;
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testContains1() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        contains(".", "gold"));
+            }
+        });
+
+        Iterator<User> expected = Iterators.singleton(goldenToad);
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testContains2() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        contains("@color", "gold"));
+            }
+        });
+
+        Iterator<User> expected = Iterators.singleton(goldenToad);
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testContains3() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        contains("profile/.", "grass"));
+            }
+        });
+
+        Iterator<User> expected = Iterators.singleton(kangaroo);
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testContains4() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        contains("profile/@food", "grass"));
+            }
+        });
+
+        Iterator<User> expected = Iterators.singleton(kangaroo);
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testCondition1() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        and(builder.
+                            property("profile/@cute", RelationOp.EQ, vf.createValue(true)), builder.
+                            not(builder.
+                                property("@color", RelationOp.EQ, vf.createValue("black")))));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] cute = user.getProperty("profile/cute");
+                    Value[] black = user.getProperty("color");
+                    return cute != null && cute.length == 1 && cute[0].getBoolean() &&
+                           !(black != null && black.length == 1 && black[0].getString().equals("black"));
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testCondition2() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        or(builder.
+                            property("profile/@food", RelationOp.EQ, vf.createValue("mice")), builder.
+                            property("profile/@food", RelationOp.EQ, vf.createValue("nectar"))));
+            }
+        });
+
+        Iterator<User> expected = Iterators.filterIterator(users.iterator(), new Predicate<User>() {
+            public boolean evaluate(User user) {
+                try {
+                    Value[] food = user.getProperty("profile/food");
+                    return food != null && food.length == 1 &&
+                          (food[0].getString().equals("mice") || food[0].getString().equals("nectar"));
+                } catch (RepositoryException e) {
+                    fail(e.getMessage());
+                }
+                return false;
+            }
+        });
+
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testImpersonation() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(builder.
+                        impersonates("jackrabbit"));
+            }
+        });
+
+        Iterator<User> expected = Iterators.singleton(elephant);
+        assertTrue(result.hasNext());
+        assertSameElements(result, expected);
+    }
+
+    public void testSortOrder1() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(
+                        builder.property("@color", RelationOp.EX, null));
+                builder.setSortOrder("@color", Direction.DESCENDING);
+            }
+        });
+
+        assertTrue(result.hasNext());
+        String prev = null;
+        while (result.hasNext()) {
+            Authorizable authorizable = result.next();
+            Value[] color = authorizable.getProperty("color");
+            assertNotNull(color);
+            assertEquals(1, color.length);
+            assertTrue(prev == null || prev.compareTo(color[0].getString()) >= 0);
+            prev = color[0].getString();
+        }
+    }
+
+    public void testSortOrder2() throws RepositoryException {
+        Iterator<Authorizable> result = userMgr.findAuthorizables(new Query() {
+            public <T> void build(QueryBuilder<T> builder) {
+                builder.setCondition(
+                        builder.property("profile/@weight", RelationOp.EX, null));
+                builder.setSortOrder("profile/@weight", Direction.ASCENDING);
+            }
+        });
+
+        assertTrue(result.hasNext());
+        double prev = Double.MIN_VALUE;
+        while (result.hasNext()) {
+            Authorizable authorizable = result.next();
+            Value[] weight = authorizable.getProperty("profile/weight");
+            assertNotNull(weight);
+            assertEquals(1, weight.length);
+            assertTrue(prev <= weight[0].getDouble());
+            prev = weight[0].getDouble();
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static void addMembers(Group group, Authorizable... authorizables) throws RepositoryException {
+        for (Authorizable authorizable : authorizables) {
+            group.addMember(authorizable);
+        }
+    }
+
+    private Group createGroup(String name) throws RepositoryException {
+        Group group = userMgr.createGroup(name);
+        groups.add(group);
+        return group;
+    }
+
+    private User createUser(String name, String food, double weight, boolean cute) throws RepositoryException {
+        User user = userMgr.createUser(name, "");
+        user.setProperty("profile/food", vf.createValue(food));
+        user.setProperty("profile/weight", vf.createValue(weight));
+        user.setProperty("profile/cute", vf.createValue(cute));
+        users.add(user);
+        return user;
+    }
+
+    private static void setProperty(String relPath, Value value, Authorizable... authorizables) throws RepositoryException {
+        for (Authorizable authorizable : authorizables) {
+            authorizable.setProperty(relPath, value);
+        }
+    }
+
+    private static <T> void assertContainsAll(Iterator<? extends T> it1, Iterator<? extends T> it2) {
+        Set<? extends T> set1 = toSet(it1);
+        Set<? extends T> set2 = toSet(it2);
+        set2.removeAll(set1);
+        if (!set2.isEmpty()) {
+            fail("Missing elements in query result: " + set2);
+        }
+    }
+
+    private static <T> void assertSameElements(Iterator<? extends T> it1, Iterator<? extends T> it2) {
+        Set<? extends T> set1 = toSet(it1);
+        Set<? extends T> set2 = toSet(it2);
+
+        Set<? super T> missing = new HashSet<T>();
+        missing.addAll(set2);
+        missing.removeAll(set1);
+
+        Set<? super T> excess = new HashSet<T>();
+        excess.addAll(set1);
+        excess.removeAll(set2);
+
+        if (!missing.isEmpty()) {
+            fail("Missing elements in query result: " + missing);
+        }
+
+        if (!excess.isEmpty()) {
+            fail("Excess elements in query result: " + excess);
+        }
+    }
+
+    private static <T> Set<T> toSet(Iterator<T> it) {
+        Set<T> set = new HashSet<T>();
+        while (it.hasNext()) {
+            set.add(it.next());
+        }
+        return set;
+    }
+
+
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java?rev=1031680&r1=1031679&r2=1031680&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java Fri Nov  5 17:53:57 2010
@@ -19,12 +19,7 @@ package org.apache.jackrabbit.core.secur
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.Impersonation;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.api.security.user.*;
 import org.apache.jackrabbit.commons.xml.ParsingContentHandler;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
@@ -41,29 +36,7 @@ import org.apache.jackrabbit.test.NotExe
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
-import javax.jcr.AccessDeniedException;
-import javax.jcr.Credentials;
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.InvalidSerializedDataException;
-import javax.jcr.Item;
-import javax.jcr.ItemExistsException;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.LoginException;
-import javax.jcr.NamespaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.ReferentialIntegrityException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.Workspace;
+import javax.jcr.*;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
@@ -71,19 +44,13 @@ import javax.jcr.retention.RetentionMana
 import javax.jcr.security.AccessControlManager;
 import javax.jcr.version.VersionException;
 import javax.security.auth.Subject;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.AccessControlException;
 import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * <code>UserImporterTest</code>...
@@ -1405,6 +1372,10 @@ public class UserImporterTest extends Ab
                     return null;
                 }
 
+                public Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException {
+                    return null;
+                }
+
                 public User createUser(String userID, String password) throws AuthorizableExistsException, RepositoryException {
                     return null;
                 }

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Predicates.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Predicates.java?rev=1031680&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Predicates.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Predicates.java Fri Nov  5 17:53:57 2010
@@ -0,0 +1,67 @@
+/*
+ * 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.jackrabbit.spi.commons.iterator;
+
+/**
+ * Utility class containing pre defined {@link Predicate}s
+ */
+public final class Predicates {
+
+    /**
+     * A predicate which is always true
+     */
+    public static final Predicate TRUE = new Predicate() {
+        public boolean evaluate(Object arg) {
+            return true;
+        }
+    };
+
+    /**
+     * A predicate which is always false
+     */
+    public static final Predicate FALSE = new Predicate() {
+        public boolean evaluate(Object arg) {
+            return true;
+        }
+    };
+
+    private Predicates() {
+        // no instances allowed
+    }
+
+    /**
+     * A predicate which is always true
+     * @param <T>
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Predicate<T> TRUE() {
+        return TRUE;
+    }
+
+    /**
+     * A predicate which is always false
+     * @param <T>
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Predicate<T> FALSE() {
+        return FALSE;
+    }
+
+}