Return-Path: X-Original-To: apmail-jackrabbit-oak-dev-archive@minotaur.apache.org Delivered-To: apmail-jackrabbit-oak-dev-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D0BC8D7FE for ; Fri, 17 Aug 2012 09:50:50 +0000 (UTC) Received: (qmail 99161 invoked by uid 500); 17 Aug 2012 09:37:05 -0000 Delivered-To: apmail-jackrabbit-oak-dev-archive@jackrabbit.apache.org Received: (qmail 98848 invoked by uid 500); 17 Aug 2012 09:36:47 -0000 Mailing-List: contact oak-dev-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: oak-dev@jackrabbit.apache.org Delivered-To: mailing list oak-dev@jackrabbit.apache.org Received: (qmail 98159 invoked by uid 99); 17 Aug 2012 09:36:27 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 17 Aug 2012 09:36:27 +0000 X-ASF-Spam-Status: No, hits=-1.3 required=5.0 tests=FRT_ADOBE2,RCVD_IN_DNSWL_MED,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (nike.apache.org: domain of anchela@adobe.com designates 64.18.1.208 as permitted sender) Received: from [64.18.1.208] (HELO exprod6og107.obsmtp.com) (64.18.1.208) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 17 Aug 2012 09:36:11 +0000 Received: from outbound-smtp-2.corp.adobe.com ([193.104.215.16]) by exprod6ob107.postini.com ([64.18.5.12]) with SMTP ID DSNKUC4QdWOuQO3QNonX/XWyYC8ZhwLR+vE/@postini.com; Fri, 17 Aug 2012 02:35:50 PDT Received: from inner-relay-4.eur.adobe.com (inner-relay-4b [10.128.4.237]) by outbound-smtp-2.corp.adobe.com (8.12.10/8.12.10) with ESMTP id q7H9Zmbq002371 for ; Fri, 17 Aug 2012 02:35:48 -0700 (PDT) Received: from nahub02.corp.adobe.com (nahub02.corp.adobe.com [10.8.189.98]) by inner-relay-4.eur.adobe.com (8.12.10/8.12.9) with ESMTP id q7H9ZkYr005165 for ; Fri, 17 Aug 2012 02:35:47 -0700 (PDT) Received: from eurhub01.eur.adobe.com (10.128.4.30) by nahub02.corp.adobe.com (10.8.189.98) with Microsoft SMTP Server (TLS) id 8.3.264.0; Fri, 17 Aug 2012 02:35:45 -0700 Received: from angela.corp.adobe.com (10.132.1.18) by eurhub01.eur.adobe.com (10.128.4.111) with Microsoft SMTP Server id 8.3.264.0; Fri, 17 Aug 2012 10:35:43 +0100 Message-ID: <502E106F.90206@adobe.com> Date: Fri, 17 Aug 2012 11:35:43 +0200 From: Angela Schreiber User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.18) Gecko/20110616 Thunderbird/3.1.11 MIME-Version: 1.0 To: Subject: Re: svn commit: r1373392 - in /jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user: ./ query/ References: <20120815132422.4090923888FE@eris.apache.org> <502E0853.1080609@adobe.com> In-Reply-To: Content-Type: text/plain; charset="ISO-8859-1"; format=flowed Content-Transfer-Encoding: 7bit hi alex the user management is not enabled so far as there are still too many missing parts in oak... for example the weakreferences and proper support for reference values. in other words: right now it doesn't bother me if the query conversion isn't completed as i can't run the tests anyway. as far as the UserQuery tests are concerned they make use of of an API method that is not yet implemented anyway. so, don't feel urged to rush in your query conversion :) thanks a lot + regards angela On 8/17/12 11:13 AM, Alex Parvulescu 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 Schreiberwrote: > >> 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, 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 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 { >>>> + private final List conditions = new >>>> ArrayList(); >>>> + >>>> + 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 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 element type of the query results >>>> + * >>>> + * TODO move to query-commons ? >>>> + */ >>>> +public class ResultIterator implements Iterator { >>>> + >>>> + public final static int OFFSET_NONE = 0; >>>> + public final static int MAX_ALL = -1; >>>> + >>>> + private final Iterator 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 >>>> 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 element type >>>> + * @return an iterator which only returns the elements in the given >>>> bounds >>>> + */ >>>> + public static Iterator create(long offset, long max, >>>> Iterator iterator) { >>>> + if (offset == OFFSET_NONE&& max == MAX_ALL) { >>>> >>>> + // no constraints on offset nor max -> return the original >>>> iterator. >>>> + return iterator; >>>> + } else { >>>> + return new ResultIterator(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 { >>>> + >>>> + private Class 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 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 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 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 result = >>>> toAuthorizables(execute(query)**); >>>> + Iterator 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 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 execute(Query query) throws >>>> RepositoryException { >>>> + return query.execute().getNodes(); >>>> + } >>>> + >>>> + @Nonnull >>>> + private Iterator toAuthorizables(Iterator >>>> nodes) { >>>> + Function transformer = new Function>>> 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 filter(Iterator >>>> authorizables, >>>> + String groupName, >>>> + final boolean >>>> declaredMembersOnly) throws RepositoryException { >>>> + Predicate predicate; >>>> + Authorizable authorizable = >>>> userManager.getAuthorizable(**groupName); >>>> + if (authorizable == null || !authorizable.isGroup()) { >>>> + predicate = Predicates.alwaysFalse(); >>>> + } else { >>>> + final Group group = (Group) authorizable; >>>> + predicate = new Predicate() { >>>> + 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 >>>> >>>> >>>> >>>>