001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.server.xdbm.search.impl;
021
022
023 import org.apache.directory.server.i18n.I18n;
024 import org.apache.directory.server.xdbm.AbstractIndexCursor;
025 import org.apache.directory.server.xdbm.ForwardIndexEntry;
026 import org.apache.directory.server.xdbm.Index;
027 import org.apache.directory.server.xdbm.IndexCursor;
028 import org.apache.directory.server.xdbm.IndexEntry;
029 import org.apache.directory.server.xdbm.Store;
030 import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
031 import org.apache.directory.shared.ldap.entry.ServerEntry;
032
033
034 /**
035 * A Cursor over entry candidates matching a GreaterEq assertion filter. This
036 * Cursor operates in two modes. The first is when an index exists for the
037 * attribute the assertion is built on. The second is when the user index for
038 * the assertion attribute does not exist. Different Cursors are used in each
039 * of these cases where the other remains null.
040 *
041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042 * @version $Rev$
043 */
044 public class GreaterEqCursor<V, ID> extends AbstractIndexCursor<V, ServerEntry, ID>
045 {
046 private static final String UNSUPPORTED_MSG = "GreaterEqCursors only support positioning by element when a user index exists on the asserted attribute.";
047
048 /** An greater eq evaluator for candidates */
049 private final GreaterEqEvaluator<V, ID> greaterEqEvaluator;
050
051 /** Cursor over attribute entry matching filter: set when index present */
052 private final IndexCursor<V, ServerEntry, ID> userIdxCursor;
053
054 /** NDN Cursor on all entries in (set when no index on user attribute) */
055 private final IndexCursor<String, ServerEntry, ID> ndnIdxCursor;
056
057 /**
058 * Used to store indexEntry from ndnCandidate so it can be saved after
059 * call to evaluate() which changes the value so it's not referring to
060 * the NDN but to the value of the attribute instead.
061 */
062 IndexEntry<String, ServerEntry, ID> ndnCandidate;
063
064 /** used in both modes */
065 private boolean available = false;
066
067
068 @SuppressWarnings("unchecked")
069 public GreaterEqCursor( Store<ServerEntry, ID> db, GreaterEqEvaluator greaterEqEvaluator ) throws Exception
070 {
071 this.greaterEqEvaluator = greaterEqEvaluator;
072
073 String attribute = greaterEqEvaluator.getExpression().getAttribute();
074 if ( db.hasIndexOn( attribute ) )
075 {
076 userIdxCursor = ( ( Index<V, ServerEntry, ID> ) db.getIndex( attribute ) ).forwardCursor();
077 ndnIdxCursor = null;
078 }
079 else
080 {
081 ndnIdxCursor = db.getNdnIndex().forwardCursor();
082 userIdxCursor = null;
083 }
084 }
085
086
087 public boolean available()
088 {
089 return available;
090 }
091
092
093 @SuppressWarnings("unchecked")
094 public void beforeValue( ID id, V value ) throws Exception
095 {
096 checkNotClosed( "beforeValue()" );
097 if ( userIdxCursor != null )
098 {
099 /*
100 * First we need to check and make sure this element is within
101 * bounds as mandated by the assertion node. To do so we compare
102 * it's value with the value of the node. If it is smaller or
103 * equal to this lower bound then we simply position the
104 * userIdxCursor before the first element. Otherwise we let the
105 * underlying userIdx Cursor position the element.
106 */
107 if ( greaterEqEvaluator.getComparator()
108 .compare( value, greaterEqEvaluator.getExpression().getValue().get() ) <= 0 )
109 {
110 beforeFirst();
111 return;
112 }
113
114 userIdxCursor.beforeValue( id, value );
115 available = false;
116 }
117 else
118 {
119 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
120 }
121 }
122
123
124 @SuppressWarnings("unchecked")
125 public void afterValue( ID id, V value ) throws Exception
126 {
127 checkNotClosed( "afterValue()" );
128 if ( userIdxCursor != null )
129 {
130 int comparedValue = greaterEqEvaluator.getComparator().compare( value,
131 greaterEqEvaluator.getExpression().getValue().get() );
132
133 /*
134 * First we need to check and make sure this element is within
135 * bounds as mandated by the assertion node. To do so we compare
136 * it's value with the value of the node. If it is equal to this
137 * lower bound then we simply position the userIdxCursor after
138 * this first node. If it is less than this value then we
139 * position the Cursor before the first entry.
140 */
141 if ( comparedValue == 0 )
142 {
143 userIdxCursor.afterValue( id, value );
144 available = false;
145 return;
146 }
147 else if ( comparedValue < 0 )
148 {
149 beforeFirst();
150 return;
151 }
152
153 // Element is in the valid range as specified by assertion
154 userIdxCursor.afterValue( id, value );
155 available = false;
156 }
157 else
158 {
159 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
160 }
161 }
162
163
164 @SuppressWarnings("unchecked")
165 public void before( IndexEntry<V, ServerEntry, ID> element ) throws Exception
166 {
167 checkNotClosed( "before()" );
168 if ( userIdxCursor != null )
169 {
170 /*
171 * First we need to check and make sure this element is within
172 * bounds as mandated by the assertion node. To do so we compare
173 * it's value with the value of the node. If it is smaller or
174 * equal to this lower bound then we simply position the
175 * userIdxCursor before the first element. Otherwise we let the
176 * underlying userIdx Cursor position the element.
177 */
178 if ( greaterEqEvaluator.getComparator().compare( element.getValue(),
179 greaterEqEvaluator.getExpression().getValue().get() ) <= 0 )
180 {
181 beforeFirst();
182 return;
183 }
184
185 userIdxCursor.before( element );
186 available = false;
187 }
188 else
189 {
190 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
191 }
192 }
193
194
195 @SuppressWarnings("unchecked")
196 public void after( IndexEntry<V, ServerEntry, ID> element ) throws Exception
197 {
198 checkNotClosed( "after()" );
199 if ( userIdxCursor != null )
200 {
201 int comparedValue = greaterEqEvaluator.getComparator().compare( element.getValue(),
202 greaterEqEvaluator.getExpression().getValue().get() );
203
204 /*
205 * First we need to check and make sure this element is within
206 * bounds as mandated by the assertion node. To do so we compare
207 * it's value with the value of the node. If it is equal to this
208 * lower bound then we simply position the userIdxCursor after
209 * this first node. If it is less than this value then we
210 * position the Cursor before the first entry.
211 */
212 if ( comparedValue == 0 )
213 {
214 userIdxCursor.after( element );
215 available = false;
216 return;
217 }
218 else if ( comparedValue < 0 )
219 {
220 beforeFirst();
221 return;
222 }
223
224 // Element is in the valid range as specified by assertion
225 userIdxCursor.after( element );
226 available = false;
227 }
228 else
229 {
230 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
231 }
232 }
233
234
235 @SuppressWarnings("unchecked")
236 public void beforeFirst() throws Exception
237 {
238 checkNotClosed( "beforeFirst()" );
239 if ( userIdxCursor != null )
240 {
241 IndexEntry<V, ServerEntry, ID> advanceTo = new ForwardIndexEntry<V, ServerEntry, ID>();
242 advanceTo.setValue( ( V ) greaterEqEvaluator.getExpression().getValue().get() );
243 userIdxCursor.before( advanceTo );
244 }
245 else
246 {
247 ndnIdxCursor.beforeFirst();
248 ndnCandidate = null;
249 }
250
251 available = false;
252 }
253
254
255 public void afterLast() throws Exception
256 {
257 checkNotClosed( "afterLast()" );
258 if ( userIdxCursor != null )
259 {
260 userIdxCursor.afterLast();
261 }
262 else
263 {
264 ndnIdxCursor.afterLast();
265 ndnCandidate = null;
266 }
267
268 available = false;
269 }
270
271
272 public boolean first() throws Exception
273 {
274 beforeFirst();
275 return next();
276 }
277
278
279 public boolean last() throws Exception
280 {
281 afterLast();
282 return previous();
283 }
284
285
286 @SuppressWarnings("unchecked")
287 public boolean previous() throws Exception
288 {
289 checkNotClosed( "previous()" );
290 if ( userIdxCursor != null )
291 {
292 /*
293 * We have to check and make sure the previous value complies by
294 * being greater than or eq to the expression node's value
295 */
296 while ( userIdxCursor.previous() )
297 {
298 checkNotClosed( "previous()" );
299 IndexEntry<?, ServerEntry, ID> candidate = userIdxCursor.get();
300 if ( greaterEqEvaluator.getComparator().compare( candidate.getValue(),
301 greaterEqEvaluator.getExpression().getValue().get() ) >= 0 )
302 {
303 return available = true;
304 }
305 }
306
307 return available = false;
308 }
309
310 while ( ndnIdxCursor.previous() )
311 {
312 checkNotClosed( "previous()" );
313 ndnCandidate = ndnIdxCursor.get();
314 if ( greaterEqEvaluator.evaluate( ndnCandidate ) )
315 {
316 return available = true;
317 }
318 }
319
320 return available = false;
321 }
322
323
324 public boolean next() throws Exception
325 {
326 checkNotClosed( "next()" );
327 if ( userIdxCursor != null )
328 {
329 /*
330 * No need to do the same check that is done in previous() since
331 * values are increasing with calls to next().
332 */
333 return available = userIdxCursor.next();
334 }
335
336 while ( ndnIdxCursor.next() )
337 {
338 checkNotClosed( "next()" );
339 ndnCandidate = ndnIdxCursor.get();
340 if ( greaterEqEvaluator.evaluate( ndnCandidate ) )
341 {
342 return available = true;
343 }
344 }
345
346 return available = false;
347 }
348
349
350 @SuppressWarnings("unchecked")
351 public IndexEntry<V, ServerEntry, ID> get() throws Exception
352 {
353 checkNotClosed( "get()" );
354 if ( userIdxCursor != null )
355 {
356 if ( available )
357 {
358 return userIdxCursor.get();
359 }
360
361 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
362 }
363
364 if ( available )
365 {
366 return ( IndexEntry<V, ServerEntry, ID> ) ndnCandidate;
367 }
368
369 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
370 }
371
372
373 public boolean isElementReused()
374 {
375 if ( userIdxCursor != null )
376 {
377 return userIdxCursor.isElementReused();
378 }
379
380 return ndnIdxCursor.isElementReused();
381 }
382
383
384 public void close() throws Exception
385 {
386 super.close();
387
388 if ( userIdxCursor != null )
389 {
390 userIdxCursor.close();
391 }
392 else
393 {
394 ndnIdxCursor.close();
395 }
396 }
397 }