View Javadoc

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 }