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 */
020package org.apache.directory.server.xdbm.search.cursor;
021
022
023import java.io.IOException;
024
025import org.apache.directory.api.ldap.model.constants.Loggers;
026import org.apache.directory.api.ldap.model.cursor.Cursor;
027import org.apache.directory.api.ldap.model.cursor.CursorException;
028import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.server.core.api.partition.PartitionTxn;
032import org.apache.directory.server.i18n.I18n;
033import org.apache.directory.server.xdbm.AbstractIndexCursor;
034import org.apache.directory.server.xdbm.Index;
035import org.apache.directory.server.xdbm.IndexEntry;
036import org.apache.directory.server.xdbm.IndexNotFoundException;
037import org.apache.directory.server.xdbm.Store;
038import org.apache.directory.server.xdbm.search.evaluator.GreaterEqEvaluator;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * A Cursor over entry candidates matching a GreaterEq assertion filter.  This
045 * Cursor operates in two modes.  The first is when an index exists for the
046 * attribute the assertion is built on.  The second is when the user index for
047 * the assertion attribute does not exist.  Different Cursors are used in each
048 * of these cases where the other remains null.
049 *
050 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
051 */
052public class GreaterEqCursor<V> extends AbstractIndexCursor<V>
053{
054    /** A dedicated log for cursors */
055    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
056
057    /** Speedup for logs */
058    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
059
060    private static final String UNSUPPORTED_MSG = "GreaterEqCursors only support positioning by element when a user index exists on the asserted attribute.";
061
062    /** An greater eq evaluator for candidates */
063    private final GreaterEqEvaluator<V> greaterEqEvaluator;
064
065    /** Cursor over attribute entry matching filter: set when index present */
066    private final Cursor<IndexEntry<V, String>> userIdxCursor;
067
068    /** NDN Cursor on all entries in  (set when no index on user attribute) */
069    private final Cursor<IndexEntry<String, String>> uuidIdxCursor;
070
071    /**
072     * Used to store indexEntry from uuidCandidate so it can be saved after
073     * call to evaluate() which changes the value so it's not referring to
074     * the NDN but to the value of the attribute instead.
075     */
076    private IndexEntry<String, String> uuidCandidate;
077
078
079    /**
080     * Creates a new instance of an GreaterEqCursor
081     * 
082     * @param partitionTxn The transaction to use
083     * @param store The store
084     * @param greaterEqEvaluator The GreaterEqEvaluator
085     * @throws LdapException If the creation failed
086     * @throws IndexNotFoundException If the index was not found
087     */
088    @SuppressWarnings("unchecked")
089    public GreaterEqCursor( PartitionTxn partitionTxn, Store store, GreaterEqEvaluator<V> greaterEqEvaluator ) 
090        throws LdapException, IndexNotFoundException
091    {
092        if ( IS_DEBUG )
093        {
094            LOG_CURSOR.debug( "Creating GreaterEqCursor {}", this );
095        }
096
097        this.greaterEqEvaluator = greaterEqEvaluator;
098        this.partitionTxn = partitionTxn;
099
100        AttributeType attributeType = greaterEqEvaluator.getExpression().getAttributeType();
101
102        if ( store.hasIndexOn( attributeType ) )
103        {
104            userIdxCursor = ( ( Index<V, String> ) store.getIndex( attributeType ) ).forwardCursor( partitionTxn );
105            uuidIdxCursor = null;
106        }
107        else
108        {
109            uuidIdxCursor = new AllEntriesCursor( partitionTxn, store );
110            userIdxCursor = null;
111        }
112    }
113
114
115    /**
116     * {@inheritDoc}
117     */
118    protected String getUnsupportedMessage()
119    {
120        return UNSUPPORTED_MSG;
121    }
122
123
124    /**
125     * {@inheritDoc}
126     */
127    @Override
128    public void before( IndexEntry<V, String> element ) throws LdapException, CursorException
129    {
130        checkNotClosed();
131
132        if ( userIdxCursor != null )
133        {
134            /*
135             * First we need to check and make sure this element is within
136             * bounds as mandated by the assertion node.  To do so we compare
137             * it's value with the value of the node.  If it is smaller or
138             * equal to this lower bound then we simply position the
139             * userIdxCursor before the first element.  Otherwise we let the
140             * underlying userIdx Cursor position the element.
141             */
142            if ( greaterEqEvaluator.getComparator().compare( element.getKey(),
143                greaterEqEvaluator.getExpression().getValue().getString() ) <= 0 )
144            {
145                beforeFirst();
146                return;
147            }
148
149            userIdxCursor.before( element );
150            setAvailable( false );
151        }
152        else
153        {
154            super.before( element );
155        }
156    }
157
158
159    /**
160     * {@inheritDoc}
161     */
162    @Override
163    public void after( IndexEntry<V, String> element ) throws LdapException, CursorException
164    {
165        checkNotClosed();
166
167        if ( userIdxCursor != null )
168        {
169            int comparedValue = greaterEqEvaluator.getComparator().compare( element.getKey(),
170                greaterEqEvaluator.getExpression().getValue().getString() );
171
172            /*
173             * First we need to check and make sure this element is within
174             * bounds as mandated by the assertion node.  To do so we compare
175             * it's value with the value of the node.  If it is equal to this
176             * lower bound then we simply position the userIdxCursor after
177             * this first node.  If it is less than this value then we
178             * position the Cursor before the first entry.
179             */
180            if ( comparedValue == 0 )
181            {
182                userIdxCursor.after( element );
183                setAvailable( false );
184
185                return;
186            }
187
188            if ( comparedValue < 0 )
189            {
190                beforeFirst();
191
192                return;
193            }
194
195            // Element is in the valid range as specified by assertion
196            userIdxCursor.after( element );
197            setAvailable( false );
198        }
199        else
200        {
201            super.after( element );
202        }
203    }
204
205
206    /**
207     * {@inheritDoc}
208     */
209    @SuppressWarnings("unchecked")
210    public void beforeFirst() throws LdapException, CursorException
211    {
212        checkNotClosed();
213
214        if ( userIdxCursor != null )
215        {
216            IndexEntry<V, String> advanceTo = new IndexEntry<>();
217            String normalizedNode = greaterEqEvaluator.getNormalizer().normalize( greaterEqEvaluator.getExpression().getValue().getString() );
218            advanceTo.setKey( ( V ) normalizedNode );
219            userIdxCursor.before( advanceTo );
220        }
221        else
222        {
223            uuidIdxCursor.beforeFirst();
224            uuidCandidate = null;
225        }
226
227        setAvailable( false );
228    }
229
230
231    /**
232     * {@inheritDoc}
233     */
234    public void afterLast() throws LdapException, CursorException
235    {
236        checkNotClosed();
237
238        if ( userIdxCursor != null )
239        {
240            userIdxCursor.afterLast();
241        }
242        else
243        {
244            uuidIdxCursor.afterLast();
245            uuidCandidate = null;
246        }
247
248        setAvailable( false );
249    }
250
251
252    /**
253     * {@inheritDoc}
254     */
255    public boolean first() throws LdapException, CursorException
256    {
257        beforeFirst();
258
259        return next();
260    }
261
262
263    /**
264     * {@inheritDoc}
265     */
266    public boolean last() throws LdapException, CursorException
267    {
268        afterLast();
269
270        return previous();
271    }
272
273
274    /**
275     * {@inheritDoc}
276     */
277    @Override
278    public boolean previous() throws LdapException, CursorException
279    {
280        checkNotClosed();
281
282        if ( userIdxCursor != null )
283        {
284            /*
285             * We have to check and make sure the previous value complies by
286             * being greater than or eq to the expression node's value
287             */
288            while ( userIdxCursor.previous() )
289            {
290                checkNotClosed();
291                IndexEntry<?, String> candidate = userIdxCursor.get();
292
293                if ( greaterEqEvaluator.getComparator().compare( candidate.getKey(),
294                    greaterEqEvaluator.getExpression().getValue().getString() ) >= 0 )
295                {
296                    return setAvailable( true );
297                }
298            }
299
300            return setAvailable( false );
301        }
302
303        while ( uuidIdxCursor.previous() )
304        {
305            checkNotClosed();
306            uuidCandidate = uuidIdxCursor.get();
307
308            if ( greaterEqEvaluator.evaluate( partitionTxn, uuidCandidate ) )
309            {
310                return setAvailable( true );
311            }
312        }
313
314        return setAvailable( false );
315    }
316
317
318    /**
319     * {@inheritDoc}
320     */
321    @Override
322    public boolean next() throws LdapException, CursorException
323    {
324        checkNotClosed();
325
326        if ( userIdxCursor != null )
327        {
328            /*
329             * No need to do the same check that is done in previous() since
330             * values are increasing with calls to next().
331             */
332            return setAvailable( userIdxCursor.next() );
333        }
334
335        while ( uuidIdxCursor.next() )
336        {
337            checkNotClosed();
338            uuidCandidate = uuidIdxCursor.get();
339
340            if ( greaterEqEvaluator.evaluate( partitionTxn, uuidCandidate ) )
341            {
342                return setAvailable( true );
343            }
344        }
345
346        return setAvailable( false );
347    }
348
349
350    /**
351     * {@inheritDoc}
352     */
353    public IndexEntry<V, String> get() throws CursorException
354    {
355        checkNotClosed();
356
357        if ( userIdxCursor != null )
358        {
359            if ( available() )
360            {
361                return userIdxCursor.get();
362            }
363
364            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
365        }
366
367        if ( available() )
368        {
369            return ( IndexEntry<V, String> ) uuidCandidate;
370        }
371
372        throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
373    }
374
375
376    /**
377     * {@inheritDoc}
378     */
379    @Override
380    public void close() throws IOException
381    {
382        if ( IS_DEBUG )
383        {
384            LOG_CURSOR.debug( "Closing GreaterEqCursor {}", this );
385        }
386
387        super.close();
388
389        if ( userIdxCursor != null )
390        {
391            userIdxCursor.close();
392        }
393        else
394        {
395            uuidIdxCursor.close();
396            uuidCandidate = null;
397        }
398    }
399
400
401    /**
402     * {@inheritDoc}
403     */
404    @Override
405    public void close( Exception cause ) throws IOException
406    {
407        if ( IS_DEBUG )
408        {
409            LOG_CURSOR.debug( "Closing GreaterEqCursor {}", this );
410        }
411
412        super.close( cause );
413
414        if ( userIdxCursor != null )
415        {
416            userIdxCursor.close( cause );
417        }
418        else
419        {
420            uuidIdxCursor.close( cause );
421            uuidCandidate = null;
422        }
423    }
424
425
426    /**
427     * @see Object#toString()
428     */
429    @Override
430    public String toString( String tabs )
431    {
432        StringBuilder sb = new StringBuilder();
433
434        sb.append( tabs ).append( "GreaterEqCursor (" );
435
436        if ( available() )
437        {
438            sb.append( "available)" );
439        }
440        else
441        {
442            sb.append( "absent)" );
443        }
444
445        sb.append( "#candidate<" ).append( uuidCandidate ).append( ">:\n" );
446
447        sb.append( tabs + "  >>" ).append( greaterEqEvaluator ).append( '\n' );
448
449        if ( userIdxCursor != null )
450        {
451            sb.append( tabs + "  <user>\n" );
452            sb.append( userIdxCursor.toString( tabs + "    " ) );
453        }
454
455        if ( uuidIdxCursor != null )
456        {
457            sb.append( tabs + "  <uuid>\n" );
458            sb.append( uuidIdxCursor.toString( tabs + "  " ) );
459        }
460
461        return sb.toString();
462    }
463
464
465    /**
466     * @see Object#toString()
467     */
468    public String toString()
469    {
470        return toString( "" );
471    }
472}