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  import java.util.Hashtable;
24  
25  import javax.naming.NamingEnumeration;
26  import javax.naming.NamingException;
27  import javax.naming.Context;
28  import javax.naming.Name;
29  import javax.naming.spi.DirectoryManager;
30  import javax.naming.directory.SearchControls;
31  import javax.naming.directory.SearchResult;
32  import javax.naming.directory.Attributes;
33  import javax.naming.directory.DirContext;
34  
35  //import org.slf4j.Logger;
36  //import org.slf4j.LoggerFactory;
37  import org.apache.ldap.server.invocation.Invocation;
38  import org.apache.ldap.common.name.LdapName;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  
43  /***
44   * A enumeration decorator which filters database search results as they are
45   * being enumerated back to the client caller.
46   *
47   * @see SearchResultFilter
48   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
49   * @version $Rev: 328266 $
50   */
51  public class SearchResultFilteringEnumeration implements NamingEnumeration
52  {
53      /*** the logger used by this class */
54      private static final Logger log = LoggerFactory.getLogger( SearchResultFilteringEnumeration.class );
55  
56      /*** the list of filters to be applied */
57      private final List filters;
58      /*** the underlying decorated enumeration */
59      private final NamingEnumeration decorated;
60  
61      /*** the first accepted search result that is prefetched */
62      private SearchResult prefetched;
63      /*** flag storing closed state of this naming enumeration */
64      private boolean isClosed = false;
65      /*** the controls associated with the search operation */
66      private final SearchControls searchControls;
67      /*** the Invocation that representing the search creating this enumeration */
68      private final Invocation invocation;
69      /*** whether or not the caller context has object factories which need to be applied to the results */
70      private final boolean applyObjectFactories;
71  
72  
73      // ------------------------------------------------------------------------
74      // C O N S T R U C T O R S
75      // ------------------------------------------------------------------------
76  
77  
78      /***
79       * Creates a new database result filtering enumeration to decorate an
80       * underlying enumeration.
81       *
82       * @param decorated the underlying decorated enumeration
83       * @param searchControls the search controls associated with the search
84       * creating this enumeration
85       * @param invocation the invocation representing the seach that created this enumeration
86       */
87      public SearchResultFilteringEnumeration( NamingEnumeration decorated,
88                                               SearchControls searchControls,
89                                               Invocation invocation,
90                                               SearchResultFilter filter )
91              throws NamingException
92      {
93          this.searchControls = searchControls;
94          this.invocation = invocation;
95          this.filters = new ArrayList();
96          this.filters.add( filter );
97          this.decorated = decorated;
98          this.applyObjectFactories = invocation.getCaller().getEnvironment().containsKey( Context.OBJECT_FACTORIES );
99  
100         if ( ! decorated.hasMore() )
101         {
102             close();
103             return;
104         }
105 
106         prefetch();
107     }
108 
109 
110     /***
111      * Creates a new database result filtering enumeration to decorate an
112      * underlying enumeration.
113      *
114      * @param decorated the underlying decorated enumeration
115      * @param searchControls the search controls associated with the search
116      * creating this enumeration
117      * @param invocation the invocation representing the seach that created this enumeration
118      */
119     public SearchResultFilteringEnumeration( NamingEnumeration decorated,
120                                              SearchControls searchControls,
121                                              Invocation invocation,
122                                              List filters )
123             throws NamingException
124     {
125         this.searchControls = searchControls;
126         this.invocation = invocation;
127         this.filters = new ArrayList();
128         this.filters.addAll( filters );
129         this.decorated = decorated;
130         this.applyObjectFactories = invocation.getCaller().getEnvironment().containsKey( Context.OBJECT_FACTORIES );
131 
132         if ( ! decorated.hasMore() )
133         {
134             close();
135             return;
136         }
137 
138         prefetch();
139     }
140 
141 
142     // ------------------------------------------------------------------------
143     // New SearchResultFilter management methods
144     // ------------------------------------------------------------------------
145 
146 
147     /***
148      * Adds a database search result filter to this filtering enumeration at
149      * the very end of the filter list.  Filters are applied in the order of
150      * addition.
151      *
152      * @param filter a filter to apply to the results
153      * @return the result of {@link List#add(Object)}
154      */
155     public boolean addResultFilter( SearchResultFilter filter )
156     {
157         return filters.add( filter );
158     }
159 
160 
161     /***
162      * Removes a database search result filter from the filter list of this
163      * filtering enumeration.
164      *
165      * @param filter a filter to remove from the filter list
166      * @return the result of {@link List#remove(Object)}
167      */
168     public boolean removeResultFilter( SearchResultFilter filter )
169     {
170         return filters.remove( filter );
171     }
172 
173 
174     /***
175      * Gets an unmodifiable list of filters.
176      *
177      * @return the result of {@link Collections#unmodifiableList(List)}
178      */
179     public List getFilters()
180     {
181         return Collections.unmodifiableList( filters );
182     }
183 
184 
185     // ------------------------------------------------------------------------
186     // NamingEnumeration Methods
187     // ------------------------------------------------------------------------
188 
189 
190     public void close() throws NamingException
191     {
192         isClosed = true;
193         decorated.close();
194     }
195 
196 
197     public boolean hasMore()
198     {
199         return !isClosed;
200     }
201 
202 
203     public Object next() throws NamingException
204     {
205         SearchResult retVal = this.prefetched;
206         prefetch();
207         return retVal;
208     }
209 
210 
211     // ------------------------------------------------------------------------
212     // Enumeration Methods
213     // ------------------------------------------------------------------------
214 
215 
216     public boolean hasMoreElements()
217     {
218         return !isClosed;
219     }
220 
221 
222     public Object nextElement()
223     {
224         SearchResult retVal = this.prefetched;
225 
226         try
227         {
228             prefetch();
229         }
230         catch ( NamingException e )
231         {
232             throw new RuntimeException( "Failed to prefetch.", e );
233         }
234 
235         return retVal;
236     }
237 
238 
239     // ------------------------------------------------------------------------
240     // Private utility methods
241     // ------------------------------------------------------------------------
242 
243 
244     private void applyObjectFactories( SearchResult result ) throws NamingException
245     {
246         // if already populated or no factories are available just return
247         if ( result.getObject() != null || ! applyObjectFactories )
248         {
249             return;
250         }
251 
252         DirContext ctx = ( DirContext ) invocation.getCaller();
253         Hashtable env = ctx.getEnvironment();
254         Attributes attrs = result.getAttributes();
255         Name name = new LdapName( result.getName() );
256         try
257         {
258             Object obj = DirectoryManager.getObjectInstance( null, name, ctx, env, attrs );
259             result.setObject( obj );
260         }
261         catch ( Exception e )
262         {
263             StringBuffer buf = new StringBuffer();
264             buf.append( "ObjectFactories threw exception while attempting to generate an object for " );
265             buf.append( result.getName() );
266             buf.append( ". Call on SearchResult.getObject() will return null." );
267             log.warn( buf.toString(), e );
268         }
269     }
270 
271 
272     /***
273      * Keeps getting results from the underlying decorated filter and applying
274      * the filters until a result is accepted by all and set as the prefetced
275      * result to return on the next() result request.  If no prefetched value
276      * can be found before exhausting the decorated enumeration, then this and
277      * the underlying enumeration is closed.
278      *
279      * @throws NamingException if there are problems getting results from the
280      * underlying enumeration
281      */
282     private void prefetch() throws NamingException
283     {
284         SearchResult tmp;
285 
286         outer:
287         while( decorated.hasMore() )
288         {
289             boolean accepted = true;
290             tmp = ( SearchResult ) decorated.next();
291 
292             // don't waste using a for loop if we got 0 or 1 element
293             if ( filters.isEmpty() )
294             {
295                 this.prefetched = tmp;
296                 applyObjectFactories( this.prefetched );
297                 return;
298             }
299             else if ( filters.size() == 1 )
300             {
301                 accepted = ( ( SearchResultFilter ) filters.get( 0 ) )
302                         .accept( invocation, tmp, searchControls );
303                 if ( accepted )
304                 {
305                     this.prefetched = tmp;
306                     applyObjectFactories( this.prefetched );
307                     return;
308                 }
309 
310                 continue;
311             }
312 
313             // apply all filters shorting their application on result denials
314             for ( int ii = 0; ii < filters.size(); ii ++ )
315             {
316                 SearchResultFilter filter = ( SearchResultFilter ) filters.get( ii );
317                 accepted &= filter.accept( invocation, tmp, searchControls );
318 
319                 if ( ! accepted )
320                 {
321                     continue outer;
322                 }
323             }
324 
325             /*
326              * If we get here then a result has been accepted by all the
327              * filters so we set the result as the prefetched value to return
328              * on the following call to the next() or nextElement() methods
329              */
330             this.prefetched = tmp;
331             applyObjectFactories( this.prefetched );
332             return;
333         }
334 
335         /*
336          * If we get here then no result was found to be accepted by all
337          * filters before we exhausted the decorated enumeration so we close
338          */
339         close();
340     }
341 }