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    public boolean previous() throws LdapException, CursorException
236    {
237        checkNotClosed();
238
239        if ( presenceCursor != null )
240        {
241            return presenceCursor.previous();
242        }
243
244        while ( uuidCursor.previous() )
245        {
246            checkNotClosed();
247            IndexEntry<?, String> candidate = uuidCursor.get();
248
249            if ( presenceEvaluator.evaluate( partitionTxn, candidate ) )
250            {
251                return setAvailable( true );
252            }
253        }
254
255        return setAvailable( false );
256    }
257
258
259    /**
260     * {@inheritDoc}
261     */
262    public boolean next() throws LdapException, CursorException
263    {
264        checkNotClosed();
265
266        if ( presenceCursor != null )
267        {
268            return presenceCursor.next();
269        }
270
271        while ( uuidCursor.next() )
272        {
273            checkNotClosed();
274            IndexEntry<String, String> candidate = uuidCursor.get();
275
276            if ( presenceEvaluator.evaluate( partitionTxn, candidate ) )
277            {
278                prefetched = candidate;
279
280                return setAvailable( true );
281            }
282        }
283
284        return setAvailable( false );
285    }
286
287
288    /**
289     * {@inheritDoc}
290     */
291    public IndexEntry<String, String> get() throws CursorException
292    {
293        checkNotClosed();
294
295        if ( presenceCursor != null )
296        {
297            if ( presenceCursor.available() )
298            {
299                return presenceCursor.get();
300            }
301
302            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
303        }
304
305        if ( available() )
306        {
307            if ( prefetched == null )
308            {
309                prefetched = uuidCursor.get();
310            }
311
312            /*
313             * The value of NDN indices is the normalized dn and we want the
314             * value to be the value of the attribute in question.  So we will
315             * set that accordingly here.
316             */
317            prefetched.setKey( presenceEvaluator.getAttributeType().getOid() );
318
319            return prefetched;
320        }
321
322        throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
323    }
324
325
326    /**
327     * {@inheritDoc}
328     */
329    @Override
330    public void close() throws IOException
331    {
332        if ( IS_DEBUG )
333        {
334            LOG_CURSOR.debug( "Closing PresenceCursor {}", this );
335        }
336
337        super.close();
338
339        if ( presenceCursor != null )
340        {
341            presenceCursor.close();
342        }
343        else
344        {
345            uuidCursor.close();
346        }
347    }
348
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    public void close( Exception cause ) throws IOException
355    {
356        if ( IS_DEBUG )
357        {
358            LOG_CURSOR.debug( "Closing PresenceCursor {}", this );
359        }
360
361        super.close( cause );
362
363        if ( presenceCursor != null )
364        {
365            presenceCursor.close( cause );
366        }
367        else
368        {
369            uuidCursor.close( cause );
370        }
371    }
372
373
374    /**
375     * @see Object#toString()
376     */
377    @Override
378    public String toString( String tabs )
379    {
380        StringBuilder sb = new StringBuilder();
381
382        sb.append( tabs ).append( "PresenceCursor (" );
383
384        if ( available() )
385        {
386            sb.append( "available)" );
387        }
388        else
389        {
390            sb.append( "absent)" );
391        }
392
393        sb.append( " :\n" );
394
395        sb.append( tabs + "  >>" ).append( presenceEvaluator ).append( '\n' );
396
397        if ( presenceCursor != null )
398        {
399            sb.append( tabs + "  <presence>\n" );
400            sb.append( presenceCursor.toString( tabs + "    " ) );
401        }
402
403        if ( uuidCursor != null )
404        {
405            sb.append( tabs + "  <uuid>\n" );
406            sb.append( uuidCursor.toString( tabs + "  " ) );
407        }
408
409        return sb.toString();
410    }
411
412
413    /**
414     * @see Object#toString()
415     */
416    public String toString()
417    {
418        return toString( "" );
419    }
420}