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