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.IndexEntry;
025    import org.apache.directory.server.xdbm.Store;
026    import org.apache.directory.server.xdbm.AbstractIndexCursor;
027    import org.apache.directory.server.xdbm.IndexCursor;
028    import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
029    import org.apache.directory.shared.ldap.entry.ServerEntry;
030    
031    
032    /**
033     * A Cursor over entries satisfying scope constraints with alias dereferencing
034     * considerations.
035     *
036     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
037     * @version $Rev$
038     */
039    public class SubtreeScopeCursor<ID> extends AbstractIndexCursor<ID, ServerEntry, ID>
040    {
041        private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_719 );
042    
043        /** The Entry database/store */
044        private final Store<ServerEntry, ID> db;
045    
046        /** A ScopeNode Evaluator */
047        private final SubtreeScopeEvaluator<ServerEntry, ID> evaluator;
048    
049        /** A Cursor over the entries in the scope of the search base */
050        private final IndexCursor<ID, ServerEntry, ID> scopeCursor;
051    
052        /** A Cursor over entries brought into scope by alias dereferencing */
053        private final IndexCursor<ID, ServerEntry, ID> dereferencedCursor;
054    
055        /** Currently active Cursor: we switch between two cursors */
056        private IndexCursor<ID, ServerEntry, ID> cursor;
057    
058        /** Whether or not this Cursor is positioned so an entry is available */
059        private boolean available = false;
060    
061        private ID contextEntryId;
062    
063    
064        /**
065         * Creates a Cursor over entries satisfying subtree level scope criteria.
066         *
067         * @param db the entry store
068         * @param evaluator an IndexEntry (candidate) evaluator
069         * @throws Exception on db access failures
070         */
071        public SubtreeScopeCursor( Store<ServerEntry, ID> db, SubtreeScopeEvaluator<ServerEntry, ID> evaluator )
072            throws Exception
073        {
074            this.db = db;
075            this.evaluator = evaluator;
076    
077            if ( evaluator.getBaseId() == getContextEntryId() )
078            {
079                scopeCursor = new AllEntriesCursor<ID>( db );
080            }
081            else
082            {
083                scopeCursor = db.getSubLevelIndex().forwardCursor( evaluator.getBaseId() );
084            }
085    
086            if ( evaluator.isDereferencing() )
087            {
088                dereferencedCursor = db.getSubAliasIndex().forwardCursor( evaluator.getBaseId() );
089            }
090            else
091            {
092                dereferencedCursor = null;
093            }
094        }
095    
096    
097        private ID getContextEntryId() throws Exception
098        {
099            if ( contextEntryId == null )
100            {
101                try
102                {
103                    this.contextEntryId = db.getEntryId( db.getSuffix().getNormName() );
104                }
105                catch ( Exception e )
106                {
107                    // might not have been created
108                    // might not have been created
109                }
110            }
111    
112            if ( contextEntryId == null )
113            {
114                return db.getDefaultId();
115            }
116    
117            return contextEntryId;
118        }
119    
120    
121        public boolean available()
122        {
123            return available;
124        }
125    
126    
127        public void beforeValue( ID id, ID value ) throws Exception
128        {
129            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
130        }
131    
132    
133        public void before( IndexEntry<ID, ServerEntry, ID> element ) throws Exception
134        {
135            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
136        }
137    
138    
139        public void afterValue( ID id, ID value ) throws Exception
140        {
141            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
142        }
143    
144    
145        public void after( IndexEntry<ID, ServerEntry, ID> element ) throws Exception
146        {
147            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
148        }
149    
150    
151        public void beforeFirst() throws Exception
152        {
153            checkNotClosed( "beforeFirst()" );
154            cursor = scopeCursor;
155            cursor.beforeFirst();
156            available = false;
157        }
158    
159    
160        public void afterLast() throws Exception
161        {
162            checkNotClosed( "afterLast()" );
163            if ( evaluator.isDereferencing() )
164            {
165                cursor = dereferencedCursor;
166            }
167            else
168            {
169                cursor = scopeCursor;
170            }
171    
172            cursor.afterLast();
173            available = false;
174        }
175    
176    
177        public boolean first() throws Exception
178        {
179            beforeFirst();
180            return next();
181        }
182    
183    
184        public boolean last() throws Exception
185        {
186            afterLast();
187            return previous();
188        }
189    
190    
191        public boolean previous() throws Exception
192        {
193            checkNotClosed( "previous()" );
194            // if the cursor has not been set - position it after last element
195            if ( cursor == null )
196            {
197                afterLast();
198            }
199    
200            // if we're using the scopeCursor (1st Cursor) then return result as is
201            if ( cursor == scopeCursor )
202            {
203                /*
204                 * If dereferencing is enabled then we must ignore alias entries, not
205                 * returning them as part of the results.
206                 */
207                if ( evaluator.isDereferencing() )
208                {
209                    // advance until nothing is available or until we find a non-alias
210                    do
211                    {
212                        checkNotClosed( "previous()" );
213                        available = cursor.previous();
214                        if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
215                        {
216                            break;
217                        }
218                    }
219                    while ( available );
220                }
221                else
222                {
223                    available = cursor.previous();
224                }
225    
226                return available;
227            }
228    
229            /*
230             * Below here we are using the dereferencedCursor so if nothing is
231             * available after an advance backwards we need to switch to the
232             * scopeCursor and try a previous call after positioning past it's
233             * last element.
234             */
235            available = cursor.previous();
236            if ( !available )
237            {
238                cursor = scopeCursor;
239                cursor.afterLast();
240    
241                // advance until nothing is available or until we find a non-alias
242                do
243                {
244                    checkNotClosed( "previous()" );
245                    available = cursor.previous();
246    
247                    if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
248                    {
249                        break;
250                    }
251                }
252                while ( available );
253    
254                return available;
255            }
256    
257            return true;
258        }
259    
260    
261        public boolean next() throws Exception
262        {
263            checkNotClosed( "next()" );
264            // if the cursor hasn't been set position it before the first element
265            if ( cursor == null )
266            {
267                beforeFirst();
268            }
269    
270            /*
271             * If dereferencing is enabled then we must ignore alias entries, not
272             * returning them as part of the results.
273             */
274            if ( evaluator.isDereferencing() )
275            {
276                // advance until nothing is available or until we find a non-alias
277                do
278                {
279                    checkNotClosed( "next()" );
280                    available = cursor.next();
281    
282                    if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
283                    {
284                        break;
285                    }
286                }
287                while ( available );
288            }
289            else
290            {
291                available = cursor.next();
292            }
293    
294            // if we're using dereferencedCursor (2nd) then we return the result
295            if ( cursor == dereferencedCursor )
296            {
297                return available;
298            }
299    
300            /*
301             * Below here we are using the scopeCursor so if nothing is
302             * available after an advance forward we need to switch to the
303             * dereferencedCursor and try a previous call after positioning past
304             * it's last element.
305             */
306            if ( !available )
307            {
308                if ( dereferencedCursor != null )
309                {
310                    cursor = dereferencedCursor;
311                    cursor.beforeFirst();
312                    return available = cursor.next();
313                }
314    
315                return false;
316            }
317    
318            return true;
319        }
320    
321    
322        public IndexEntry<ID, ServerEntry, ID> get() throws Exception
323        {
324            checkNotClosed( "get()" );
325            if ( available )
326            {
327                return cursor.get();
328            }
329    
330            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
331        }
332    
333    
334        public boolean isElementReused()
335        {
336            return scopeCursor.isElementReused() || ( dereferencedCursor != null && dereferencedCursor.isElementReused() );
337        }
338    }