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.commons.collections.ArrayStack;
026import org.apache.directory.api.ldap.model.constants.Loggers;
027import org.apache.directory.api.ldap.model.cursor.Cursor;
028import org.apache.directory.api.ldap.model.cursor.CursorException;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.name.Rdn;
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.IndexEntry;
035import org.apache.directory.server.xdbm.ParentIdAndRdn;
036import org.apache.directory.server.xdbm.Store;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040
041/**
042 * A Cursor over entries satisfying one level scope constraints with alias
043 * dereferencing considerations when enabled during search.
044 * We use the Rdn index to fetch all the descendants of a given entry.
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class DescendantCursor extends AbstractIndexCursor<String>
049{
050    /** A dedicated log for cursors */
051    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
052
053    /** Speedup for logs */
054    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
055
056    /** Error message for unsupported operations */
057    private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_719 );
058
059    /** The entry database/store */
060    private final Store db;
061
062    /** The prefetched element */
063    private IndexEntry prefetched;
064
065    /** The current Cursor over the entries in the scope of the search base */
066    private Cursor<IndexEntry<ParentIdAndRdn, String>> currentCursor;
067
068    /** The current Parent ID */
069    private String currentParentId;
070
071    /** The stack of cursors used to process the depth-first traversal */
072    private ArrayStack cursorStack;
073
074    /** The stack of parentIds used to process the depth-first traversal */
075    private ArrayStack parentIdStack;
076
077    /** The initial entry ID we are looking descendants for */
078    private String baseId;
079
080    /** A flag to tell that we are in the top level cursor or not */
081    private boolean topLevel;
082
083    protected static final boolean TOP_LEVEL = true;
084    protected static final boolean INNER = false;
085
086
087    /**
088     * Creates a Cursor over entries satisfying one level scope criteria.
089     *
090     * @param partitionTxn The transaction to use
091     * @param db the entry store
092     * @param baseId The base ID
093     * @param parentId The parent ID
094     * @param cursor The wrapped cursor
095     */
096    public DescendantCursor( PartitionTxn partitionTxn, Store db, String baseId, String parentId,
097            Cursor<IndexEntry<ParentIdAndRdn, String>> cursor )
098    {
099        this( partitionTxn, db, baseId, parentId, cursor, TOP_LEVEL );
100    }
101
102
103    /**
104     * Creates a Cursor over entries satisfying one level scope criteria.
105     *
106     * @param partitionTxn The transaction to use
107     * @param db the entry store
108     * @param baseId The base ID
109     * @param parentId The parent ID
110     * @param cursor The wrapped cursor
111     * @param topLevel If we are at the top level
112     */
113    public DescendantCursor( PartitionTxn partitionTxn, Store db, String baseId, String parentId, 
114            Cursor<IndexEntry<ParentIdAndRdn, String>> cursor, boolean topLevel )
115    {
116        this.db = db;
117        currentParentId = parentId;
118        currentCursor = cursor;
119        cursorStack = new ArrayStack();
120        parentIdStack = new ArrayStack();
121        this.baseId = baseId;
122        this.topLevel = topLevel;
123        this.partitionTxn = partitionTxn;
124
125        if ( IS_DEBUG )
126        {
127            LOG_CURSOR.debug( "Creating ChildrenCursor {}", this );
128        }
129    }
130
131
132    /**
133     * {@inheritDoc}
134     */
135    protected String getUnsupportedMessage()
136    {
137        return UNSUPPORTED_MSG;
138    }
139
140
141    /**
142     * {@inheritDoc}
143     */
144    public void beforeFirst() throws LdapException, CursorException
145    {
146        checkNotClosed();
147        setAvailable( false );
148    }
149
150
151    /**
152     * {@inheritDoc}
153     */
154    public void afterLast() throws LdapException, CursorException
155    {
156        throw new UnsupportedOperationException( getUnsupportedMessage() );
157    }
158
159
160    /**
161     * {@inheritDoc}
162     */
163    public boolean first() throws LdapException, CursorException
164    {
165        beforeFirst();
166
167        return next();
168    }
169
170
171    /**
172     * {@inheritDoc}
173     */
174    public boolean last() throws LdapException, CursorException
175    {
176        throw new UnsupportedOperationException( getUnsupportedMessage() );
177    }
178
179
180    /**
181     * {@inheritDoc}
182     */
183    @Override
184    public boolean previous() throws LdapException, CursorException
185    {
186        checkNotClosed();
187
188        boolean hasPrevious = currentCursor.previous();
189
190        if ( hasPrevious )
191        {
192            IndexEntry entry = currentCursor.get();
193
194            if ( ( ( ParentIdAndRdn ) entry.getTuple().getKey() ).getParentId().equals( currentParentId ) )
195            {
196                prefetched = entry;
197                return true;
198            }
199        }
200
201        return false;
202    }
203
204
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public boolean next() throws LdapException, CursorException
210    {
211        checkNotClosed();
212        boolean finished = false;
213
214        while ( !finished )
215        {
216            boolean hasNext = currentCursor.next();
217
218            // We will use a depth first approach. The alternative (Breadth-first) would be
219            // too memory consuming. 
220            // The idea is to use a ChildrenCursor each time we have an entry with chidren, 
221            // and process recursively.
222            if ( hasNext )
223            {
224                IndexEntry cursorEntry = currentCursor.get();
225                ParentIdAndRdn parentIdAndRdn = ( ( ParentIdAndRdn ) ( cursorEntry.getKey() ) );
226
227                // Check that we aren't out of the cursor's limit
228                if ( !parentIdAndRdn.getParentId().equals( currentParentId ) )
229                {
230                    // Ok, we went too far. Unstack the cursor and return
231                    finished = cursorStack.isEmpty();
232
233                    if ( !finished )
234                    {
235                        try
236                        {
237                            currentCursor.close();
238                        }
239                        catch ( IOException ioe )
240                        {
241                            throw new LdapException( ioe.getMessage(), ioe );
242                        }
243
244                        currentCursor = ( Cursor<IndexEntry<ParentIdAndRdn, String>> ) cursorStack.pop();
245                        currentParentId = ( String ) parentIdStack.pop();
246                    }
247
248                    // And continue...
249                }
250                else
251                {
252                    // We have a candidate, it will be returned.
253                    if ( topLevel )
254                    {
255                        prefetched = new IndexEntry();
256                        prefetched.setId( cursorEntry.getId() );
257                        prefetched.setKey( baseId );
258                    }
259                    else
260                    {
261                        prefetched = cursorEntry;
262                    }
263
264                    // Check if the current entry has children or not.
265                    if ( parentIdAndRdn.getNbDescendants() > 0 )
266                    {
267                        String newParentId = ( String ) cursorEntry.getId();
268
269                        // Yes, then create a new cursor and go down one level
270                        Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = db.getRdnIndex().forwardCursor( partitionTxn );
271
272                        IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
273                        startingPos.setKey( new ParentIdAndRdn( newParentId, ( Rdn[] ) null ) );
274                        cursor.before( startingPos );
275
276                        cursorStack.push( currentCursor );
277                        parentIdStack.push( currentParentId );
278
279                        currentCursor = cursor;
280                        currentParentId = newParentId;
281                    }
282
283                    return true;
284                }
285            }
286            else
287            {
288                // The current cursor has been exhausted. Get back to the parent's cursor.
289                finished = cursorStack.isEmpty();
290
291                if ( !finished )
292                {
293                    try
294                    {
295                        currentCursor.close();
296                    }
297                    catch ( IOException ioe )
298                    {
299                        throw new LdapException( ioe.getMessage(), ioe );
300                    }
301
302                    currentCursor = ( Cursor<IndexEntry<ParentIdAndRdn, String>> ) cursorStack.pop();
303                    currentParentId = ( String ) parentIdStack.pop();
304                }
305                // and continue...
306            }
307        }
308
309        return false;
310    }
311
312
313    /**
314     * {@inheritDoc}
315     */
316    public IndexEntry<String, String> get() throws CursorException
317    {
318        checkNotClosed();
319
320        return prefetched;
321    }
322
323
324    /**
325     * {@inheritDoc}
326     */
327    @Override
328    public void close() throws IOException
329    {
330        if ( IS_DEBUG )
331        {
332            LOG_CURSOR.debug( "Closing ChildrenCursor {}", this );
333        }
334
335        // Close the cursors stored in the stack, if we have some
336        for ( Object cursor : cursorStack )
337        {
338            ( ( Cursor<IndexEntry<?, ?>> ) cursor ).close();
339        }
340
341        // And finally, close the current cursor
342        currentCursor.close();
343
344        super.close();
345    }
346
347
348    /**
349     * {@inheritDoc}
350     */
351    @Override
352    public void close( Exception cause ) throws IOException
353    {
354        if ( IS_DEBUG )
355        {
356            LOG_CURSOR.debug( "Closing ChildrenCursor {}", this );
357        }
358
359        // Close the cursors stored in the stack, if we have some
360        for ( Object cursor : cursorStack )
361        {
362            ( ( Cursor<IndexEntry<?, ?>> ) cursor ).close( cause );
363        }
364
365        // And finally, close the current cursor
366        currentCursor.close( cause );
367
368        super.close( cause );
369    }
370
371
372    /**
373     * Dumps the cursors
374     */
375    private String dumpCursors( String tabs )
376    {
377        StringBuilder sb = new StringBuilder();
378
379        for ( Object cursor : cursorStack )
380        {
381            sb.append( ( ( Cursor<IndexEntry<ParentIdAndRdn, String>> ) cursor ).toString( tabs + "  " ) );
382            sb.append( "\n" );
383        }
384
385        return sb.toString();
386    }
387
388
389    /**
390     * @see Object#toString()
391     */
392    @Override
393    public String toString( String tabs )
394    {
395        StringBuilder sb = new StringBuilder();
396
397        sb.append( tabs ).append( "DescendantCursor (" );
398
399        if ( available() )
400        {
401            sb.append( "available)" );
402        }
403        else
404        {
405            sb.append( "absent)" );
406        }
407
408        sb.append( "#baseId<" ).append( baseId );
409        sb.append( ", " ).append( db ).append( "> :\n" );
410
411        sb.append( dumpCursors( tabs + "  " ) );
412
413        if ( currentCursor != null )
414        {
415            sb.append( tabs + "  <current>\n" );
416            sb.append( currentCursor.toString( tabs + "    " ) );
417        }
418
419        return sb.toString();
420    }
421
422
423    /**
424     * @see Object#toString()
425     */
426    public String toString()
427    {
428        return toString( "" );
429    }
430}