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