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;
024
025import org.apache.directory.api.ldap.model.constants.Loggers;
026import org.apache.directory.api.ldap.model.cursor.Cursor;
027import org.apache.directory.api.ldap.model.cursor.CursorException;
028import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.server.core.api.partition.PartitionTxn;
032import org.apache.directory.server.i18n.I18n;
033import org.apache.directory.server.xdbm.AbstractIndexCursor;
034import org.apache.directory.server.xdbm.IndexEntry;
035import org.apache.directory.server.xdbm.Store;
036import org.apache.directory.server.xdbm.search.evaluator.PresenceEvaluator;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040
041/**
042 * A returning candidates satisfying an attribute presence expression.
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046public class PresenceCursor extends AbstractIndexCursor<String>
047{
048    /** A dedicated log for cursors */
049    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
050
051    /** Speedup for logs */
052    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
053
054    private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_724 );
055    private final Cursor<IndexEntry<String, String>> uuidCursor;
056    private final Cursor<IndexEntry<String, String>> presenceCursor;
057    private final PresenceEvaluator presenceEvaluator;
058
059    /** The prefetched entry, if it's a valid one */
060    private IndexEntry<String, String> prefetched;
061
062
063    /**
064     * Creates a new instance of an PresenceCursor
065     * 
066     * @param partitionTxn The transaction to use
067     * @param store The store
068     * @param presenceEvaluator The Presence evaluator
069     * @throws LdapException If the cursor can't be created
070     */
071    public PresenceCursor( PartitionTxn partitionTxn, Store store, PresenceEvaluator presenceEvaluator ) 
072            throws LdapException
073    {
074        if ( IS_DEBUG )
075        {
076            LOG_CURSOR.debug( "Creating PresenceCursor {}", this );
077        }
078
079        this.presenceEvaluator = presenceEvaluator;
080        this.partitionTxn = partitionTxn;
081        AttributeType type = presenceEvaluator.getAttributeType();
082
083        // we don't maintain a presence index for objectClass, and entryCSN
084        // as it doesn't make sense because every entry has such an attribute
085        // instead for those attributes and all un-indexed attributes we use the ndn index
086        if ( store.hasUserIndexOn( type ) )
087        {
088            presenceCursor = store.getPresenceIndex().forwardCursor( partitionTxn, type.getOid() );
089            uuidCursor = null;
090        }
091        else
092        {
093            presenceCursor = null;
094            uuidCursor = new AllEntriesCursor( partitionTxn, store );
095        }
096    }
097
098
099    /**
100     * {@inheritDoc}
101     */
102    protected String getUnsupportedMessage()
103    {
104        return UNSUPPORTED_MSG;
105    }
106
107
108    /**
109     * {@inheritDoc}
110     */
111    @Override
112    public boolean available()
113    {
114        if ( presenceCursor != null )
115        {
116            return presenceCursor.available();
117        }
118
119        return super.available();
120    }
121
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public void before( IndexEntry<String, String> element ) throws LdapException, CursorException
128    {
129        checkNotClosed();
130
131        if ( presenceCursor != null )
132        {
133            presenceCursor.before( element );
134
135            return;
136        }
137
138        super.before( element );
139    }
140
141
142    /**
143     * {@inheritDoc}
144     */
145    @Override
146    public void after( IndexEntry<String, String> element ) throws LdapException, CursorException
147    {
148        checkNotClosed();
149
150        if ( presenceCursor != null )
151        {
152            presenceCursor.after( element );
153
154            return;
155        }
156
157        super.after( element );
158    }
159
160
161    /**
162     * {@inheritDoc}
163     */
164    public void beforeFirst() throws LdapException, CursorException
165    {
166        checkNotClosed();
167
168        if ( presenceCursor != null )
169        {
170            presenceCursor.beforeFirst();
171
172            return;
173        }
174
175        uuidCursor.beforeFirst();
176        setAvailable( false );
177    }
178
179
180    /**
181     * {@inheritDoc}
182     */
183    public void afterLast() throws LdapException, CursorException
184    {
185        checkNotClosed();
186
187        if ( presenceCursor != null )
188        {
189            presenceCursor.afterLast();
190            return;
191        }
192
193        uuidCursor.afterLast();
194        setAvailable( false );
195    }
196
197
198    /**
199     * {@inheritDoc}
200     */
201    public boolean first() throws LdapException, CursorException
202    {
203        checkNotClosed();
204        if ( presenceCursor != null )
205        {
206            return presenceCursor.first();
207        }
208
209        beforeFirst();
210        return next();
211    }
212
213
214    /**
215     * {@inheritDoc}
216     */
217    public boolean last() throws LdapException, CursorException
218    {
219        checkNotClosed();
220
221        if ( presenceCursor != null )
222        {
223            return presenceCursor.last();
224        }
225
226        afterLast();
227
228        return previous();
229    }
230
231
232    /**
233     * {@inheritDoc}
234     */
235    @Override
236    public boolean previous() throws LdapException, CursorException
237    {
238        checkNotClosed();
239
240        if ( presenceCursor != null )
241        {
242            return presenceCursor.previous();
243        }
244
245        while ( uuidCursor.previous() )
246        {
247            checkNotClosed();
248            IndexEntry<?, String> candidate = uuidCursor.get();
249
250            if ( presenceEvaluator.evaluate( partitionTxn, candidate ) )
251            {
252                return setAvailable( true );
253            }
254        }
255
256        return setAvailable( false );
257    }
258
259
260    /**
261     * {@inheritDoc}
262     */
263    @Override
264    public boolean next() throws LdapException, CursorException
265    {
266        checkNotClosed();
267
268        if ( presenceCursor != null )
269        {
270            return presenceCursor.next();
271        }
272
273        while ( uuidCursor.next() )
274        {
275            checkNotClosed();
276            IndexEntry<String, String> candidate = uuidCursor.get();
277
278            if ( presenceEvaluator.evaluate( partitionTxn, candidate ) )
279            {
280                prefetched = candidate;
281
282                return setAvailable( true );
283            }
284        }
285
286        return setAvailable( false );
287    }
288
289
290    /**
291     * {@inheritDoc}
292     */
293    public IndexEntry<String, String> get() throws CursorException
294    {
295        checkNotClosed();
296
297        if ( presenceCursor != null )
298        {
299            if ( presenceCursor.available() )
300            {
301                return presenceCursor.get();
302            }
303
304            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
305        }
306
307        if ( available() )
308        {
309            if ( prefetched == null )
310            {
311                prefetched = uuidCursor.get();
312            }
313
314            /*
315             * The value of NDN indices is the normalized dn and we want the
316             * value to be the value of the attribute in question.  So we will
317             * set that accordingly here.
318             */
319            prefetched.setKey( presenceEvaluator.getAttributeType().getOid() );
320
321            return prefetched;
322        }
323
324        throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
325    }
326
327
328    /**
329     * {@inheritDoc}
330     */
331    @Override
332    public void close() throws IOException
333    {
334        if ( IS_DEBUG )
335        {
336            LOG_CURSOR.debug( "Closing PresenceCursor {}", this );
337        }
338
339        super.close();
340
341        if ( presenceCursor != null )
342        {
343            presenceCursor.close();
344        }
345        else
346        {
347            uuidCursor.close();
348        }
349    }
350
351
352    /**
353     * {@inheritDoc}
354     */
355    @Override
356    public void close( Exception cause ) throws IOException
357    {
358        if ( IS_DEBUG )
359        {
360            LOG_CURSOR.debug( "Closing PresenceCursor {}", this );
361        }
362
363        super.close( cause );
364
365        if ( presenceCursor != null )
366        {
367            presenceCursor.close( cause );
368        }
369        else
370        {
371            uuidCursor.close( cause );
372        }
373    }
374
375
376    /**
377     * @see Object#toString()
378     */
379    @Override
380    public String toString( String tabs )
381    {
382        StringBuilder sb = new StringBuilder();
383
384        sb.append( tabs ).append( "PresenceCursor (" );
385
386        if ( available() )
387        {
388            sb.append( "available)" );
389        }
390        else
391        {
392            sb.append( "absent)" );
393        }
394
395        sb.append( " :\n" );
396
397        sb.append( tabs + "  >>" ).append( presenceEvaluator ).append( '\n' );
398
399        if ( presenceCursor != null )
400        {
401            sb.append( tabs + "  <presence>\n" );
402            sb.append( presenceCursor.toString( tabs + "    " ) );
403        }
404
405        if ( uuidCursor != null )
406        {
407            sb.append( tabs + "  <uuid>\n" );
408            sb.append( uuidCursor.toString( tabs + "  " ) );
409        }
410
411        return sb.toString();
412    }
413
414
415    /**
416     * @see Object#toString()
417     */
418    public String toString()
419    {
420        return toString( "" );
421    }
422}