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