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