1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.directory.server.core.filtering;
21
22
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.List;
27
28 import org.apache.directory.server.core.cursor.ClosureMonitor;
29 import org.apache.directory.server.core.cursor.Cursor;
30 import org.apache.directory.server.core.cursor.CursorIterator;
31 import org.apache.directory.server.core.cursor.InvalidCursorPositionException;
32 import org.apache.directory.server.core.entry.ClonedServerEntry;
33 import org.apache.directory.server.core.entry.ServerEntry;
34 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
35 import org.apache.directory.shared.ldap.exception.OperationAbandonedException;
36 import org.apache.directory.shared.ldap.schema.AttributeType;
37 import org.apache.directory.shared.ldap.schema.AttributeTypeOptions;
38 import org.apache.directory.shared.ldap.schema.UsageEnum;
39
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43
44 /**
45 * A Cursor which uses a list of filters to selectively return entries and/or
46 * modify the contents of entries. Uses lazy pre-fetching on positioning
47 * operations which means adding filters after creation will not miss candidate
48 * entries.
49 *
50 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
51 * @version $Rev$, $Date$
52 */
53 public class BaseEntryFilteringCursor implements EntryFilteringCursor
54 {
55 /** the logger used by this class */
56 private static final Logger log = LoggerFactory.getLogger( BaseEntryFilteringCursor.class );
57
58 /** the underlying wrapped search results Cursor */
59 private final Cursor<ServerEntry> wrapped;
60
61 /** the parameters associated with the search operation */
62 private final SearchingOperationContext operationContext;
63
64 /** the list of filters to be applied */
65 private final List<EntryFilter> filters;
66
67 /** the first accepted search result that is pre fetched */
68 private ClonedServerEntry prefetched;
69
70
71 // ------------------------------------------------------------------------
72 // C O N S T R U C T O R S
73 // ------------------------------------------------------------------------
74
75
76 /**
77 * Creates a new entry filtering Cursor over an existing Cursor using a
78 * single filter initially: more can be added later after creation.
79 *
80 * @param wrapped the underlying wrapped Cursor whose entries are filtered
81 * @param searchControls the controls of search that created this Cursor
82 * @param invocation the search operation invocation creating this Cursor
83 * @param filter a single filter to be used
84 */
85 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped,
86 SearchingOperationContext operationContext, EntryFilter filter )
87 {
88 this( wrapped, operationContext, Collections.singletonList( filter ) );
89 }
90
91
92 /**
93 * Creates a new entry filtering Cursor over an existing Cursor using a
94 * no filter initially: more can be added later after creation.
95 *
96 * @param wrapped the underlying wrapped Cursor whose entries are filtered
97 * @param searchControls the controls of search that created this Cursor
98 * @param invocation the search operation invocation creating this Cursor
99 * @param filter a single filter to be used
100 */
101 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped, SearchingOperationContext operationContext )
102 {
103 this.wrapped = wrapped;
104 this.operationContext = operationContext;
105 this.filters = new ArrayList<EntryFilter>();
106 }
107
108
109 /**
110 * Creates a new entry filtering Cursor over an existing Cursor using a
111 * list of filters initially: more can be added later after creation.
112 *
113 * @param wrapped the underlying wrapped Cursor whose entries are filtered
114 * @param operationContext the operation context that created this Cursor
115 * @param invocation the search operation invocation creating this Cursor
116 * @param filters a list of filters to be used
117 */
118 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped,
119 SearchingOperationContext operationContext, List<EntryFilter> filters )
120 {
121 this.wrapped = wrapped;
122 this.operationContext = operationContext;
123 this.filters = new ArrayList<EntryFilter>();
124 this.filters.addAll( filters );
125 }
126
127
128 // ------------------------------------------------------------------------
129 // Class Specific Methods
130 // ------------------------------------------------------------------------
131
132
133 /* (non-Javadoc)
134 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isAbandoned()
135 */
136 public boolean isAbandoned()
137 {
138 return getOperationContext().isAbandoned();
139 }
140
141
142 /* (non-Javadoc)
143 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#setAbandoned(boolean)
144 */
145 public void setAbandoned( boolean abandoned )
146 {
147 getOperationContext().setAbandoned( abandoned );
148
149 if ( abandoned )
150 {
151 log.info( "Cursor has been abandoned." );
152 }
153 }
154
155
156 /* (non-Javadoc)
157 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#addEntryFilter(org.apache.directory.server.core.filtering.EntryFilter)
158 */
159 public boolean addEntryFilter( EntryFilter filter )
160 {
161 return filters.add( filter );
162 }
163
164
165 /* (non-Javadoc)
166 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#removeEntryFilter(org.apache.directory.server.core.filtering.EntryFilter)
167 */
168 public boolean removeEntryFilter( EntryFilter filter )
169 {
170 return filters.remove( filter );
171 }
172
173
174 /* (non-Javadoc)
175 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#getEntryFilters()
176 */
177 public List<EntryFilter> getEntryFilters()
178 {
179 return Collections.unmodifiableList( filters );
180 }
181
182
183 /* (non-Javadoc)
184 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#getOperationContext()
185 */
186 public SearchingOperationContext getOperationContext()
187 {
188 return operationContext;
189 }
190
191
192 // ------------------------------------------------------------------------
193 // Cursor Interface Methods
194 // ------------------------------------------------------------------------
195
196
197 /*
198 * @see Cursor#after(Object)
199 */
200 /* (non-Javadoc)
201 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#after(org.apache.directory.server.core.entry.ClonedServerEntry)
202 */
203 public void after( ClonedServerEntry element ) throws Exception
204 {
205 throw new UnsupportedOperationException();
206 }
207
208
209 /*
210 * @see Cursor#afterLast()
211 */
212 /* (non-Javadoc)
213 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#afterLast()
214 */
215 public void afterLast() throws Exception
216 {
217 wrapped.afterLast();
218 prefetched = null;
219 }
220
221
222 /*
223 * @see Cursor#available()
224 */
225 /* (non-Javadoc)
226 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#available()
227 */
228 public boolean available()
229 {
230 return prefetched != null;
231 }
232
233
234 /*
235 * @see Cursor#before(java.lang.Object)
236 */
237 /* (non-Javadoc)
238 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#before(org.apache.directory.server.core.entry.ClonedServerEntry)
239 */
240 public void before( ClonedServerEntry element ) throws Exception
241 {
242 throw new UnsupportedOperationException();
243 }
244
245
246 /*
247 * @see Cursor#beforeFirst()
248 */
249 /* (non-Javadoc)
250 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#beforeFirst()
251 */
252 public void beforeFirst() throws Exception
253 {
254 wrapped.beforeFirst();
255 prefetched = null;
256 }
257
258
259 /*
260 * @see Cursor#close()
261 */
262 /* (non-Javadoc)
263 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#close()
264 */
265 public void close() throws Exception
266 {
267 wrapped.close();
268 prefetched = null;
269 }
270
271
272 /*
273 * @see Cursor#close()
274 */
275 /* (non-Javadoc)
276 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#close()
277 */
278 public void close( Exception reason ) throws Exception
279 {
280 wrapped.close( reason );
281 prefetched = null;
282 }
283
284
285 public final void setClosureMonitor( ClosureMonitor monitor )
286 {
287 wrapped.setClosureMonitor( monitor );
288 }
289
290
291 /*
292 * @see Cursor#first()
293 */
294 /* (non-Javadoc)
295 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#first()
296 */
297 public boolean first() throws Exception
298 {
299 if ( getOperationContext().isAbandoned() )
300 {
301 log.info( "Cursor has been abandoned." );
302 close();
303 throw new OperationAbandonedException();
304 }
305
306 beforeFirst();
307 return next();
308 }
309
310
311 /*
312 * @see Cursor#get()
313 */
314 /* (non-Javadoc)
315 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#get()
316 */
317 public ClonedServerEntry get() throws Exception
318 {
319 if ( available() )
320 {
321 return prefetched;
322 }
323
324 throw new InvalidCursorPositionException();
325 }
326
327
328 /*
329 * @see Cursor#isClosed()
330 */
331 /* (non-Javadoc)
332 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isClosed()
333 */
334 public boolean isClosed() throws Exception
335 {
336 return wrapped.isClosed();
337 }
338
339
340 /*
341 * @see Cursor#isElementReused()
342 */
343 /* (non-Javadoc)
344 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isElementReused()
345 */
346 public boolean isElementReused()
347 {
348 return true;
349 }
350
351
352 /*
353 * @see Cursor#last()
354 */
355 /* (non-Javadoc)
356 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#last()
357 */
358 public boolean last() throws Exception
359 {
360 if ( getOperationContext().isAbandoned() )
361 {
362 log.info( "Cursor has been abandoned." );
363 close();
364 throw new OperationAbandonedException();
365 }
366
367 afterLast();
368 return previous();
369 }
370
371
372 private void filterContents( ClonedServerEntry entry ) throws Exception
373 {
374 boolean returnAll = getOperationContext().getReturningAttributes() == null ||
375 ( getOperationContext().isAllOperationalAttributes() && getOperationContext().isAllUserAttributes() );
376
377 if ( returnAll )
378 {
379 return;
380 }
381
382 if ( getOperationContext().isAllUserAttributes() )
383 {
384 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
385 {
386 boolean isNotRequested = true;
387
388 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
389 {
390 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
391 {
392 isNotRequested = false;
393 break;
394 }
395 }
396
397 boolean isNotUserAttribute = at.getUsage() != UsageEnum.USER_APPLICATIONS;
398
399 if ( isNotRequested && isNotUserAttribute )
400 {
401 entry.removeAttributes( at );
402 }
403 }
404
405 return;
406 }
407
408 if ( getOperationContext().isAllOperationalAttributes() )
409 {
410 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
411 {
412 boolean isNotRequested = true;
413
414 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
415 {
416 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
417 {
418 isNotRequested = false;
419 break;
420 }
421 }
422
423 boolean isUserAttribute = at.getUsage() == UsageEnum.USER_APPLICATIONS;
424
425 if ( isNotRequested && isUserAttribute )
426 {
427 entry.removeAttributes( at );
428 }
429 }
430
431 return;
432 }
433
434 if ( getOperationContext().isNoAttributes() )
435 {
436 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
437 {
438 boolean isNotRequested = true;
439
440 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
441 {
442 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
443 {
444 isNotRequested = false;
445 break;
446 }
447 }
448
449 if ( isNotRequested )
450 {
451 entry.removeAttributes( at );
452 }
453 }
454
455 return;
456 }
457
458 if ( getOperationContext().getReturningAttributes() != null )
459 {
460 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
461 {
462 boolean isNotRequested = true;
463
464 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
465 {
466 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
467 {
468 isNotRequested = false;
469 break;
470 }
471 }
472
473 if ( isNotRequested )
474 {
475 entry.removeAttributes( at );
476 }
477 }
478 }
479 }
480
481
482 /*
483 * @see Cursor#next()
484 */
485 /* (non-Javadoc)
486 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#next()
487 */
488 public boolean next() throws Exception
489 {
490 if ( getOperationContext().isAbandoned() )
491 {
492 log.info( "Cursor has been abandoned." );
493 close();
494 throw new OperationAbandonedException();
495 }
496
497 ClonedServerEntry tempResult = null;
498 outer: while ( wrapped.next() )
499 {
500 boolean accepted = true;
501
502 ServerEntry tempEntry = wrapped.get();
503 if ( tempEntry instanceof ClonedServerEntry )
504 {
505 tempResult = ( ClonedServerEntry ) tempEntry;
506 }
507 else
508 {
509 tempResult = new ClonedServerEntry( tempEntry );
510 }
511
512 /*
513 * O P T I M I Z A T I O N
514 * -----------------------
515 *
516 * Don't want to waste cycles on enabling a loop for processing
517 * filters if we have zero or one filter.
518 */
519
520 if ( filters.isEmpty() )
521 {
522 prefetched = tempResult;
523 filterContents( prefetched );
524 return true;
525 }
526
527 if ( filters.size() == 1 )
528 {
529 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) )
530 {
531 prefetched = tempResult;
532 filterContents( prefetched );
533 return true;
534 }
535 }
536
537 /* E N D O P T I M I Z A T I O N */
538
539 for ( EntryFilter filter : filters )
540 {
541 // if a filter rejects then short and continue with outer loop
542 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) )
543 {
544 continue outer;
545 }
546 }
547
548 /*
549 * Here the entry has been accepted by all filters.
550 */
551 prefetched = tempResult;
552 filterContents( prefetched );
553 return true;
554 }
555
556 prefetched = null;
557 return false;
558 }
559
560
561 /*
562 * @see Cursor#previous()
563 */
564 /* (non-Javadoc)
565 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#previous()
566 */
567 public boolean previous() throws Exception
568 {
569 if ( getOperationContext().isAbandoned() )
570 {
571 log.info( "Cursor has been abandoned." );
572 close();
573 throw new OperationAbandonedException();
574 }
575
576 ClonedServerEntry tempResult = null;
577 outer: while ( wrapped.previous() )
578 {
579 boolean accepted = true;
580 tempResult = new ClonedServerEntry( wrapped.get() );
581
582 /*
583 * O P T I M I Z A T I O N
584 * -----------------------
585 *
586 * Don't want to waste cycles on enabling a loop for processing
587 * filters if we have zero or one filter.
588 */
589
590 if ( filters.isEmpty() )
591 {
592 prefetched = tempResult;
593 filterContents( prefetched );
594 return true;
595 }
596
597 if ( filters.size() == 1 )
598 {
599 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) )
600 {
601 prefetched = tempResult;
602 filterContents( prefetched );
603 return true;
604 }
605 }
606
607 /* E N D O P T I M I Z A T I O N */
608
609 for ( EntryFilter filter : filters )
610 {
611 // if a filter rejects then short and continue with outer loop
612 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) )
613 {
614 continue outer;
615 }
616 }
617
618 /*
619 * Here the entry has been accepted by all filters.
620 */
621 prefetched = tempResult;
622 filterContents( prefetched );
623 return true;
624 }
625
626 prefetched = null;
627 return false;
628 }
629
630
631 /*
632 * @see Iterable#iterator()
633 */
634 /* (non-Javadoc)
635 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#iterator()
636 */
637 public Iterator<ClonedServerEntry> iterator()
638 {
639 return new CursorIterator<ClonedServerEntry>( this );
640 }
641 }