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.impl;
21  
22  
23  import org.apache.directory.server.core.cursor.InvalidCursorPositionException;
24  import org.apache.directory.server.core.entry.ServerEntry;
25  import org.apache.directory.server.xdbm.*;
26  
27  
28  /**
29   * A Cursor traversing candidates matching a Substring assertion expression.
30   *
31   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
32   * @version $Rev$
33   */
34  public class SubstringCursor extends AbstractIndexCursor<String, ServerEntry>
35  {
36      private static final String UNSUPPORTED_MSG =
37          "SubstringCursors may not be ordered and do not support positioning by element.";
38      private final boolean hasIndex;
39      private final IndexCursor<String,ServerEntry> wrapped;
40      private final SubstringEvaluator evaluator;
41      private final ForwardIndexEntry<String,ServerEntry> indexEntry =
42          new ForwardIndexEntry<String,ServerEntry>();
43      private boolean available = false;
44  
45  
46      @SuppressWarnings("unchecked")
47      public SubstringCursor( Store<ServerEntry> db,
48                              final SubstringEvaluator substringEvaluator ) throws Exception
49      {
50          evaluator = substringEvaluator;
51          hasIndex = db.hasUserIndexOn( evaluator.getExpression().getAttribute() );
52  
53          if ( hasIndex )
54          {
55              wrapped = ( ( Index<String,ServerEntry> ) db.getUserIndex( evaluator.getExpression().getAttribute() ) )
56                  .forwardCursor();
57          }
58          else
59          {
60              /*
61               * There is no index on the attribute here.  We have no choice but
62               * to perform a full table scan but need to leverage an index for the
63               * wrapped Cursor.  We know that all entries are listed under
64               * the ndn index and so this will enumerate over all entries.  The
65               * substringEvaluator is used in an assertion to constrain the
66               * result set to only those entries matching the pattern.  The
67               * substringEvaluator handles all the details of normalization and
68               * knows to use it, when it itself detects the lack of an index on
69               * the node's attribute.
70               */
71              wrapped = db.getNdnIndex().forwardCursor();
72          }
73      }
74  
75  
76      public boolean available()
77      {
78          return available;
79      }
80  
81  
82      public void beforeValue( Long id, String value ) throws Exception
83      {
84          throw new UnsupportedOperationException( UNSUPPORTED_MSG );
85      }
86  
87  
88      public void afterValue( Long id, String value ) throws Exception
89      {
90          throw new UnsupportedOperationException( UNSUPPORTED_MSG );
91      }
92  
93  
94      public void before( IndexEntry<String, ServerEntry> element ) throws Exception
95      {
96          throw new UnsupportedOperationException( UNSUPPORTED_MSG );
97      }
98  
99  
100     public void after( IndexEntry<String, ServerEntry> element ) throws Exception
101     {
102         throw new UnsupportedOperationException( UNSUPPORTED_MSG );
103     }
104 
105 
106     public void beforeFirst() throws Exception
107     {
108         checkNotClosed( "beforeFirst()" );
109         if ( evaluator.getExpression().getInitial() != null && hasIndex )
110         {
111             ForwardIndexEntry<String,ServerEntry> indexEntry = new ForwardIndexEntry<String,ServerEntry>();
112             indexEntry.setValue( evaluator.getExpression().getInitial() );
113             wrapped.before( indexEntry );
114         }
115         else
116         {
117             wrapped.beforeFirst();
118         }
119 
120         clear();
121     }
122 
123 
124     private void clear()
125     {
126         available = false;
127         indexEntry.setObject( null );
128         indexEntry.setId( null );
129         indexEntry.setValue( null );
130     }
131 
132 
133     public void afterLast() throws Exception
134     {
135         checkNotClosed( "afterLast()" );
136 
137         // to keep the cursor always *after* the last matched tuple
138         // This fixes an issue if the last matched tuple is also the last record present in the 
139         // index. In this case the wrapped cursor is positioning on the last tuple instead of positioning after that
140         wrapped.afterLast();
141         clear();
142     }
143 
144 
145     public boolean first() throws Exception
146     {
147         beforeFirst();
148         return next();
149     }
150 
151 
152     private boolean evaluateCandidate( IndexEntry<String,ServerEntry> indexEntry ) throws Exception
153     {
154         if ( hasIndex )
155         {
156             return evaluator.getPattern().matcher( indexEntry.getValue() ).matches();
157         }
158         else
159         {
160             return evaluator.evaluate( indexEntry );
161         }
162     }
163 
164 
165     public boolean last() throws Exception
166     {
167         afterLast();
168         return previous();
169     }
170 
171 
172     public boolean previous() throws Exception
173     {
174         while ( wrapped.previous() )
175         {
176             checkNotClosed( "previous()" );
177             IndexEntry<String,ServerEntry> entry = wrapped.get();
178             if ( evaluateCandidate( entry ) )
179             {
180                 available = true;
181                 this.indexEntry.setId( entry.getId() );
182                 this.indexEntry.setValue( entry.getValue() );
183                 this.indexEntry.setObject( entry.getObject() );
184                 return true;
185             }
186         }
187 
188         clear();
189         return false;
190     }
191 
192 
193     public boolean next() throws Exception
194     {
195         while ( wrapped.next() )
196         {
197             checkNotClosed( "next()" );
198             IndexEntry<String,ServerEntry> entry = wrapped.get();
199             if ( evaluateCandidate( entry ) )
200             {
201                 available = true;
202                 this.indexEntry.setId( entry.getId() );
203                 this.indexEntry.setValue( entry.getValue() );
204                 this.indexEntry.setObject( entry.getObject() );
205                 return true;
206             }
207         }
208 
209         clear();
210         return false;
211     }
212 
213 
214     public IndexEntry<String, ServerEntry> get() throws Exception
215     {
216         checkNotClosed( "get()" );
217         if ( available )
218         {
219             return indexEntry;
220         }
221 
222         throw new InvalidCursorPositionException( "Cursor has yet to be positioned." );
223     }
224 
225 
226     public boolean isElementReused()
227     {
228         return wrapped.isElementReused();
229     }
230 
231 
232     public void close() throws Exception
233     {
234         super.close();
235         wrapped.close();
236         clear();
237     }
238 }