1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
36
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
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
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
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
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
241
242
243
244 private void applyObjectFactories( SearchResult result ) throws NamingException
245 {
246
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
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
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
327
328
329
330 this.prefetched = tmp;
331 applyObjectFactories( this.prefetched );
332 return;
333 }
334
335
336
337
338
339 close();
340 }
341 }