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.xdbm.search.cursor;
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.Cursor;
30  import org.apache.directory.api.ldap.model.cursor.CursorException;
31  import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
32  import org.apache.directory.api.ldap.model.exception.LdapException;
33  import org.apache.directory.api.ldap.model.filter.ExprNode;
34  import org.apache.directory.server.core.api.partition.PartitionTxn;
35  import org.apache.directory.server.i18n.I18n;
36  import org.apache.directory.server.xdbm.AbstractIndexCursor;
37  import org.apache.directory.server.xdbm.IndexEntry;
38  import org.apache.directory.server.xdbm.search.Evaluator;
39  import org.apache.directory.server.xdbm.search.impl.ScanCountComparator;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  
44  /**
45   * A Cursor returning candidates satisfying a logical conjunction expression.
46   *
47   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
48   */
49  public class AndCursor<V> extends AbstractIndexCursor<V>
50  {
51      /** A dedicated log for cursors */
52      private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
53  
54      /** Speedup for logs */
55      private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
56  
57      /** The message for unsupported operations */
58      private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_707 );
59  
60      /** */
61      private final Cursor<IndexEntry<V, String>> wrapped;
62  
63      /** The evaluators used for the members of the And filter */
64      private final List<Evaluator<? extends ExprNode>> evaluators;
65  
66  
67      /**
68       * Creates an instance of a AndCursor. It wraps an index cursor and the list
69       * of evaluators associated with all the elements connected by the And.
70       * 
71       * @param partitionTxn The transaction to use
72       * @param wrapped The encapsulated IndexCursor
73       * @param evaluators The list of evaluators associated with the elements
74       */
75      public AndCursor( PartitionTxn partitionTxn, Cursor<IndexEntry<V, String>> wrapped,
76          List<Evaluator<? extends ExprNode>> evaluators )
77      {
78          if ( IS_DEBUG )
79          {
80              LOG_CURSOR.debug( "Creating AndCursor {}", this );
81          }
82  
83          this.wrapped = wrapped;
84          this.evaluators = optimize( evaluators );
85          this.partitionTxn = partitionTxn;
86      }
87  
88  
89      /**
90       * {@inheritDoc}
91       */
92      protected String getUnsupportedMessage()
93      {
94          return UNSUPPORTED_MSG;
95      }
96  
97  
98      /**
99       * {@inheritDoc}
100      */
101     public void beforeFirst() throws LdapException, CursorException
102     {
103         checkNotClosed();
104         wrapped.beforeFirst();
105         setAvailable( false );
106     }
107 
108 
109     /**
110      * {@inheritDoc}
111      */
112     public void afterLast() throws LdapException, CursorException
113     {
114         checkNotClosed();
115         wrapped.afterLast();
116         setAvailable( false );
117     }
118 
119 
120     /**
121      * {@inheritDoc}
122      */
123     public boolean first() throws LdapException, CursorException
124     {
125         beforeFirst();
126 
127         return next();
128     }
129 
130 
131     /**
132      * {@inheritDoc}
133      */
134     public boolean last() throws LdapException, CursorException
135     {
136         afterLast();
137 
138         return previous();
139     }
140 
141 
142     /**
143      * {@inheritDoc}
144      */
145     public boolean previous( PartitionTxn partitionTxn ) throws LdapException, CursorException
146     {
147         while ( wrapped.previous() )
148         {
149             checkNotClosed();
150 
151             IndexEntry<V, String> candidate = wrapped.get();
152 
153             if ( matches( partitionTxn, candidate ) )
154             {
155                 return setAvailable( true );
156             }
157         }
158 
159         return setAvailable( false );
160     }
161 
162 
163     /**
164      * {@inheritDoc}
165      */
166     public boolean next( PartitionTxn partitionTxn ) throws LdapException, CursorException
167     {
168         while ( wrapped.next() )
169         {
170             checkNotClosed();
171             IndexEntry<V, String> candidate = wrapped.get();
172 
173             if ( matches( partitionTxn, candidate ) )
174             {
175                 return setAvailable( true );
176             }
177         }
178 
179         return setAvailable( false );
180     }
181 
182 
183     /**
184      * {@inheritDoc}
185      */
186     public IndexEntry<V, String> get() throws CursorException
187     {
188         checkNotClosed();
189 
190         if ( available() )
191         {
192             return wrapped.get();
193         }
194 
195         throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
196     }
197 
198 
199     /**
200      * {@inheritDoc}
201      */
202     @Override
203     public void close() throws IOException
204     {
205         if ( IS_DEBUG )
206         {
207             LOG_CURSOR.debug( "Closing AndCursor {}", this );
208         }
209 
210         super.close();
211         wrapped.close();
212     }
213 
214 
215     /**
216      * {@inheritDoc}
217      */
218     @Override
219     public void close( Exception cause ) throws IOException
220     {
221         if ( IS_DEBUG )
222         {
223             LOG_CURSOR.debug( "Closing AndCursor {}", this );
224         }
225 
226         super.close( cause );
227         wrapped.close( cause );
228     }
229 
230 
231     /**
232      * Takes a set of Evaluators and copies then sorts them in a new list with
233      * increasing scan counts on their expression nodes.  This is done to have
234      * the Evaluators with the least scan count which have the highest
235      * probability of rejecting a candidate first.  That will increase the
236      * chance of shorting the checks on evaluators early so extra lookups and
237      * comparisons are avoided.
238      *
239      * @param unoptimized the unoptimized list of Evaluators
240      * @return optimized Evaluator list with increasing scan count ordering
241      */
242     private List<Evaluator<? extends ExprNode>> optimize(
243         List<Evaluator<? extends ExprNode>> unoptimized )
244     {
245         List<Evaluator<? extends ExprNode>> optimized = new ArrayList<>(
246             unoptimized.size() );
247         optimized.addAll( unoptimized );
248 
249         Collections.sort( optimized, new ScanCountComparator() );
250 
251         return optimized;
252     }
253 
254 
255     /**
256      * Checks if the entry is a valid candidate by using the evaluators.
257      */
258     private boolean matches( PartitionTxn partitionTxn, IndexEntry<V, String> indexEntry ) throws LdapException
259     {
260         for ( Evaluator<?> evaluator : evaluators )
261         {
262             if ( !evaluator.evaluate( partitionTxn, indexEntry ) )
263             {
264                 return false;
265             }
266         }
267 
268         return true;
269     }
270 
271 
272     /**
273      * Dumps the evaluators
274      */
275     private String dumpEvaluators( String tabs )
276     {
277         StringBuilder sb = new StringBuilder();
278 
279         for ( Evaluator<? extends ExprNode> evaluator : evaluators )
280         {
281             sb.append( evaluator.toString( tabs + "  >>" ) );
282         }
283 
284         return sb.toString();
285     }
286 
287 
288     /**
289      * @see Object#toString()
290      */
291     @Override
292     public String toString( String tabs )
293     {
294         StringBuilder sb = new StringBuilder();
295 
296         sb.append( tabs ).append( "AndCursor (" );
297 
298         if ( available() )
299         {
300             sb.append( "available) :\n" );
301         }
302         else
303         {
304             sb.append( "absent) :\n" );
305         }
306 
307         if ( ( evaluators != null ) && !evaluators.isEmpty() )
308         {
309             sb.append( dumpEvaluators( tabs ) );
310         }
311 
312         sb.append( wrapped.toString( tabs + "  " ) );
313 
314         return sb.toString();
315     }
316 
317 
318     /**
319      * @see Object#toString()
320      */
321     public String toString()
322     {
323         return toString( "" );
324     }
325 }