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.PrepareString;
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.Index;
035import org.apache.directory.server.xdbm.IndexEntry;
036import org.apache.directory.server.xdbm.IndexNotFoundException;
037import org.apache.directory.server.xdbm.Store;
038import org.apache.directory.server.xdbm.search.evaluator.SubstringEvaluator;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * A Cursor traversing candidates matching a Substring assertion expression.
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class SubstringCursor extends AbstractIndexCursor<String>
049{
050    /** A dedicated log for cursors */
051    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
052
053    /** Speedup for logs */
054    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
055
056    private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_725 );
057    private final boolean hasIndex;
058    private final Cursor<IndexEntry<String, String>> wrapped;
059    private final SubstringEvaluator evaluator;
060    private final IndexEntry<String, String> indexEntry = new IndexEntry<>();
061
062
063    /**
064     * Creates a new instance of an SubstringCursor
065     * 
066     * @param partitionTxn The transaction to use
067     * @param store The store
068     * @param substringEvaluator The SubstringEvaluator
069     * @throws LdapException If the creation failed
070     * @throws IndexNotFoundException If the index was not found
071     */
072    @SuppressWarnings("unchecked")
073    public SubstringCursor( PartitionTxn partitionTxn, Store store, final SubstringEvaluator substringEvaluator )
074        throws LdapException, IndexNotFoundException
075    {
076        if ( IS_DEBUG )
077        {
078            LOG_CURSOR.debug( "Creating SubstringCursor {}", this );
079        }
080
081        evaluator = substringEvaluator;
082        this.partitionTxn = partitionTxn;
083        hasIndex = store.hasIndexOn( evaluator.getExpression().getAttributeType() );
084
085        if ( hasIndex )
086        {
087            wrapped = ( ( Index<String, String> ) store.getIndex( evaluator.getExpression().getAttributeType() ) )
088                .forwardCursor( partitionTxn );
089        }
090        else
091        {
092            /*
093             * There is no index on the attribute here.  We have no choice but
094             * to perform a full table scan but need to leverage an index for the
095             * wrapped Cursor.  We know that all entries are listed under
096             * the ndn index and so this will enumerate over all entries.  The
097             * substringEvaluator is used in an assertion to constrain the
098             * result set to only those entries matching the pattern.  The
099             * substringEvaluator handles all the details of normalization and
100             * knows to use it, when it itself detects the lack of an index on
101             * the node's attribute.
102             */
103            wrapped = new AllEntriesCursor( partitionTxn, store );
104        }
105    }
106
107
108    /**
109     * {@inheritDoc}
110     */
111    protected String getUnsupportedMessage()
112    {
113        return UNSUPPORTED_MSG;
114    }
115
116
117    /**
118     * {@inheritDoc}
119     */
120    public void beforeFirst() throws LdapException, CursorException
121    {
122        checkNotClosed();
123        
124        if ( evaluator.getExpression().getInitial() != null && hasIndex )
125        {
126            IndexEntry<String, String> beforeFirstIndexEntry = new IndexEntry<>();
127            String normalizedKey = evaluator.getExpression().getAttributeType().getEquality().getNormalizer().normalize( 
128                evaluator.getExpression().getInitial(), PrepareString.AssertionType.SUBSTRING_INITIAL );
129            beforeFirstIndexEntry.setKey( normalizedKey );
130            wrapped.before( beforeFirstIndexEntry );
131        }
132        else
133        {
134            wrapped.beforeFirst();
135        }
136
137        clear();
138    }
139
140
141    private void clear()
142    {
143        setAvailable( false );
144        indexEntry.setEntry( null );
145        indexEntry.setId( null );
146        indexEntry.setKey( null );
147    }
148
149
150    /**
151     * {@inheritDoc}
152     */
153    public void afterLast() throws LdapException, CursorException
154    {
155        checkNotClosed();
156
157        // to keep the cursor always *after* the last matched tuple
158        // This fixes an issue if the last matched tuple is also the last record present in the
159        // index. In this case the wrapped cursor is positioning on the last tuple instead of positioning after that
160        wrapped.afterLast();
161        clear();
162    }
163
164
165    /**
166     * {@inheritDoc}
167     */
168    public boolean first() throws LdapException, CursorException
169    {
170        beforeFirst();
171        return next();
172    }
173
174
175    private boolean evaluateCandidate( PartitionTxn partitionTxn, IndexEntry<String, String> indexEntry ) throws LdapException
176    {
177        if ( hasIndex )
178        {
179            String key = indexEntry.getKey();
180            return evaluator.getPattern().matcher( key ).matches();
181        }
182        else
183        {
184            return evaluator.evaluate( partitionTxn, indexEntry );
185        }
186    }
187
188
189    /**
190     * {@inheritDoc}
191     */
192    public boolean last() throws LdapException, CursorException
193    {
194        afterLast();
195
196        return previous();
197    }
198
199
200    /**
201     * {@inheritDoc}
202     */
203    @Override
204    public boolean previous() throws LdapException, CursorException
205    {
206        while ( wrapped.previous() )
207        {
208            checkNotClosed();
209            IndexEntry<String, String> entry = wrapped.get();
210
211            if ( evaluateCandidate( partitionTxn, entry ) )
212            {
213                setAvailable( true );
214                this.indexEntry.setId( entry.getId() );
215                this.indexEntry.setKey( entry.getKey() );
216                this.indexEntry.setEntry( entry.getEntry() );
217                return true;
218            }
219        }
220
221        clear();
222        return false;
223    }
224
225
226    /**
227     * {@inheritDoc}
228     */
229    @Override
230    public boolean next() throws LdapException, CursorException
231    {
232        while ( wrapped.next() )
233        {
234            checkNotClosed();
235            IndexEntry<String, String> entry = wrapped.get();
236
237            if ( evaluateCandidate( partitionTxn, entry ) )
238            {
239                setAvailable( true );
240                this.indexEntry.setId( entry.getId() );
241                this.indexEntry.setKey( entry.getKey() );
242                this.indexEntry.setEntry( entry.getEntry() );
243
244                return true;
245            }
246        }
247
248        clear();
249        return false;
250    }
251
252
253    /**
254     * {@inheritDoc}
255     */
256    public IndexEntry<String, String> get() throws CursorException
257    {
258        checkNotClosed();
259
260        if ( available() )
261        {
262            return indexEntry;
263        }
264
265        throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
266    }
267
268
269    /**
270     * {@inheritDoc}
271     */
272    @Override
273    public void close() throws IOException
274    {
275        if ( IS_DEBUG )
276        {
277            LOG_CURSOR.debug( "Closing SubstringCursor {}", this );
278        }
279
280        super.close();
281        wrapped.close();
282        clear();
283    }
284
285
286    /**
287     * {@inheritDoc}
288     */
289    @Override
290    public void close( Exception cause ) throws IOException
291    {
292        if ( IS_DEBUG )
293        {
294            LOG_CURSOR.debug( "Closing SubstringCursor {}", this );
295        }
296
297        super.close( cause );
298        wrapped.close( cause );
299        clear();
300    }
301
302
303    /**
304     * @see Object#toString()
305     */
306    @Override
307    public String toString( String tabs )
308    {
309        StringBuilder sb = new StringBuilder();
310
311        sb.append( tabs ).append( "SubstringCursor (" );
312
313        if ( available() )
314        {
315            sb.append( "available)" );
316        }
317        else
318        {
319            sb.append( "absent)" );
320        }
321
322        sb.append( "#index<" ).append( hasIndex ).append( "> :\n" );
323
324        sb.append( tabs + "  >>" ).append( evaluator ).append( '\n' );
325
326        sb.append( wrapped.toString( tabs + "    " ) );
327
328        return sb.toString();
329    }
330
331
332    /**
333     * @see Object#toString()
334     */
335    public String toString()
336    {
337        return toString( "" );
338    }
339}