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  
25  import org.apache.directory.api.ldap.model.constants.Loggers;
26  import org.apache.directory.api.ldap.model.cursor.Cursor;
27  import org.apache.directory.api.ldap.model.cursor.CursorException;
28  import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.apache.directory.api.ldap.model.schema.PrepareString;
31  import org.apache.directory.server.core.api.partition.PartitionTxn;
32  import org.apache.directory.server.i18n.I18n;
33  import org.apache.directory.server.xdbm.AbstractIndexCursor;
34  import org.apache.directory.server.xdbm.Index;
35  import org.apache.directory.server.xdbm.IndexEntry;
36  import org.apache.directory.server.xdbm.IndexNotFoundException;
37  import org.apache.directory.server.xdbm.Store;
38  import org.apache.directory.server.xdbm.search.evaluator.SubstringEvaluator;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  
43  /**
44   * A Cursor traversing candidates matching a Substring assertion expression.
45   *
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   */
48  public class SubstringCursor extends AbstractIndexCursor<String>
49  {
50      /** A dedicated log for cursors */
51      private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
52  
53      /** Speedup for logs */
54      private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
55  
56      private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_725 );
57      private final boolean hasIndex;
58      private final Cursor<IndexEntry<String, String>> wrapped;
59      private final SubstringEvaluator evaluator;
60      private final IndexEntry<String, String> indexEntry = new IndexEntry<>();
61  
62  
63      /**
64       * Creates a new instance of an SubstringCursor
65       * 
66       * @param partitionTxn The transaction to use
67       * @param store The store
68       * @param substringEvaluator The SubstringEvaluator
69       * @throws LdapException If the creation failed
70       * @throws IndexNotFoundException If the index was not found
71       */
72      @SuppressWarnings("unchecked")
73      public SubstringCursor( PartitionTxn partitionTxn, Store store, final SubstringEvaluator substringEvaluator )
74          throws LdapException, IndexNotFoundException
75      {
76          if ( IS_DEBUG )
77          {
78              LOG_CURSOR.debug( "Creating SubstringCursor {}", this );
79          }
80  
81          evaluator = substringEvaluator;
82          this.partitionTxn = partitionTxn;
83          hasIndex = store.hasIndexOn( evaluator.getExpression().getAttributeType() );
84  
85          if ( hasIndex )
86          {
87              wrapped = ( ( Index<String, String> ) store.getIndex( evaluator.getExpression().getAttributeType() ) )
88                  .forwardCursor( partitionTxn );
89          }
90          else
91          {
92              /*
93               * There is no index on the attribute here.  We have no choice but
94               * to perform a full table scan but need to leverage an index for the
95               * wrapped Cursor.  We know that all entries are listed under
96               * the ndn index and so this will enumerate over all entries.  The
97               * substringEvaluator is used in an assertion to constrain the
98               * result set to only those entries matching the pattern.  The
99               * substringEvaluator handles all the details of normalization and
100              * knows to use it, when it itself detects the lack of an index on
101              * the node's attribute.
102              */
103             wrapped = new AllEntriesCursor( partitionTxn, store );
104         }
105     }
106 
107 
108     /**
109      * {@inheritDoc}
110      */
111     protected String getUnsupportedMessage()
112     {
113         return UNSUPPORTED_MSG;
114     }
115 
116 
117     /**
118      * {@inheritDoc}
119      */
120     public void beforeFirst() throws LdapException, CursorException
121     {
122         checkNotClosed();
123         
124         if ( evaluator.getExpression().getInitial() != null && hasIndex )
125         {
126             IndexEntry<String, String> beforeFirstIndexEntry = new IndexEntry<>();
127             String normalizedKey = evaluator.getExpression().getAttributeType().getEquality().getNormalizer().normalize( 
128                 evaluator.getExpression().getInitial(), PrepareString.AssertionType.SUBSTRING_INITIAL );
129             beforeFirstIndexEntry.setKey( normalizedKey );
130             wrapped.before( beforeFirstIndexEntry );
131         }
132         else
133         {
134             wrapped.beforeFirst();
135         }
136 
137         clear();
138     }
139 
140 
141     private void clear()
142     {
143         setAvailable( false );
144         indexEntry.setEntry( null );
145         indexEntry.setId( null );
146         indexEntry.setKey( null );
147     }
148 
149 
150     /**
151      * {@inheritDoc}
152      */
153     public void afterLast() throws LdapException, CursorException
154     {
155         checkNotClosed();
156 
157         // to keep the cursor always *after* the last matched tuple
158         // This fixes an issue if the last matched tuple is also the last record present in the
159         // index. In this case the wrapped cursor is positioning on the last tuple instead of positioning after that
160         wrapped.afterLast();
161         clear();
162     }
163 
164 
165     /**
166      * {@inheritDoc}
167      */
168     public boolean first() throws LdapException, CursorException
169     {
170         beforeFirst();
171         return next();
172     }
173 
174 
175     private boolean evaluateCandidate( PartitionTxn partitionTxn, IndexEntry<String, String> indexEntry ) throws LdapException
176     {
177         if ( hasIndex )
178         {
179             String key = indexEntry.getKey();
180             return evaluator.getPattern().matcher( key ).matches();
181         }
182         else
183         {
184             return evaluator.evaluate( partitionTxn, indexEntry );
185         }
186     }
187 
188 
189     /**
190      * {@inheritDoc}
191      */
192     public boolean last() throws LdapException, CursorException
193     {
194         afterLast();
195 
196         return previous();
197     }
198 
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
204     public boolean previous() throws LdapException, CursorException
205     {
206         while ( wrapped.previous() )
207         {
208             checkNotClosed();
209             IndexEntry<String, String> entry = wrapped.get();
210 
211             if ( evaluateCandidate( partitionTxn, entry ) )
212             {
213                 setAvailable( true );
214                 this.indexEntry.setId( entry.getId() );
215                 this.indexEntry.setKey( entry.getKey() );
216                 this.indexEntry.setEntry( entry.getEntry() );
217                 return true;
218             }
219         }
220 
221         clear();
222         return false;
223     }
224 
225 
226     /**
227      * {@inheritDoc}
228      */
229     @Override
230     public boolean next() throws LdapException, CursorException
231     {
232         while ( wrapped.next() )
233         {
234             checkNotClosed();
235             IndexEntry<String, String> entry = wrapped.get();
236 
237             if ( evaluateCandidate( partitionTxn, entry ) )
238             {
239                 setAvailable( true );
240                 this.indexEntry.setId( entry.getId() );
241                 this.indexEntry.setKey( entry.getKey() );
242                 this.indexEntry.setEntry( entry.getEntry() );
243 
244                 return true;
245             }
246         }
247 
248         clear();
249         return false;
250     }
251 
252 
253     /**
254      * {@inheritDoc}
255      */
256     public IndexEntry<String, String> get() throws CursorException
257     {
258         checkNotClosed();
259 
260         if ( available() )
261         {
262             return indexEntry;
263         }
264 
265         throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
266     }
267 
268 
269     /**
270      * {@inheritDoc}
271      */
272     @Override
273     public void close() throws IOException
274     {
275         if ( IS_DEBUG )
276         {
277             LOG_CURSOR.debug( "Closing SubstringCursor {}", this );
278         }
279 
280         super.close();
281         wrapped.close();
282         clear();
283     }
284 
285 
286     /**
287      * {@inheritDoc}
288      */
289     @Override
290     public void close( Exception cause ) throws IOException
291     {
292         if ( IS_DEBUG )
293         {
294             LOG_CURSOR.debug( "Closing SubstringCursor {}", this );
295         }
296 
297         super.close( cause );
298         wrapped.close( cause );
299         clear();
300     }
301 
302 
303     /**
304      * @see Object#toString()
305      */
306     @Override
307     public String toString( String tabs )
308     {
309         StringBuilder sb = new StringBuilder();
310 
311         sb.append( tabs ).append( "SubstringCursor (" );
312 
313         if ( available() )
314         {
315             sb.append( "available)" );
316         }
317         else
318         {
319             sb.append( "absent)" );
320         }
321 
322         sb.append( "#index<" ).append( hasIndex ).append( "> :\n" );
323 
324         sb.append( tabs + "  >>" ).append( evaluator ).append( '\n' );
325 
326         sb.append( wrapped.toString( tabs + "    " ) );
327 
328         return sb.toString();
329     }
330 
331 
332     /**
333      * @see Object#toString()
334      */
335     public String toString()
336     {
337         return toString( "" );
338     }
339 }