001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.server.core.filtering;
021
022
023 import java.util.ArrayList;
024 import java.util.Collections;
025 import java.util.Iterator;
026 import java.util.List;
027
028 import org.apache.directory.server.core.entry.ClonedServerEntry;
029 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
030 import org.apache.directory.shared.ldap.cursor.ClosureMonitor;
031 import org.apache.directory.shared.ldap.cursor.Cursor;
032 import org.apache.directory.shared.ldap.cursor.CursorIterator;
033 import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
034 import org.apache.directory.shared.ldap.entry.ServerEntry;
035 import org.apache.directory.shared.ldap.exception.OperationAbandonedException;
036 import org.apache.directory.shared.ldap.schema.AttributeType;
037 import org.apache.directory.shared.ldap.schema.AttributeTypeOptions;
038 import org.apache.directory.shared.ldap.schema.UsageEnum;
039
040 import org.slf4j.Logger;
041 import org.slf4j.LoggerFactory;
042
043
044 /**
045 * A Cursor which uses a list of filters to selectively return entries and/or
046 * modify the contents of entries. Uses lazy pre-fetching on positioning
047 * operations which means adding filters after creation will not miss candidate
048 * entries.
049 *
050 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
051 * @version $Rev$, $Date$
052 */
053 public class BaseEntryFilteringCursor implements EntryFilteringCursor
054 {
055 /** the logger used by this class */
056 private static final Logger log = LoggerFactory.getLogger( BaseEntryFilteringCursor.class );
057
058 /** the underlying wrapped search results Cursor */
059 private final Cursor<ServerEntry> wrapped;
060
061 /** the parameters associated with the search operation */
062 private final SearchingOperationContext operationContext;
063
064 /** the list of filters to be applied */
065 private final List<EntryFilter> filters;
066
067 /** the first accepted search result that is pre fetched */
068 private ClonedServerEntry prefetched;
069
070
071 // ------------------------------------------------------------------------
072 // C O N S T R U C T O R S
073 // ------------------------------------------------------------------------
074
075
076 /**
077 * Creates a new entry filtering Cursor over an existing Cursor using a
078 * single filter initially: more can be added later after creation.
079 *
080 * @param wrapped the underlying wrapped Cursor whose entries are filtered
081 * @param searchControls the controls of search that created this Cursor
082 * @param invocation the search operation invocation creating this Cursor
083 * @param filter a single filter to be used
084 */
085 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped,
086 SearchingOperationContext operationContext, EntryFilter filter )
087 {
088 this( wrapped, operationContext, Collections.singletonList( filter ) );
089 }
090
091
092 /**
093 * Creates a new entry filtering Cursor over an existing Cursor using a
094 * no filter initially: more can be added later after creation.
095 *
096 * @param wrapped the underlying wrapped Cursor whose entries are filtered
097 * @param searchControls the controls of search that created this Cursor
098 * @param invocation the search operation invocation creating this Cursor
099 * @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 typesOnly = getOperationContext().isTypesOnly();
375
376 boolean returnAll = ( getOperationContext().getReturningAttributes() == null ||
377 ( getOperationContext().isAllOperationalAttributes() && getOperationContext().isAllUserAttributes() ) ) && ( ! typesOnly );
378
379 if ( returnAll )
380 {
381 return;
382 }
383
384 if ( getOperationContext().isNoAttributes() )
385 {
386 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
387 {
388 entry.remove( entry.get( at ) );
389 }
390
391 return;
392 }
393
394
395 if ( getOperationContext().isAllUserAttributes() )
396 {
397 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
398 {
399 boolean isNotRequested = true;
400
401 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
402 {
403 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
404 {
405 isNotRequested = false;
406 break;
407 }
408 }
409
410 boolean isNotUserAttribute = at.getUsage() != UsageEnum.USER_APPLICATIONS;
411
412 if ( isNotRequested && isNotUserAttribute )
413 {
414 entry.removeAttributes( at );
415 }
416 else if( typesOnly )
417 {
418 entry.get( at ).clear();
419 }
420 }
421
422 return;
423 }
424
425 if ( getOperationContext().isAllOperationalAttributes() )
426 {
427 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
428 {
429 boolean isNotRequested = true;
430
431 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
432 {
433 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
434 {
435 isNotRequested = false;
436 break;
437 }
438 }
439
440 boolean isUserAttribute = at.getUsage() == UsageEnum.USER_APPLICATIONS;
441
442 if ( isNotRequested && isUserAttribute )
443 {
444 entry.removeAttributes( at );
445 }
446 else if( typesOnly )
447 {
448 entry.get( at ).clear();
449 }
450 }
451
452 return;
453 }
454
455 if ( getOperationContext().getReturningAttributes() != null )
456 {
457 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() )
458 {
459 boolean isNotRequested = true;
460
461 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() )
462 {
463 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) )
464 {
465 isNotRequested = false;
466 break;
467 }
468 }
469
470 if ( isNotRequested )
471 {
472 entry.removeAttributes( at );
473 }
474 else if( typesOnly )
475 {
476 entry.get( at ).clear();
477 }
478 }
479 }
480 }
481
482
483 /*
484 * @see Cursor#next()
485 */
486 /* (non-Javadoc)
487 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#next()
488 */
489 public boolean next() throws Exception
490 {
491 if ( getOperationContext().isAbandoned() )
492 {
493 log.info( "Cursor has been abandoned." );
494 close();
495 throw new OperationAbandonedException();
496 }
497
498 ClonedServerEntry tempResult = null;
499 outer: while ( wrapped.next() )
500 {
501 boolean accepted = true;
502
503 ServerEntry tempEntry = wrapped.get();
504 if ( tempEntry instanceof ClonedServerEntry )
505 {
506 tempResult = ( ClonedServerEntry ) tempEntry;
507 }
508 else
509 {
510 tempResult = new ClonedServerEntry( tempEntry );
511 }
512
513 /*
514 * O P T I M I Z A T I O N
515 * -----------------------
516 *
517 * Don't want to waste cycles on enabling a loop for processing
518 * filters if we have zero or one filter.
519 */
520
521 if ( filters.isEmpty() )
522 {
523 prefetched = tempResult;
524 filterContents( prefetched );
525 return true;
526 }
527
528 if ( filters.size() == 1 )
529 {
530 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) )
531 {
532 prefetched = tempResult;
533 filterContents( prefetched );
534 return true;
535 }
536 }
537
538 /* E N D O P T I M I Z A T I O N */
539
540 for ( EntryFilter filter : filters )
541 {
542 // if a filter rejects then short and continue with outer loop
543 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) )
544 {
545 continue outer;
546 }
547 }
548
549 /*
550 * Here the entry has been accepted by all filters.
551 */
552 prefetched = tempResult;
553 filterContents( prefetched );
554 return true;
555 }
556
557 prefetched = null;
558 return false;
559 }
560
561
562 /*
563 * @see Cursor#previous()
564 */
565 /* (non-Javadoc)
566 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#previous()
567 */
568 public boolean previous() throws Exception
569 {
570 if ( getOperationContext().isAbandoned() )
571 {
572 log.info( "Cursor has been abandoned." );
573 close();
574 throw new OperationAbandonedException();
575 }
576
577 ClonedServerEntry tempResult = null;
578 outer: while ( wrapped.previous() )
579 {
580 boolean accepted = true;
581 tempResult = new ClonedServerEntry( wrapped.get() );
582
583 /*
584 * O P T I M I Z A T I O N
585 * -----------------------
586 *
587 * Don't want to waste cycles on enabling a loop for processing
588 * filters if we have zero or one filter.
589 */
590
591 if ( filters.isEmpty() )
592 {
593 prefetched = tempResult;
594 filterContents( prefetched );
595 return true;
596 }
597
598 if ( filters.size() == 1 )
599 {
600 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) )
601 {
602 prefetched = tempResult;
603 filterContents( prefetched );
604 return true;
605 }
606 }
607
608 /* E N D O P T I M I Z A T I O N */
609
610 for ( EntryFilter filter : filters )
611 {
612 // if a filter rejects then short and continue with outer loop
613 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) )
614 {
615 continue outer;
616 }
617 }
618
619 /*
620 * Here the entry has been accepted by all filters.
621 */
622 prefetched = tempResult;
623 filterContents( prefetched );
624 return true;
625 }
626
627 prefetched = null;
628 return false;
629 }
630
631
632 /*
633 * @see Iterable#iterator()
634 */
635 /* (non-Javadoc)
636 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#iterator()
637 */
638 public Iterator<ClonedServerEntry> iterator()
639 {
640 return new CursorIterator<ClonedServerEntry>( this );
641 }
642 }