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.core.partition.impl.btree.jdbm;
021
022
023import java.io.IOException;
024
025import jdbm.btree.BTree;
026import jdbm.helper.TupleBrowser;
027
028import org.apache.directory.api.ldap.model.constants.Loggers;
029import org.apache.directory.api.ldap.model.cursor.AbstractCursor;
030import org.apache.directory.api.ldap.model.cursor.CursorException;
031import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
032import org.apache.directory.api.ldap.model.cursor.Tuple;
033import org.apache.directory.api.ldap.model.exception.LdapException;
034import org.apache.directory.server.i18n.I18n;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038
039/**
040 * A cursor for browsing tables with duplicates which returns the container
041 * for values rather than just the value.
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 * @param K The Key
045 * @param V The associated value
046 */
047public class DupsContainerCursor<K, V> extends AbstractCursor<Tuple<K, DupsContainer<V>>>
048{
049    /** A dedicated log for cursors */
050    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
051
052    /** Speedup for logs */
053    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
054
055    /** The JDBM table we are building a cursor over */
056    private final JdbmTable<K, V> table;
057
058    /** A container to pass to the underlying JDBM to get back a tuple */
059    private jdbm.helper.Tuple<K, V> jdbmTuple = new jdbm.helper.Tuple<>();
060
061    private Tuple<K, DupsContainer<V>> returnedTuple = new Tuple<>();
062
063    /** A browser over the JDBM Table */
064    private TupleBrowser<K, V> browser;
065
066    /** Tells if we have a tuple to return */
067    private boolean valueAvailable;
068
069    /** TODO : do we need this flag ??? */
070    private Boolean forwardDirection;
071
072
073    /**
074     * Creates a Cursor over the tuples of a JDBM table.
075     *
076     * @param table the JDBM Table to build a Cursor over
077     */
078    public DupsContainerCursor( JdbmTable<K, V> table )
079    {
080        if ( IS_DEBUG )
081        {
082            LOG_CURSOR.debug( "Creating DupsContainerCursor {}", this );
083        }
084
085        if ( !table.isDupsEnabled() )
086        {
087            throw new IllegalStateException( I18n.err( I18n.ERR_572 ) );
088        }
089
090        this.table = table;
091    }
092
093
094    /**
095     * Clean the tuples we use to store the returned resut.
096     */
097    private void clearValue()
098    {
099        returnedTuple.setKey( null );
100        returnedTuple.setValue( null );
101        jdbmTuple.setKey( null );
102        jdbmTuple.setValue( null );
103        valueAvailable = false;
104    }
105
106
107    /**
108     * {@inheritDoc}
109     */
110    public boolean available()
111    {
112        return valueAvailable;
113    }
114
115
116    /**
117     * {@inheritDoc}
118     */
119    @SuppressWarnings("unchecked")
120    public void beforeKey( K key ) throws CursorException
121    {
122        checkNotClosed();
123        try
124        {
125            browser = ( ( BTree<K, V> ) table.getBTree() ).browse( key );
126            forwardDirection = null;
127            clearValue();
128        }
129        catch ( IOException e )
130        {
131            throw new CursorException( e );
132        }
133    }
134
135
136    /**
137     * {@inheritDoc}
138     */
139    @SuppressWarnings("unchecked")
140    public void afterKey( K key ) throws CursorException
141    {
142        checkNotClosed();
143
144        try
145        {
146            browser = ( ( BTree<K, V> ) table.getBTree() ).browse( key );
147            forwardDirection = null;
148
149            /*
150             * While the next value is less than or equal to the element keep
151             * advancing forward to the next item.  If we cannot advance any
152             * further then stop and return.  If we find a value greater than
153             * the element then we stop, backup, and return so subsequent calls
154             * to getNext() will return a value greater than the element.
155             */
156            while ( browser.getNext( jdbmTuple ) )
157            {
158                checkNotClosed();
159                K next = jdbmTuple.getKey();
160
161                int nextCompared = table.getKeyComparator().compare( next, key );
162
163                if ( nextCompared > 0 )
164                {
165                    browser.getPrevious( jdbmTuple );
166
167                    // switch in direction bug workaround: when a JDBM browser
168                    // switches direction with next then previous as is occurring
169                    // here then two previous moves are needed.
170                    browser.getPrevious( jdbmTuple );
171                    forwardDirection = false;
172                    clearValue();
173
174                    return;
175                }
176            }
177
178            clearValue();
179        }
180        catch ( IOException e )
181        {
182            throw new CursorException( e );
183        }
184    }
185
186
187    /**
188     * {@inheritDoc}
189     */
190    public void beforeValue( K key, DupsContainer<V> value ) throws Exception
191    {
192        throw new UnsupportedOperationException( I18n.err( I18n.ERR_573 ) );
193    }
194
195
196    /**
197     * {@inheritDoc}
198     */
199    public void afterValue( K key, DupsContainer<V> value ) throws Exception
200    {
201        throw new UnsupportedOperationException( I18n.err( I18n.ERR_573 ) );
202    }
203
204
205    /**
206     * Positions this Cursor before the key of the supplied tuple.
207     *
208     * @param element the tuple who's key is used to position this Cursor
209     * @throws LdapException if there are failures to position the Cursor
210     * @throws CursorException if there are failures to position the Cursor
211     */
212    public void before( Tuple<K, DupsContainer<V>> element ) throws LdapException, CursorException
213    {
214        beforeKey( element.getKey() );
215    }
216
217
218    /**
219     * {@inheritDoc}
220     */
221    public void after( Tuple<K, DupsContainer<V>> element ) throws CursorException
222    {
223        afterKey( element.getKey() );
224    }
225
226
227    /**
228     * {@inheritDoc}
229     */
230    @SuppressWarnings("unchecked")
231    public void beforeFirst() throws LdapException, CursorException
232    {
233        checkNotClosed();
234        try
235        {
236            browser = table.getBTree().browse();
237            forwardDirection = null;
238            clearValue();
239        }
240        catch ( IOException e )
241        {
242            throw new CursorException( e );
243        }
244    }
245
246
247    /**
248     * {@inheritDoc}
249     */
250    @SuppressWarnings("unchecked")
251    public void afterLast() throws LdapException, CursorException
252    {
253        checkNotClosed();
254        try
255        {
256            browser = table.getBTree().browse( null );
257            forwardDirection = null;
258            clearValue();
259        }
260        catch ( IOException e )
261        {
262            throw new CursorException( e );
263        }
264    }
265
266
267    /**
268     * {@inheritDoc}
269     */
270    public boolean first() throws LdapException, CursorException
271    {
272        beforeFirst();
273
274        return next();
275    }
276
277
278    /**
279     * {@inheritDoc}
280     */
281    public boolean last() throws LdapException, CursorException
282    {
283        afterLast();
284
285        return previous();
286    }
287
288
289    /**
290     * {@inheritDoc}
291     */
292    public boolean previous() throws LdapException, CursorException
293    {
294        checkNotClosed();
295
296        if ( browser == null )
297        {
298            afterLast();
299        }
300
301        try
302        {
303            boolean advanceSuccess = browser.getPrevious( jdbmTuple );
304
305            // only want to set this if the advance is a success which means we
306            // are not at front
307            if ( forwardDirection == null )
308            {
309                if ( advanceSuccess )
310                {
311                    forwardDirection = false;
312                }
313                else
314                {
315                    clearValue();
316
317                    return false;
318                }
319            }
320            else if ( forwardDirection )
321            {
322                advanceSuccess = browser.getPrevious( jdbmTuple );
323                forwardDirection = false;
324            }
325
326            valueAvailable = advanceSuccess;
327
328            if ( valueAvailable )
329            {
330                returnedTuple.setKey( jdbmTuple.getKey() );
331                returnedTuple.setValue( table.getDupsContainer( ( byte[] ) jdbmTuple.getValue() ) );
332            }
333            else
334            {
335                clearValue();
336            }
337
338            return valueAvailable;
339        }
340        catch ( IOException e )
341        {
342            throw new CursorException( e );
343        }
344    }
345
346
347    /**
348     * {@inheritDoc}
349     */
350    public boolean next() throws LdapException, CursorException
351    {
352        checkNotClosed();
353
354        if ( browser == null )
355        {
356            // The tuple browser is not initialized : set it to the beginning of the cursor
357            beforeFirst();
358        }
359
360        try
361        {
362            // Check if we can move forward and grab a tuple
363            boolean advanceSuccess = browser.getNext( jdbmTuple );
364
365            // only want to set this if the advance is a success which means
366            // we are not at end
367            if ( forwardDirection == null )
368            {
369                if ( advanceSuccess )
370                {
371                    forwardDirection = true;
372                }
373                else
374                {
375                    clearValue();
376
377                    // No value available
378                    return false;
379                }
380            }
381
382            if ( !forwardDirection )
383            {
384                advanceSuccess = browser.getNext( jdbmTuple );
385                forwardDirection = true;
386            }
387
388            valueAvailable = advanceSuccess;
389
390            if ( valueAvailable )
391            {
392                // create the fetched tuple containing the key and the deserialized value
393                returnedTuple.setKey( jdbmTuple.getKey() );
394                returnedTuple.setValue( table.getDupsContainer( ( byte[] ) jdbmTuple.getValue() ) );
395            }
396            else
397            {
398                clearValue();
399            }
400
401            return valueAvailable;
402        }
403        catch ( IOException e )
404        {
405            throw new CursorException( e );
406        }
407    }
408
409
410    /**
411     * {@inheritDoc}
412     */
413    public Tuple<K, DupsContainer<V>> get() throws CursorException
414    {
415        checkNotClosed();
416
417        if ( valueAvailable )
418        {
419            return returnedTuple;
420        }
421
422        throw new InvalidCursorPositionException();
423    }
424
425
426    /**
427     * {@inheritDoc}
428     */
429    @Override
430    public void close() throws IOException
431    {
432        if ( IS_DEBUG )
433        {
434            LOG_CURSOR.debug( "Closing DupsContainerCursor {}", this );
435        }
436
437        super.close();
438    }
439
440
441    /**
442     * {@inheritDoc}
443     */
444    @Override
445    public void close( Exception cause ) throws IOException
446    {
447        if ( IS_DEBUG )
448        {
449            LOG_CURSOR.debug( "Closing DupsContainerCursor {}", this );
450        }
451
452        super.close( cause );
453    }
454}