jackrabbit-oak-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ang...@apache.org
Subject svn commit: r1373392 - in /jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user: ./ query/
Date Wed, 15 Aug 2012 13:24:21 GMT
Author: angela
Date: Wed Aug 15 13:24:21 2012
New Revision: 1373392

URL: http://svn.apache.org/viewvc?rev=1373392&view=rev
Log:
OAK-50 : Implement User Management (WIP)

Added:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/Condition.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ConditionVisitor.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/RelationOp.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ResultIterator.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryBuilder.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryEvaluator.java
Modified:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java?rev=1373392&r1=1373391&r2=1373392&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java
(original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java
Wed Aug 15 13:24:21 2012
@@ -41,6 +41,8 @@ import org.apache.jackrabbit.oak.api.Pro
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.jcr.SessionDelegate;
+import org.apache.jackrabbit.oak.jcr.security.user.query.XPathQueryBuilder;
+import org.apache.jackrabbit.oak.jcr.security.user.query.XPathQueryEvaluator;
 import org.apache.jackrabbit.oak.jcr.value.ValueConverter;
 import org.apache.jackrabbit.oak.security.user.UserProviderImpl;
 import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
@@ -142,8 +144,9 @@ public class UserManagerImpl implements 
 
     @Override
     public Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException
{
-        // TODO : execute the specified query
-        throw new UnsupportedOperationException("Not Implemented");
+        XPathQueryBuilder builder = new XPathQueryBuilder();
+        query.build(builder);
+        return new XPathQueryEvaluator(builder, this, sessionDelegate.getQueryManager(),
sessionDelegate.getNamePathMapper()).eval();
     }
 
     @Override

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/Condition.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/Condition.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/Condition.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/Condition.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,190 @@
+/*
+ * 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.oak.jcr.security.user.query;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+
+interface Condition {
+
+    void accept(ConditionVisitor visitor) throws RepositoryException;
+
+    //------------------------------------------< Condition implementations >---
+
+    static class Node implements Condition {
+        private final String pattern;
+
+        public Node(String pattern) {
+            this.pattern = pattern;
+        }
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    static class Property implements Condition {
+        private final String relPath;
+        private final RelationOp op;
+        private final Value value;
+        private final String pattern;
+
+        public Property(String relPath, RelationOp op, Value value) {
+            this.relPath = relPath;
+            this.op = op;
+            this.value = value;
+            pattern = null;
+        }
+
+        public Property(String relPath, RelationOp op, String pattern) {
+            this.relPath = relPath;
+            this.op = op;
+            value = null;
+            this.pattern = pattern;
+        }
+
+        public Property(String relPath, RelationOp op) {
+            this.relPath = relPath;
+            this.op = op;
+            value = null;
+            pattern = null;
+        }
+
+        public String getRelPath() {
+            return relPath;
+        }
+
+        public RelationOp getOp() {
+            return op;
+        }
+
+        public Value getValue() {
+            return value;
+        }
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    static class Contains implements Condition {
+        private final String relPath;
+        private final String searchExpr;
+
+        public Contains(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 Impersonation implements Condition {
+        private final String name;
+
+        public Impersonation(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void accept(ConditionVisitor visitor) {
+            visitor.visit(this);
+        }
+    }
+
+    static class Not implements Condition {
+        private final Condition condition;
+
+        public Not(Condition condition) {
+            this.condition = condition;
+        }
+
+        public Condition getCondition() {
+            return condition;
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    abstract static class Compound implements Condition, Iterable<Condition> {
+        private final List<Condition> conditions = new ArrayList<Condition>();
+
+        public Compound() {
+            super();
+        }
+
+        public Compound(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 And extends Compound {
+        public And(Condition condition1, Condition condition2) {
+            super(condition1, condition2);
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+
+    static class Or extends Compound {
+        public Or(Condition condition1, Condition condition2) {
+            super(condition1, condition2);
+        }
+
+        public void accept(ConditionVisitor visitor) throws RepositoryException {
+            visitor.visit(this);
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ConditionVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ConditionVisitor.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ConditionVisitor.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ConditionVisitor.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,36 @@
+/*
+ * 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.oak.jcr.security.user.query;
+
+import javax.jcr.RepositoryException;
+
+interface ConditionVisitor {
+
+    void visit(Condition.Node node) throws RepositoryException;
+
+    void visit(Condition.Property condition) throws RepositoryException;
+
+    void visit(Condition.Contains condition);
+
+    void visit(Condition.Impersonation condition);
+
+    void visit(Condition.Not condition) throws RepositoryException;
+
+    void visit(Condition.And condition) throws RepositoryException;
+
+    void visit(Condition.Or condition) throws RepositoryException;
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/RelationOp.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/RelationOp.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/RelationOp.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/RelationOp.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,28 @@
+package org.apache.jackrabbit.oak.jcr.security.user.query;
+
+/**
+ * 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(""),
+    LIKE("like");
+
+    private final String op;
+
+    RelationOp(String op) {
+        this.op = op;
+    }
+
+    String getOp() {
+        return op;
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ResultIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ResultIterator.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ResultIterator.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/ResultIterator.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,120 @@
+/*
+ * 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.oak.jcr.security.user.query;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implements a query result iterator which only returns a maximum number of
+ * element from an underlying iterator starting at a given offset.
+ *
+ * @param <T> element type of the query results
+ *
+ * TODO move to query-commons ?
+ */
+public class ResultIterator<T> implements Iterator<T> {
+
+    public final static int OFFSET_NONE = 0;
+    public final static int MAX_ALL = -1;
+
+    private final Iterator<T> iterator;
+    private final long offset;
+    private final long max;
+    private int pos;
+    private T next;
+
+    /**
+     * Create a new {@code ResultIterator} with a given offset and maximum
+     *
+     * @param offset Offset to start iteration at. Must be non negative
+     * @param max Maximum elements this iterator should return.
+     * Set to {@link #MAX_ALL} for all results.
+     * @param iterator the underlying iterator
+     * @throws IllegalArgumentException if offset is negative
+     */
+    private ResultIterator(long offset, long max, Iterator<T> iterator) {
+        if (offset < OFFSET_NONE) {
+            throw new IllegalArgumentException("Offset must not be negative");
+        }
+        this.iterator = iterator;
+        this.offset = offset;
+        this.max = max;
+    }
+
+    /**
+     * Returns an iterator respecting the specified {@code offset} and {@code max}.
+     *
+     * @param offset   offset to start iteration at. Must be non negative
+     * @param max      maximum elements this iterator should return. Set to
+     * {@link #MAX_ALL} for all
+     * @param iterator the underlying iterator
+     * @param <T>      element type
+     * @return an iterator which only returns the elements in the given bounds
+     */
+    public static <T> Iterator<T> create(long offset, long max, Iterator<T>
iterator) {
+        if (offset == OFFSET_NONE && max == MAX_ALL) {
+            // no constraints on offset nor max -> return the original iterator.
+            return iterator;
+        } else {
+            return new ResultIterator<T>(offset, max, iterator);
+        }
+    }
+
+    //-----------------------------------------------------------< Iterator >---
+    @Override
+    public boolean hasNext() {
+        if (next == null) {
+            fetchNext();
+        }
+        return next != null;
+    }
+
+    @Override
+    public T next() {
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        return consumeNext();
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private void fetchNext() {
+        for (; pos < offset && iterator.hasNext(); pos++) {
+            next = iterator.next();
+        }
+
+        if (pos < offset || !iterator.hasNext() || max >= 0 && pos - offset
+ 1 > max) {
+            next = null;
+        } else {
+            next = iterator.next();
+            pos++;
+        }
+    }
+
+    private T consumeNext() {
+        T element = next;
+        next = null;
+        return element;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryBuilder.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryBuilder.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryBuilder.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,195 @@
+/*
+ * 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.oak.jcr.security.user.query;
+
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.QueryBuilder;
+
+public class XPathQueryBuilder implements QueryBuilder<Condition> {
+
+    private Class<? extends Authorizable> selector = Authorizable.class;
+    private String groupName;
+    private boolean declaredMembersOnly;
+    private Condition condition;
+    private String sortProperty;
+    private Direction sortDirection = Direction.ASCENDING;
+    private boolean sortIgnoreCase;
+    private Value bound;
+    private long offset;
+    private long maxCount = -1;
+
+    //-------------------------------------------------------< QueryBuilder >---
+    @Override
+    public void setSelector(Class<? extends Authorizable> selector) {
+        this.selector = selector;
+    }
+
+    @Override
+    public void setScope(String groupName, boolean declaredOnly) {
+        this.groupName = groupName;
+        declaredMembersOnly = declaredOnly;
+    }
+
+    @Override
+    public void setCondition(Condition condition) {
+        this.condition = condition;
+    }
+
+    @Override
+    public void setSortOrder(String propertyName, Direction direction, boolean ignoreCase)
{
+        sortProperty = propertyName;
+        sortDirection = direction;
+        sortIgnoreCase = ignoreCase;
+    }
+
+    @Override
+    public void setSortOrder(String propertyName, Direction direction) {
+        setSortOrder(propertyName, direction, false);
+    }
+
+    @Override
+    public void setLimit(Value bound, long maxCount) {
+        offset = 0;   // Unset any previously set offset
+        this.bound = bound;
+        this.maxCount = maxCount;
+    }
+
+    @Override
+    public void setLimit(long offset, long maxCount) {
+        bound = null; // Unset any previously set bound
+        this.offset = offset;
+        this.maxCount = maxCount;
+    }
+
+    @Override
+    public Condition nameMatches(String pattern) {
+        return new Condition.Node(pattern);
+    }
+
+    @Override
+    public Condition neq(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.NE, value);
+    }
+
+    @Override
+    public Condition eq(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.EQ, value);
+    }
+
+    @Override
+    public Condition lt(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.LT, value);
+    }
+
+    @Override
+    public Condition le(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.LE, value);
+    }
+
+    @Override
+    public Condition gt(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.GT, value);
+    }
+
+    @Override
+    public Condition ge(String relPath, Value value) {
+        return new Condition.Property(relPath, RelationOp.GE, value);
+    }
+
+    @Override
+    public Condition exists(String relPath) {
+        return new Condition.Property(relPath, RelationOp.EX);
+    }
+
+    @Override
+    public Condition like(String relPath, String pattern) {
+        return new Condition.Property(relPath, RelationOp.LIKE, pattern);
+    }
+
+    @Override
+    public Condition contains(String relPath, String searchExpr) {
+        return new Condition.Contains(relPath, searchExpr);
+    }
+
+    @Override
+    public Condition impersonates(String name) {
+        return new Condition.Impersonation(name);
+    }
+
+    @Override
+    public Condition not(Condition condition) {
+        return new Condition.Not(condition);
+    }
+
+    @Override
+    public Condition and(Condition condition1, Condition condition2) {
+        return new Condition.And(condition1, condition2);
+    }
+
+    @Override
+    public Condition or(Condition condition1, Condition condition2) {
+        return new Condition.Or(condition1, condition2);
+    }
+
+    //-----------------------------------------------------------< internal >---
+
+    Condition property(String relPath, RelationOp op, Value value) {
+        return new Condition.Property(relPath, op, value);
+    }
+
+    Class<? extends Authorizable> getSelector() {
+        return selector;
+    }
+
+    String getGroupName() {
+        return groupName;
+    }
+
+    boolean isDeclaredMembersOnly() {
+        return declaredMembersOnly;
+    }
+
+    Condition getCondition() {
+        return condition;
+    }
+
+    String getSortProperty() {
+        return sortProperty;
+    }
+
+    Direction getSortDirection() {
+        return sortDirection;
+    }
+
+    boolean getSortIgnoreCase() {
+        return sortIgnoreCase;
+    }
+
+    Value getBound() {
+        return bound;
+    }
+
+    long getOffset() {
+        return offset;
+    }
+
+    long getMaxCount() {
+        return maxCount;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryEvaluator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryEvaluator.java?rev=1373392&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryEvaluator.java
(added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/query/XPathQueryEvaluator.java
Wed Aug 15 13:24:21 2012
@@ -0,0 +1,340 @@
+/*
+ * 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.oak.jcr.security.user.query;
+
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+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 com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterators;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.QueryBuilder;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.jcr.security.user.UserManagerImpl;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This evaluator for {@link org.apache.jackrabbit.api.security.user.Query}s use XPath
+ * and some minimal client side filtering.
+ */
+public class XPathQueryEvaluator implements ConditionVisitor {
+    static final Logger log = LoggerFactory.getLogger(XPathQueryEvaluator.class);
+
+    private final XPathQueryBuilder builder;
+    private final UserManager userManager;
+    private final QueryManager queryManager;
+    private final NamePathMapper namePathMapper;
+
+    private final StringBuilder xPath = new StringBuilder();
+
+    public XPathQueryEvaluator(XPathQueryBuilder builder, UserManagerImpl userManager,
+                               QueryManager queryManager, NamePathMapper namePathMapper)
{
+        this.builder = builder;
+        this.userManager = userManager;
+        this.queryManager = queryManager;
+        this.namePathMapper = namePathMapper;
+    }
+
+    public Iterator<Authorizable> eval() throws RepositoryException {
+        xPath.append("//element(*,")
+                .append(getNtName(builder.getSelector()))
+                .append(')');
+
+        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();
+        QueryBuilder.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, getCollation(sortDir),
bound);
+                condition = condition == null
+                        ? boundCondition
+                        : builder.and(condition, boundCondition);
+            }
+        }
+
+        if (condition != null) {
+            xPath.append('[');
+            condition.accept(this);
+            xPath.append(']');
+        }
+
+        if (sortCol != null) {
+            boolean ignoreCase = builder.getSortIgnoreCase();
+            xPath.append(" order by ")
+                    .append(ignoreCase ? "" : "fn:lower-case(")
+                    .append(sortCol)
+                    .append(ignoreCase ? " " : ") ")
+                    .append(sortDir.getDirection());
+        }
+
+        Query query = queryManager.createQuery(xPath.toString(), Query.XPATH);
+        long maxCount = builder.getMaxCount();
+        if (maxCount == 0) {
+            return Iterators.emptyIterator();
+        }
+
+        // If we are scoped to a group and have a limit, we have to apply the limit
+        // here (inefficient!) otherwise we can apply the limit in the query
+        if (builder.getGroupName() == null) {
+            if (offset > 0) {
+                query.setOffset(offset);
+            }
+            if (maxCount > 0) {
+                query.setLimit(maxCount);
+            }
+            return toAuthorizables(execute(query));
+        } else {
+            Iterator<Authorizable> result = toAuthorizables(execute(query));
+            Iterator<Authorizable> filtered = filter(result, builder.getGroupName(),
builder.isDeclaredMembersOnly());
+            return ResultIterator.create(offset, maxCount, filtered);
+        }
+    }
+
+    //---------------------------------------------------< ConditionVisitor >---
+    @Override
+    public void visit(Condition.Node condition) throws RepositoryException {
+        xPath.append('(')
+                .append("jcr:like(")
+                .append(namePathMapper.getJcrName(UserConstants.REP_PRINCIPAL_NAME))
+                .append(",'")
+                .append(condition.getPattern())
+                .append("')")
+                .append(" or ")
+                .append("jcr:like(fn:name(.),'")
+                .append(escape(condition.getPattern()))
+                .append("')")
+                .append(')');
+    }
+
+    @Override
+    public void visit(Condition.Property condition) throws RepositoryException {
+        RelationOp relOp = condition.getOp();
+        if (relOp == RelationOp.EX) {
+            xPath.append(condition.getRelPath());
+        } else if (relOp == RelationOp.LIKE) {
+            xPath.append("jcr:like(")
+                    .append(condition.getRelPath())
+                    .append(",'")
+                    .append(condition.getPattern())
+                    .append("')");
+        } else {
+            xPath.append(condition.getRelPath())
+                    .append(condition.getOp().getOp())
+                    .append(format(condition.getValue()));
+        }
+    }
+
+    @Override
+    public void visit(Condition.Contains condition) {
+        xPath.append("jcr:contains(")
+                .append(condition.getRelPath())
+                .append(",'")
+                .append(condition.getSearchExpr())
+                .append("')");
+    }
+
+    @Override
+    public void visit(Condition.Impersonation condition) {
+        xPath.append("@rep:impersonators='")
+                .append(condition.getName())
+                .append('\'');
+    }
+
+    @Override
+    public void visit(Condition.Not condition) throws RepositoryException {
+        xPath.append("not(");
+        condition.getCondition().accept(this);
+        xPath.append(')');
+    }
+
+    @Override
+    public void visit(Condition.And condition) throws RepositoryException {
+        int count = 0;
+        for (Condition c : condition) {
+            xPath.append(count++ > 0 ? " and " : "");
+            c.accept(this);
+        }
+    }
+
+    @Override
+    public void visit(Condition.Or condition) throws RepositoryException {
+        int pos = xPath.length();
+
+        int count = 0;
+        for (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 >---
+    /**
+     * Escape {@code string} for matching in jcr escaped node names
+     *
+     * @param string string to escape
+     * @return escaped string
+     */
+    @Nonnull
+    public static String escape(String string) {
+        StringBuilder result = new StringBuilder();
+
+        int k = 0;
+        int j;
+        do {
+            j = string.indexOf('%', k); // split on %
+            if (j < 0) {
+                // jcr escape trail
+                result.append(Text.escapeIllegalJcrChars(string.substring(k)));
+            } else if (j > 0 && string.charAt(j - 1) == '\\') {
+                // literal occurrence of % -> jcr escape
+                result.append(Text.escapeIllegalJcrChars(string.substring(k, j) + '%'));
+            } else {
+                // wildcard occurrence of % -> jcr escape all but %
+                result.append(Text.escapeIllegalJcrChars(string.substring(k, j))).append('%');
+            }
+
+            k = j + 1;
+        } while (j >= 0);
+
+        return result.toString();
+    }
+
+    @Nonnull
+    private String getNtName(Class<? extends Authorizable> selector) {
+        String ntName;
+        if (User.class.isAssignableFrom(selector)) {
+            ntName = namePathMapper.getJcrName(UserConstants.NT_REP_USER);
+        } else if (Group.class.isAssignableFrom(selector)) {
+            ntName = namePathMapper.getJcrName(UserConstants.NT_REP_GROUP);
+        } else {
+            ntName = namePathMapper.getJcrName(UserConstants.NT_REP_AUTHORIZABLE);
+        }
+        if (ntName == null) {
+            log.warn("Failed to retrieve JCR name for authorizable node type.");
+            ntName = UserConstants.NT_REP_AUTHORIZABLE;
+        }
+        return ntName;
+    }
+
+    @Nonnull
+    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");
+        }
+    }
+
+    @Nonnull
+    private static RelationOp getCollation(QueryBuilder.Direction direction) throws RepositoryException
{
+        switch (direction) {
+            case ASCENDING:
+                return RelationOp.GT;
+            case DESCENDING:
+                return RelationOp.LT;
+            default:
+                throw new RepositoryException("Unknown sort order " + direction);
+        }
+    }
+
+    @Nonnull
+    @SuppressWarnings("unchecked")
+    private static Iterator<Node> execute(Query query) throws RepositoryException {
+        return query.execute().getNodes();
+    }
+
+    @Nonnull
+    private Iterator<Authorizable> toAuthorizables(Iterator<Node> nodes) {
+        Function<Node, Authorizable> transformer = new Function<Node, Authorizable>()
{
+            public Authorizable apply(Node node) {
+                try {
+                    return userManager.getAuthorizableByPath(node.getPath());
+                } catch (RepositoryException e) {
+                    log.warn("Cannot create authorizable from node {}", node);
+                    log.debug(e.getMessage(), e);
+                    return null;
+                }
+            }
+        };
+
+        return Iterators.transform(nodes, transformer);
+    }
+
+    @Nonnull
+    private Iterator<Authorizable> filter(Iterator<Authorizable> authorizables,
+                                          String groupName,
+                                          final boolean declaredMembersOnly) throws RepositoryException
{
+        Predicate<Authorizable> predicate;
+        Authorizable authorizable = userManager.getAuthorizable(groupName);
+        if (authorizable == null || !authorizable.isGroup()) {
+            predicate = Predicates.alwaysFalse();
+        } else {
+            final Group group = (Group) authorizable;
+            predicate = new Predicate<Authorizable>() {
+                public boolean apply(Authorizable authorizable) {
+                    try {
+                        return (declaredMembersOnly) ? group.isDeclaredMember(authorizable)
: group.isMember(authorizable);
+                    } catch (RepositoryException e) {
+                        log.debug("Cannot determine group membership for {}", authorizable,
e.getMessage());
+                        return false;
+                    }
+                }
+            };
+        }
+        return Iterators.filter(authorizables, predicate);
+    }
+}
\ No newline at end of file



Mime
View raw message