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 */
019package org.apache.directory.server.core.api.filtering;
020
021
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025
026import org.apache.directory.api.i18n.I18n;
027import org.apache.directory.api.ldap.model.constants.Loggers;
028import org.apache.directory.api.ldap.model.cursor.AbstractCursor;
029import org.apache.directory.api.ldap.model.cursor.ClosureMonitor;
030import org.apache.directory.api.ldap.model.cursor.Cursor;
031import org.apache.directory.api.ldap.model.cursor.CursorException;
032import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
033import org.apache.directory.api.ldap.model.entry.Entry;
034import org.apache.directory.api.ldap.model.exception.LdapException;
035import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039
040/**
041 * An implementation of a Cursor based on a {@link List} of {@link Cursor}s.  Optionally, the
042 * Cursor may be limited to a specific range within the list.
043 * 
044 * This class is modeled based on the implementation of {@link org.apache.directory.api.ldap.model.cursor.ListCursor}
045 * 
046 * WARN this is only used internally !
047 * 
048 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
049 */
050public class CursorList extends AbstractCursor<Entry> implements EntryFilteringCursor
051{
052    /** A dedicated log for cursors */
053    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
054
055    /** Speedup for logs */
056    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
057
058    /** The inner List */
059    private final List<EntryFilteringCursor> list;
060
061    /** The starting position for the cursor in the list. It can be > 0 */
062    private final int start;
063
064    /** The ending position for the cursor in the list. It can be < List.size() */
065    private final int end;
066
067    /** The number of cursors in the list */
068    private final int listSize;
069
070    /** The current position in the list */
071    private int index;
072
073    /** The current cursor being used */
074    private EntryFilteringCursor currentCursor;
075
076    /** the operation context */
077    private SearchOperationContext searchContext;
078
079    /** flag to detect the closed cursor */
080    private boolean closed;
081
082    /** The logger for this class */
083    private static final Logger LOG = LoggerFactory.getLogger( CursorList.class );
084
085
086    /**
087     * Creates a new ListCursor with lower (inclusive) and upper (exclusive)
088     * bounds.
089     *
090     * As with all Cursors, this ListCursor requires a successful return from
091     * advance operations (next() or previous()) to properly return values
092     * using the get() operation.
093     *
094     * @param start the lower bound index
095     * @param list the list this ListCursor operates on
096     * @param end the upper bound index
097     * @param searchContext The SearchContext instance
098     */
099    public CursorList( int start, List<EntryFilteringCursor> list, int end, SearchOperationContext searchContext )
100    {
101        if ( IS_DEBUG )
102        {
103            LOG_CURSOR.debug( "Creating CursorList {}", this );
104        }
105
106        if ( list != null )
107        {
108            this.list = list;
109        }
110        else
111        {
112            this.list = Collections.emptyList();
113        }
114
115        listSize = this.list.size();
116
117        if ( ( start < 0 ) || ( start > listSize ) )
118        {
119            throw new IllegalArgumentException( I18n.err( I18n.ERR_13105_START_INDEX_OUT_OF_RANGE, start ) );
120        }
121
122        if ( ( end < 0 ) || ( end > listSize ) )
123        {
124            throw new IllegalArgumentException( I18n.err( I18n.ERR_13106_END_INDEX_OUT_OF_RANGE, end ) );
125        }
126
127        // check list is not empty list since the empty list is the only situation
128        // where we allow for start to equal the end: in other cases it makes no sense
129        if ( ( listSize > 0 ) && ( start >= end ) )
130        {
131            throw new IllegalArgumentException( I18n.err( I18n.ERR_13107_START_INDEX_ABOVE_END_INDEX, start, end ) );
132        }
133
134        this.start = start;
135        this.end = end;
136        this.searchContext = searchContext;
137        index = start;
138        currentCursor = this.list.get( index );
139    }
140
141
142    /**
143     * Creates a new ListCursor without specific bounds: the bounds are
144     * acquired from the size of the list.
145     *
146     * @param list the backing for this ListCursor
147     * @param searchContext The SearchContext instance
148     */
149    public CursorList( List<EntryFilteringCursor> list, SearchOperationContext searchContext )
150    {
151        this( 0, list, list.size(), searchContext );
152    }
153
154
155    /**
156     * {@inheritDoc}
157     */
158    public boolean available()
159    {
160        if ( ( index >= 0 ) && ( index < end ) )
161        {
162            return list.get( index ).available();
163        }
164
165        return false;
166    }
167
168
169    /**
170     * {@inheritDoc}
171     */
172    public void before( Entry element ) throws LdapException, CursorException
173    {
174        throw new UnsupportedOperationException( I18n.err( I18n.ERR_13108_LIST_MAY_BE_SORTED ) );
175    }
176
177
178    /**
179     * {@inheritDoc}
180     */
181    public void after( Entry element ) throws LdapException, CursorException
182    {
183        throw new UnsupportedOperationException( I18n.err( I18n.ERR_13108_LIST_MAY_BE_SORTED ) );
184    }
185
186
187    /**
188     * {@inheritDoc}
189     */
190    public void beforeFirst() throws LdapException, CursorException
191    {
192        index = 0;
193        currentCursor = list.get( index );
194        currentCursor.beforeFirst();
195    }
196
197
198    /**
199     * {@inheritDoc}
200     */
201    public void afterLast() throws LdapException, CursorException
202    {
203        index = end - 1;
204        currentCursor = list.get( index );
205        currentCursor.afterLast();
206    }
207
208
209    /**
210     * {@inheritDoc}
211     */
212    public boolean first() throws LdapException, CursorException
213    {
214        if ( listSize > 0 )
215        {
216            index = start;
217
218            return list.get( index ).first();
219        }
220
221        return false;
222    }
223
224
225    /**
226     * {@inheritDoc}
227     */
228    public boolean last() throws LdapException, CursorException
229    {
230        if ( listSize > 0 )
231        {
232            index = end - 1;
233            currentCursor = list.get( index );
234
235            return currentCursor.last();
236        }
237
238        return false;
239    }
240
241
242    /**
243     * {@inheritDoc}
244     */
245    public boolean isFirst()
246    {
247        return ( listSize > 0 ) && ( index == start ) && list.get( index ).isFirst();
248    }
249
250
251    /**
252     * {@inheritDoc}
253     */
254    public boolean isLast()
255    {
256        return ( listSize > 0 ) && ( index == end - 1 ) && list.get( index ).isLast();
257    }
258
259
260    /**
261     * {@inheritDoc}
262     */
263    public boolean isAfterLast()
264    {
265        return ( index == end );
266    }
267
268
269    /**
270     * {@inheritDoc}
271     */
272    public boolean isBeforeFirst()
273    {
274        return index == -1;
275    }
276
277
278    /**
279     * {@inheritDoc}
280     */
281    public boolean previous() throws LdapException, CursorException
282    {
283        while ( index > -1 )
284        {
285            currentCursor = list.get( index );
286
287            if ( currentCursor.previous() )
288            {
289                return true;
290            }
291            else
292            {
293                index--;
294            }
295        }
296
297        return false;
298    }
299
300
301    /**
302     * {@inheritDoc}
303     */
304    public boolean next() throws LdapException, CursorException
305    {
306        if ( listSize > 0 )
307        {
308            if ( index == -1 )
309            {
310                index = start;
311            }
312
313            while ( index < end )
314            {
315                currentCursor = list.get( index );
316
317                if ( currentCursor.next() )
318                {
319                    return true;
320                }
321                else
322                {
323                    index++;
324                }
325            }
326        }
327
328        return false;
329    }
330
331
332    /**
333     * {@inheritDoc}
334     */
335    public Entry get() throws CursorException
336    {
337        if ( ( index < start ) || ( index >= end ) )
338        {
339            throw new CursorException( I18n.err( I18n.ERR_13109_CURSOR_NOT_POSITIONED ) );
340        }
341
342        if ( currentCursor.available() )
343        {
344            return currentCursor.get();
345        }
346
347        throw new InvalidCursorPositionException();
348    }
349
350
351    /**
352     * {@inheritDoc}
353     */
354    public boolean addEntryFilter( EntryFilter filter )
355    {
356        for ( EntryFilteringCursor efc : list )
357        {
358            efc.addEntryFilter( filter );
359        }
360
361        // returning hard coded value, shouldn't be a problem
362        return true;
363    }
364
365
366    /**
367     * {@inheritDoc}
368     */
369    public List<EntryFilter> getEntryFilters()
370    {
371        throw new UnsupportedOperationException( "CursorList doesn't support this operation" );
372    }
373
374
375    /**
376     * {@inheritDoc}
377     */
378    public SearchOperationContext getOperationContext()
379    {
380        return searchContext;
381    }
382
383
384    public boolean isAbandoned()
385    {
386        return searchContext.isAbandoned();
387    }
388
389
390    public void setAbandoned( boolean abandoned )
391    {
392        searchContext.setAbandoned( abandoned );
393
394        if ( abandoned )
395        {
396            LOG.info( "Cursor has been abandoned." );
397        }
398    }
399
400
401    /**
402     * {@inheritDoc}
403     */
404    public void close()
405    {
406        if ( IS_DEBUG )
407        {
408            LOG_CURSOR.debug( "Closing CursorList {}", this );
409        }
410
411        close( null );
412    }
413
414
415    /**
416     * {@inheritDoc}
417     */
418    public void close( Exception reason )
419    {
420        if ( IS_DEBUG )
421        {
422            LOG_CURSOR.debug( "Closing CursorList {}", this );
423        }
424
425        closed = true;
426
427        for ( EntryFilteringCursor cursor : list )
428        {
429            try
430            {
431                if ( reason != null )
432                {
433                    cursor.close( reason );
434                }
435                else
436                {
437                    cursor.close();
438                }
439            }
440            catch ( Exception e )
441            {
442                LOG.warn( "Failed to close the cursor" );
443            }
444        }
445    }
446
447
448    /**
449     * {@inheritDoc}
450     */
451    public boolean isClosed()
452    {
453        return closed;
454    }
455
456
457    public Iterator<Entry> iterator()
458    {
459        throw new UnsupportedOperationException();
460    }
461
462
463    /**
464     * {@inheritDoc}
465     */
466    public void setClosureMonitor( ClosureMonitor monitor )
467    {
468        for ( EntryFilteringCursor c : list )
469        {
470            c.setClosureMonitor( monitor );
471        }
472    }
473}