Hi,

I'd like to raise a concern here about the XPath query builder that made
its way into oak-jcr with this commit.

There is no native XPath support in Oak. Currently the XPath queries are
beaing translated into (more or less) equivalent SQL2 queries. See
also OAK-225.
So under these circumstances it doesn't make sense to build a query
programatically as XPath just to have it translated into SQL2 at a later
stage.

thoughts?

thanks,
alex


On Wed, Aug 15, 2012 at 3:24 PM, <ang...@apache.org> wrote:

> 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
>
>
>

Reply via email to