I've created OAK-253 best, alex
https://issues.apache.org/jira/browse/OAK-253 On Fri, Aug 17, 2012 at 11:13 AM, Alex Parvulescu <[email protected] > wrote: > Hi Angela, > > I fully agree with you about the need to have this code in oak. > That is why my email started with... > > > I'd like to raise a concern > > XPath to SQL2 conversion is a WIP, the level of support for XPath queries > is still under discussion, which makes the platform you are building the > user parts on unstable. > I just wanted to make sure that the risk is known. > > On the other hand, I'm sure that the extensive range of tests that cover > this functionality assures us that there are no obvious problems. > > > do you volunteer to take care of that? that would be perfect. > I'll create an issue to make sure we don't forget to come back to the code > later on. > > > thanks, > alex > > > On Fri, Aug 17, 2012 at 11:01 AM, Angela Schreiber <[email protected]>wrote: > >> hi alex >> >> for backward compatibility the form jr-user-query implementation >> and the authorizable-query-utility present in jackrabbit-jcr-commons >> is required to work as is as nobody will have time to fix all >> usages of that in our products. >> >> we may - just in case we have time left - add a additional implementation >> of michael's user-query API and start deprecating >> the old one in a subsequent release of oak. do you volunteer >> to take care of that? that would be perfect. >> >> kind regards >> angela >> >> >> On 8/17/12 10:43 AM, Alex Parvulescu wrote: >> >>> 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,<[email protected]> wrote: >>> >>> Author: angela >>>> Date: Wed Aug 15 13:24:21 2012 >>>> New Revision: 1373392 >>>> >>>> URL: >>>> http://svn.apache.org/viewvc?**rev=1373392&view=rev<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<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<http://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<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<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<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<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<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<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<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<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<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<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<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 >>>> >>>> >>>> >>>> >
