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;
024import java.util.ArrayList;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import org.apache.directory.api.ldap.model.constants.Loggers;
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.exception.LdapException;
034import org.apache.directory.api.ldap.model.filter.ExprNode;
035import org.apache.directory.server.core.api.partition.PartitionTxn;
036import org.apache.directory.server.i18n.I18n;
037import org.apache.directory.server.xdbm.AbstractIndexCursor;
038import org.apache.directory.server.xdbm.IndexEntry;
039import org.apache.directory.server.xdbm.search.Evaluator;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043
044/**
045 * A Cursor returning candidates satisfying a logical disjunction expression.
046 *
047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048 */
049public class OrCursor<V> extends AbstractIndexCursor<V>
050{
051    /** A dedicated log for cursors */
052    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
053
054    /** Speedup for logs */
055    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
056
057    private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_722 );
058    private final List<Cursor<IndexEntry<V, String>>> cursors;
059    private final List<Evaluator<? extends ExprNode>> evaluators;
060    private final List<Set<String>> blacklists;
061    private int cursorIndex = -1;
062
063    /** The candidate we have fetched in the next/previous call */
064    private IndexEntry<V, String> prefetched;
065
066
067    /**
068     * Creates a new instance of an OrCursor
069     * 
070     * @param partitionTxn The transaction to use
071     * @param cursors The encapsulated Cursors
072     * @param evaluators The list of evaluators associated with the elements
073     */
074    // TODO - do same evaluator fail fast optimization that we do in AndCursor
075    public OrCursor( PartitionTxn partitionTxn, List<Cursor<IndexEntry<V, String>>> cursors,
076        List<Evaluator<? extends ExprNode>> evaluators )
077    {
078        if ( IS_DEBUG )
079        {
080            LOG_CURSOR.debug( "Creating OrCursor {}", this );
081        }
082
083        if ( cursors.size() <= 1 )
084        {
085            throw new IllegalArgumentException( I18n.err( I18n.ERR_723 ) );
086        }
087
088        this.cursors = cursors;
089        this.evaluators = evaluators;
090        this.blacklists = new ArrayList<>();
091        this.partitionTxn = partitionTxn;
092
093        for ( int i = 0; i < cursors.size(); i++ )
094        {
095            this.blacklists.add( new HashSet<String>() );
096        }
097
098        this.cursorIndex = 0;
099    }
100
101
102    /**
103     * {@inheritDoc}
104     */
105    protected String getUnsupportedMessage()
106    {
107        return UNSUPPORTED_MSG;
108    }
109
110
111    /**
112     * {@inheritDoc}
113     */
114    public void beforeFirst() throws LdapException, CursorException
115    {
116        checkNotClosed();
117        cursorIndex = 0;
118        cursors.get( cursorIndex ).beforeFirst();
119        setAvailable( false );
120        prefetched = null;
121    }
122
123
124    /**
125     * {@inheritDoc}
126     */
127    public void afterLast() throws LdapException, CursorException
128    {
129        checkNotClosed();
130        cursorIndex = cursors.size() - 1;
131        cursors.get( cursorIndex ).afterLast();
132        setAvailable( false );
133        prefetched = null;
134    }
135
136
137    /**
138     * {@inheritDoc}
139     */
140    public boolean first() throws LdapException, CursorException
141    {
142        beforeFirst();
143
144        return setAvailable( next() );
145    }
146
147
148    /**
149     * {@inheritDoc}
150     */
151    public boolean last() throws LdapException, CursorException
152    {
153        afterLast();
154
155        return setAvailable( previous() );
156    }
157
158
159    private boolean isBlackListed( String id )
160    {
161        return blacklists.get( cursorIndex ).contains( id );
162    }
163
164
165    /**
166     * The first sub-expression Cursor to advance to an entry adds the entry
167     * to the blacklists of other Cursors that might return that entry.
168     *
169     * @param indexEntry the index entry to blacklist
170     * @throws Exception if there are problems accessing underlying db
171     */
172    private void blackListIfDuplicate( PartitionTxn partitionTxn, IndexEntry<?, String> indexEntry ) throws LdapException
173    {
174        for ( int ii = 0; ii < evaluators.size(); ii++ )
175        {
176            if ( ii == cursorIndex )
177            {
178                continue;
179            }
180
181            if ( evaluators.get( ii ).evaluate( partitionTxn, indexEntry ) )
182            {
183                blacklists.get( ii ).add( indexEntry.getId() );
184            }
185        }
186    }
187
188
189    /**
190     * {@inheritDoc}
191     */
192    public boolean previous() throws LdapException, CursorException
193    {
194        while ( cursors.get( cursorIndex ).previous() )
195        {
196            checkNotClosed();
197            IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get();
198
199            if ( !isBlackListed( candidate.getId() ) )
200            {
201                blackListIfDuplicate( partitionTxn, candidate );
202
203                prefetched = candidate;
204                return setAvailable( true );
205            }
206        }
207
208        while ( cursorIndex > 0 )
209        {
210            checkNotClosed();
211            cursorIndex--;
212            cursors.get( cursorIndex ).afterLast();
213
214            while ( cursors.get( cursorIndex ).previous() )
215            {
216                checkNotClosed();
217                IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get();
218
219                if ( !isBlackListed( candidate.getId() ) )
220                {
221                    blackListIfDuplicate( partitionTxn, candidate );
222
223                    prefetched = candidate;
224                    return setAvailable( true );
225                }
226            }
227        }
228
229        prefetched = null;
230
231        return setAvailable( false );
232    }
233
234
235    /**
236     * {@inheritDoc}
237     */
238    public boolean next() throws LdapException, CursorException
239    {
240        while ( cursors.get( cursorIndex ).next() )
241        {
242            checkNotClosed();
243            IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get();
244
245            if ( !isBlackListed( candidate.getId() ) )
246            {
247                blackListIfDuplicate( partitionTxn, candidate );
248
249                prefetched = candidate;
250
251                return setAvailable( true );
252            }
253        }
254
255        while ( cursorIndex < cursors.size() - 1 )
256        {
257            checkNotClosed();
258            cursorIndex++;
259            cursors.get( cursorIndex ).beforeFirst();
260
261            while ( cursors.get( cursorIndex ).next() )
262            {
263                checkNotClosed();
264                IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get();
265
266                if ( !isBlackListed( candidate.getId() ) )
267                {
268                    blackListIfDuplicate( partitionTxn, candidate );
269
270                    prefetched = candidate;
271
272                    return setAvailable( true );
273                }
274            }
275        }
276
277        prefetched = null;
278
279        return setAvailable( false );
280    }
281
282
283    /**
284     * {@inheritDoc}
285     */
286    public IndexEntry<V, String> get() throws CursorException
287    {
288        checkNotClosed();
289
290        if ( available() )
291        {
292            return prefetched;
293        }
294
295        throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
296    }
297
298
299    /**
300     * {@inheritDoc}
301     */
302    @Override
303    public void close() throws IOException
304    {
305        if ( IS_DEBUG )
306        {
307            LOG_CURSOR.debug( "Closing OrCursor {}", this );
308        }
309
310        super.close();
311
312        for ( Cursor<?> cursor : cursors )
313        {
314            cursor.close();
315        }
316    }
317
318
319    /**
320     * {@inheritDoc}
321     */
322    @Override
323    public void close( Exception cause ) throws IOException
324    {
325        if ( IS_DEBUG )
326        {
327            LOG_CURSOR.debug( "Closing OrCursor {}", this );
328        }
329
330        super.close( cause );
331
332        for ( Cursor<?> cursor : cursors )
333        {
334            cursor.close( cause );
335        }
336    }
337
338
339    /**
340     * Dumps the evaluators
341     */
342    private String dumpEvaluators( String tabs )
343    {
344        StringBuilder sb = new StringBuilder();
345
346        for ( Evaluator<? extends ExprNode> evaluator : evaluators )
347        {
348            sb.append( evaluator.toString( tabs + "  >>" ) );
349        }
350
351        return sb.toString();
352    }
353
354
355    /**
356     * Dumps the cursors
357     */
358    private String dumpCursors( String tabs )
359    {
360        StringBuilder sb = new StringBuilder();
361
362        for ( Cursor<IndexEntry<V, String>> cursor : cursors )
363        {
364            sb.append( cursor.toString( tabs + "  " ) );
365            sb.append( "\n" );
366        }
367
368        return sb.toString();
369    }
370
371
372    /**
373     * @see Object#toString()
374     */
375    @Override
376    public String toString( String tabs )
377    {
378        StringBuilder sb = new StringBuilder();
379
380        sb.append( tabs ).append( "OrCursor (" );
381
382        if ( available() )
383        {
384            sb.append( "available)" );
385        }
386        else
387        {
388            sb.append( "absent)" );
389        }
390
391        sb.append( "#" ).append( cursorIndex ).append( " : \n" );
392
393        if ( ( evaluators != null ) && !evaluators.isEmpty() )
394        {
395            sb.append( dumpEvaluators( tabs ) );
396        }
397
398        if ( ( cursors != null ) && !cursors.isEmpty() )
399        {
400            sb.append( dumpCursors( tabs ) ).append( '\n' );
401        }
402
403        return sb.toString();
404    }
405
406
407    /**
408     * @see Object#toString()
409     */
410    public String toString()
411    {
412        return toString( "" );
413    }
414}