View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.db;
18  
19  
20  import javax.naming.NamingEnumeration;
21  import javax.naming.NamingException;
22  import javax.naming.directory.SearchControls;
23  import javax.naming.directory.SearchResult;
24  import javax.naming.ldap.LdapContext;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  
29  
30  /***
31   * A enumeration decorator which filters database search results as they are
32   * being enumerated back to the client caller.
33   *
34   * @see SearchResultFilter
35   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
36   * @version $Rev: 159259 $
37   */
38  public class ResultFilteringEnumeration implements NamingEnumeration
39  {
40      /*** the list of filters to be applied */
41      private final List filters;
42      /*** the underlying decorated enumeration */
43      private final NamingEnumeration decorated;
44  
45      /*** the first accepted search result that is prefetched */
46      private SearchResult prefetched;
47      /*** flag storing closed state of this naming enumeration */
48      private boolean isClosed = false;
49      /*** the controls associated with the search operation */
50      private final SearchControls searchControls;
51      /*** the LDAP context that made the search creating this enumeration */
52      private final LdapContext ctx;
53  
54  
55      // ------------------------------------------------------------------------
56      // C O N S T R U C T O R S
57      // ------------------------------------------------------------------------
58  
59  
60      /***
61       * Creates a new database result filtering enumeration to decorate an
62       * underlying enumeration.
63       *
64       * @param decorated the underlying decorated enumeration
65       * @param searchControls the search controls associated with the search
66       * creating this enumeration
67       * @param ctx the LDAP context that made the search creating this
68       * enumeration
69       */
70      public ResultFilteringEnumeration( NamingEnumeration decorated,
71                                         SearchControls searchControls,
72                                         LdapContext ctx,
73                                         SearchResultFilter filter )
74              throws NamingException
75      {
76          this.searchControls = searchControls;
77          this.ctx = ctx;
78          this.filters = new ArrayList();
79          this.filters.add( filter );
80          this.decorated = decorated;
81  
82          if ( ! decorated.hasMore() )
83          {
84              close();
85              return;
86          }
87  
88          prefetch();
89      }
90  
91  
92      /***
93       * Creates a new database result filtering enumeration to decorate an
94       * underlying enumeration.
95       *
96       * @param decorated the underlying decorated enumeration
97       * @param searchControls the search controls associated with the search
98       * creating this enumeration
99       * @param ctx the LDAP context that made the search creating this
100      * enumeration
101      */
102     public ResultFilteringEnumeration( NamingEnumeration decorated,
103                                        SearchControls searchControls,
104                                        LdapContext ctx,
105                                        List filters )
106             throws NamingException
107     {
108         this.searchControls = searchControls;
109         this.ctx = ctx;
110         this.filters = new ArrayList();
111         this.filters.addAll( filters );
112         this.decorated = decorated;
113 
114         if ( ! decorated.hasMore() )
115         {
116             close();
117             return;
118         }
119 
120         prefetch();
121     }
122 
123 
124     // ------------------------------------------------------------------------
125     // New SearchResultFilter management methods
126     // ------------------------------------------------------------------------
127 
128 
129     /***
130      * Adds a database search result filter to this filtering enumeration at
131      * the very end of the filter list.  Filters are applied in the order of
132      * addition.
133      *
134      * @param filter a filter to apply to the results
135      * @return the result of {@link List#add(Object)}
136      */
137     public boolean addResultFilter( SearchResultFilter filter )
138     {
139         return filters.add( filter );
140     }
141 
142 
143     /***
144      * Removes a database search result filter from the filter list of this
145      * filtering enumeration.
146      *
147      * @param filter a filter to remove from the filter list
148      * @return the result of {@link List#remove(Object)}
149      */
150     public boolean removeResultFilter( SearchResultFilter filter )
151     {
152         return filters.remove( filter );
153     }
154 
155 
156     /***
157      * Gets an unmodifiable list of filters.
158      *
159      * @return the result of {@link Collections#unmodifiableList(List)}
160      */
161     public List getFilters()
162     {
163         return Collections.unmodifiableList( filters );
164     }
165 
166 
167     // ------------------------------------------------------------------------
168     // NamingEnumeration Methods
169     // ------------------------------------------------------------------------
170 
171 
172     public void close() throws NamingException
173     {
174         isClosed = true;
175         decorated.close();
176     }
177 
178 
179     public boolean hasMore()
180     {
181         return !isClosed;
182     }
183 
184 
185     public Object next() throws NamingException
186     {
187         SearchResult retVal = this.prefetched;
188         prefetch();
189         return retVal;
190     }
191 
192 
193     // ------------------------------------------------------------------------
194     // Enumeration Methods
195     // ------------------------------------------------------------------------
196 
197 
198     public boolean hasMoreElements()
199     {
200         return !isClosed;
201     }
202 
203 
204     public Object nextElement()
205     {
206         SearchResult retVal = this.prefetched;
207 
208         try
209         {
210             prefetch();
211         }
212         catch ( NamingException e )
213         {
214             e.printStackTrace();
215         }
216 
217         return retVal;
218     }
219 
220 
221     // ------------------------------------------------------------------------
222     // Private utility methods
223     // ------------------------------------------------------------------------
224 
225 
226     /***
227      * Keeps getting results from the underlying decorated filter and applying
228      * the filters until a result is accepted by all and set as the prefetced
229      * result to return on the next() result request.  If no prefetched value
230      * can be found before exhausting the decorated enumeration, then this and
231      * the underlying enumeration is closed.
232      *
233      * @throws NamingException if there are problems getting results from the
234      * underlying enumeration
235      */
236     private void prefetch() throws NamingException
237     {
238         SearchResult tmp = null;
239 
240         while( decorated.hasMore() )
241         {
242             boolean accepted = true;
243             tmp = ( SearchResult ) decorated.next();
244 
245             // don't waste using a for loop if we got 0 or 1 element
246             if ( filters.isEmpty() )
247             {
248                 this.prefetched = tmp;
249                 return;
250             }
251             else if ( filters.size() == 1 )
252             {
253                 accepted = ( ( SearchResultFilter ) filters.get( 0 ) )
254                         .accept( ctx, tmp, searchControls );
255                 if ( accepted )
256                 {
257                     this.prefetched = tmp;
258                     return;
259                 }
260 
261                 continue;
262             }
263 
264             // apply all filters shorting their application on result denials
265             for ( int ii = 0; ii < filters.size(); ii ++ )
266             {
267                 SearchResultFilter filter = ( SearchResultFilter ) filters.get( ii );
268                 accepted &= filter.accept( ctx, tmp, searchControls );
269 
270                 if ( ! accepted )
271                 {
272                     continue;
273                 }
274             }
275 
276             /*
277              * If we get here then a result has been accepted by all the
278              * filters so we set the result as the prefetched value to return
279              * on the following call to the next() or nextElement() methods
280              */
281             this.prefetched = tmp;
282             return;
283         }
284 
285         /*
286          * If we get here then no result was found to be accepted by all
287          * filters before we exhausted the decorated enumeration so we close
288          */
289         close();
290     }
291 }