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.AbstractIndexCursor;
025    import org.apache.directory.server.xdbm.ForwardIndexEntry;
026    import org.apache.directory.server.xdbm.Index;
027    import org.apache.directory.server.xdbm.IndexCursor;
028    import org.apache.directory.server.xdbm.IndexEntry;
029    import org.apache.directory.server.xdbm.Store;
030    import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
031    import org.apache.directory.shared.ldap.entry.ServerEntry;
032    
033    
034    /**
035     * A Cursor over entry candidates matching a GreaterEq assertion filter.  This
036     * Cursor operates in two modes.  The first is when an index exists for the
037     * attribute the assertion is built on.  The second is when the user index for
038     * the assertion attribute does not exist.  Different Cursors are used in each
039     * of these cases where the other remains null.
040     *
041     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042     * @version $Rev$
043     */
044    public class GreaterEqCursor<V, ID> extends AbstractIndexCursor<V, ServerEntry, ID>
045    {
046        private static final String UNSUPPORTED_MSG = "GreaterEqCursors only support positioning by element when a user index exists on the asserted attribute.";
047    
048        /** An greater eq evaluator for candidates */
049        private final GreaterEqEvaluator<V, ID> greaterEqEvaluator;
050    
051        /** Cursor over attribute entry matching filter: set when index present */
052        private final IndexCursor<V, ServerEntry, ID> userIdxCursor;
053    
054        /** NDN Cursor on all entries in  (set when no index on user attribute) */
055        private final IndexCursor<String, ServerEntry, ID> ndnIdxCursor;
056    
057        /**
058         * Used to store indexEntry from ndnCandidate so it can be saved after
059         * call to evaluate() which changes the value so it's not referring to
060         * the NDN but to the value of the attribute instead.
061         */
062        IndexEntry<String, ServerEntry, ID> ndnCandidate;
063    
064        /** used in both modes */
065        private boolean available = false;
066    
067    
068        @SuppressWarnings("unchecked")
069        public GreaterEqCursor( Store<ServerEntry, ID> db, GreaterEqEvaluator greaterEqEvaluator ) throws Exception
070        {
071            this.greaterEqEvaluator = greaterEqEvaluator;
072    
073            String attribute = greaterEqEvaluator.getExpression().getAttribute();
074            if ( db.hasIndexOn( attribute ) )
075            {
076                userIdxCursor = ( ( Index<V, ServerEntry, ID> ) db.getIndex( attribute ) ).forwardCursor();
077                ndnIdxCursor = null;
078            }
079            else
080            {
081                ndnIdxCursor = db.getNdnIndex().forwardCursor();
082                userIdxCursor = null;
083            }
084        }
085    
086    
087        public boolean available()
088        {
089            return available;
090        }
091    
092    
093        @SuppressWarnings("unchecked")
094        public void beforeValue( ID id, V value ) throws Exception
095        {
096            checkNotClosed( "beforeValue()" );
097            if ( userIdxCursor != null )
098            {
099                /*
100                 * First we need to check and make sure this element is within
101                 * bounds as mandated by the assertion node.  To do so we compare
102                 * it's value with the value of the node.  If it is smaller or
103                 * equal to this lower bound then we simply position the
104                 * userIdxCursor before the first element.  Otherwise we let the
105                 * underlying userIdx Cursor position the element.
106                 */
107                if ( greaterEqEvaluator.getComparator()
108                    .compare( value, greaterEqEvaluator.getExpression().getValue().get() ) <= 0 )
109                {
110                    beforeFirst();
111                    return;
112                }
113    
114                userIdxCursor.beforeValue( id, value );
115                available = false;
116            }
117            else
118            {
119                throw new UnsupportedOperationException( UNSUPPORTED_MSG );
120            }
121        }
122    
123    
124        @SuppressWarnings("unchecked")
125        public void afterValue( ID id, V value ) throws Exception
126        {
127            checkNotClosed( "afterValue()" );
128            if ( userIdxCursor != null )
129            {
130                int comparedValue = greaterEqEvaluator.getComparator().compare( value,
131                    greaterEqEvaluator.getExpression().getValue().get() );
132    
133                /*
134                 * First we need to check and make sure this element is within
135                 * bounds as mandated by the assertion node.  To do so we compare
136                 * it's value with the value of the node.  If it is equal to this
137                 * lower bound then we simply position the userIdxCursor after
138                 * this first node.  If it is less than this value then we
139                 * position the Cursor before the first entry.
140                 */
141                if ( comparedValue == 0 )
142                {
143                    userIdxCursor.afterValue( id, value );
144                    available = false;
145                    return;
146                }
147                else if ( comparedValue < 0 )
148                {
149                    beforeFirst();
150                    return;
151                }
152    
153                // Element is in the valid range as specified by assertion
154                userIdxCursor.afterValue( id, value );
155                available = false;
156            }
157            else
158            {
159                throw new UnsupportedOperationException( UNSUPPORTED_MSG );
160            }
161        }
162    
163    
164        @SuppressWarnings("unchecked")
165        public void before( IndexEntry<V, ServerEntry, ID> element ) throws Exception
166        {
167            checkNotClosed( "before()" );
168            if ( userIdxCursor != null )
169            {
170                /*
171                 * First we need to check and make sure this element is within
172                 * bounds as mandated by the assertion node.  To do so we compare
173                 * it's value with the value of the node.  If it is smaller or
174                 * equal to this lower bound then we simply position the
175                 * userIdxCursor before the first element.  Otherwise we let the
176                 * underlying userIdx Cursor position the element.
177                 */
178                if ( greaterEqEvaluator.getComparator().compare( element.getValue(),
179                    greaterEqEvaluator.getExpression().getValue().get() ) <= 0 )
180                {
181                    beforeFirst();
182                    return;
183                }
184    
185                userIdxCursor.before( element );
186                available = false;
187            }
188            else
189            {
190                throw new UnsupportedOperationException( UNSUPPORTED_MSG );
191            }
192        }
193    
194    
195        @SuppressWarnings("unchecked")
196        public void after( IndexEntry<V, ServerEntry, ID> element ) throws Exception
197        {
198            checkNotClosed( "after()" );
199            if ( userIdxCursor != null )
200            {
201                int comparedValue = greaterEqEvaluator.getComparator().compare( element.getValue(),
202                    greaterEqEvaluator.getExpression().getValue().get() );
203    
204                /*
205                 * First we need to check and make sure this element is within
206                 * bounds as mandated by the assertion node.  To do so we compare
207                 * it's value with the value of the node.  If it is equal to this
208                 * lower bound then we simply position the userIdxCursor after
209                 * this first node.  If it is less than this value then we
210                 * position the Cursor before the first entry.
211                 */
212                if ( comparedValue == 0 )
213                {
214                    userIdxCursor.after( element );
215                    available = false;
216                    return;
217                }
218                else if ( comparedValue < 0 )
219                {
220                    beforeFirst();
221                    return;
222                }
223    
224                // Element is in the valid range as specified by assertion
225                userIdxCursor.after( element );
226                available = false;
227            }
228            else
229            {
230                throw new UnsupportedOperationException( UNSUPPORTED_MSG );
231            }
232        }
233    
234    
235        @SuppressWarnings("unchecked")
236        public void beforeFirst() throws Exception
237        {
238            checkNotClosed( "beforeFirst()" );
239            if ( userIdxCursor != null )
240            {
241                IndexEntry<V, ServerEntry, ID> advanceTo = new ForwardIndexEntry<V, ServerEntry, ID>();
242                advanceTo.setValue( ( V ) greaterEqEvaluator.getExpression().getValue().get() );
243                userIdxCursor.before( advanceTo );
244            }
245            else
246            {
247                ndnIdxCursor.beforeFirst();
248                ndnCandidate = null;
249            }
250    
251            available = false;
252        }
253    
254    
255        public void afterLast() throws Exception
256        {
257            checkNotClosed( "afterLast()" );
258            if ( userIdxCursor != null )
259            {
260                userIdxCursor.afterLast();
261            }
262            else
263            {
264                ndnIdxCursor.afterLast();
265                ndnCandidate = null;
266            }
267    
268            available = false;
269        }
270    
271    
272        public boolean first() throws Exception
273        {
274            beforeFirst();
275            return next();
276        }
277    
278    
279        public boolean last() throws Exception
280        {
281            afterLast();
282            return previous();
283        }
284    
285    
286        @SuppressWarnings("unchecked")
287        public boolean previous() throws Exception
288        {
289            checkNotClosed( "previous()" );
290            if ( userIdxCursor != null )
291            {
292                /*
293                 * We have to check and make sure the previous value complies by
294                 * being greater than or eq to the expression node's value
295                 */
296                while ( userIdxCursor.previous() )
297                {
298                    checkNotClosed( "previous()" );
299                    IndexEntry<?, ServerEntry, ID> candidate = userIdxCursor.get();
300                    if ( greaterEqEvaluator.getComparator().compare( candidate.getValue(),
301                        greaterEqEvaluator.getExpression().getValue().get() ) >= 0 )
302                    {
303                        return available = true;
304                    }
305                }
306    
307                return available = false;
308            }
309    
310            while ( ndnIdxCursor.previous() )
311            {
312                checkNotClosed( "previous()" );
313                ndnCandidate = ndnIdxCursor.get();
314                if ( greaterEqEvaluator.evaluate( ndnCandidate ) )
315                {
316                    return available = true;
317                }
318            }
319    
320            return available = false;
321        }
322    
323    
324        public boolean next() throws Exception
325        {
326            checkNotClosed( "next()" );
327            if ( userIdxCursor != null )
328            {
329                /*
330                 * No need to do the same check that is done in previous() since
331                 * values are increasing with calls to next().
332                 */
333                return available = userIdxCursor.next();
334            }
335    
336            while ( ndnIdxCursor.next() )
337            {
338                checkNotClosed( "next()" );
339                ndnCandidate = ndnIdxCursor.get();
340                if ( greaterEqEvaluator.evaluate( ndnCandidate ) )
341                {
342                    return available = true;
343                }
344            }
345    
346            return available = false;
347        }
348    
349    
350        @SuppressWarnings("unchecked")
351        public IndexEntry<V, ServerEntry, ID> get() throws Exception
352        {
353            checkNotClosed( "get()" );
354            if ( userIdxCursor != null )
355            {
356                if ( available )
357                {
358                    return userIdxCursor.get();
359                }
360    
361                throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
362            }
363    
364            if ( available )
365            {
366                return ( IndexEntry<V, ServerEntry, ID> ) ndnCandidate;
367            }
368    
369            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
370        }
371    
372    
373        public boolean isElementReused()
374        {
375            if ( userIdxCursor != null )
376            {
377                return userIdxCursor.isElementReused();
378            }
379    
380            return ndnIdxCursor.isElementReused();
381        }
382    
383    
384        public void close() throws Exception
385        {
386            super.close();
387    
388            if ( userIdxCursor != null )
389            {
390                userIdxCursor.close();
391            }
392            else
393            {
394                ndnIdxCursor.close();
395            }
396        }
397    }