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     */
020    package org.apache.directory.server.xdbm.search.impl;
021    
022    
023    import org.apache.directory.server.i18n.I18n;
024    import org.apache.directory.server.xdbm.AbstractIndexCursor;
025    import org.apache.directory.server.xdbm.ForwardIndexEntry;
026    import org.apache.directory.server.xdbm.Index;
027    import org.apache.directory.server.xdbm.IndexCursor;
028    import org.apache.directory.server.xdbm.IndexEntry;
029    import org.apache.directory.server.xdbm.Store;
030    import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
031    import org.apache.directory.shared.ldap.entry.ServerEntry;
032    
033    
034    /**
035     * A Cursor traversing candidates matching a Substring assertion expression.
036     *
037     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
038     * @version $Rev$
039     */
040    public class SubstringCursor<ID> extends AbstractIndexCursor<String, ServerEntry, ID>
041    {
042        private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_725 );
043        private final boolean hasIndex;
044        private final IndexCursor<String, ServerEntry, ID> wrapped;
045        private final SubstringEvaluator<ID> evaluator;
046        private final ForwardIndexEntry<String, ServerEntry, ID> indexEntry = new ForwardIndexEntry<String, ServerEntry, ID>();
047        private boolean available = false;
048    
049    
050        @SuppressWarnings("unchecked")
051        public SubstringCursor( Store<ServerEntry, ID> db, final SubstringEvaluator<ID> substringEvaluator )
052            throws Exception
053        {
054            evaluator = substringEvaluator;
055            hasIndex = db.hasIndexOn( evaluator.getExpression().getAttribute() );
056    
057            if ( hasIndex )
058            {
059                wrapped = ( ( Index<String, ServerEntry, ID> ) db.getIndex( evaluator.getExpression().getAttribute() ) )
060                    .forwardCursor();
061            }
062            else
063            {
064                /*
065                 * There is no index on the attribute here.  We have no choice but
066                 * to perform a full table scan but need to leverage an index for the
067                 * wrapped Cursor.  We know that all entries are listed under
068                 * the ndn index and so this will enumerate over all entries.  The
069                 * substringEvaluator is used in an assertion to constrain the
070                 * result set to only those entries matching the pattern.  The
071                 * substringEvaluator handles all the details of normalization and
072                 * knows to use it, when it itself detects the lack of an index on
073                 * the node's attribute.
074                 */
075                wrapped = db.getNdnIndex().forwardCursor();
076            }
077        }
078    
079    
080        public boolean available()
081        {
082            return available;
083        }
084    
085    
086        public void beforeValue( ID id, String value ) throws Exception
087        {
088            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
089        }
090    
091    
092        public void afterValue( ID id, String value ) throws Exception
093        {
094            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
095        }
096    
097    
098        public void before( IndexEntry<String, ServerEntry, ID> element ) throws Exception
099        {
100            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
101        }
102    
103    
104        public void after( IndexEntry<String, ServerEntry, ID> element ) throws Exception
105        {
106            throw new UnsupportedOperationException( UNSUPPORTED_MSG );
107        }
108    
109    
110        public void beforeFirst() throws Exception
111        {
112            checkNotClosed( "beforeFirst()" );
113            if ( evaluator.getExpression().getInitial() != null && hasIndex )
114            {
115                ForwardIndexEntry<String, ServerEntry, ID> indexEntry = new ForwardIndexEntry<String, ServerEntry, ID>();
116                indexEntry.setValue( evaluator.getExpression().getInitial() );
117                wrapped.before( indexEntry );
118            }
119            else
120            {
121                wrapped.beforeFirst();
122            }
123    
124            clear();
125        }
126    
127    
128        private void clear()
129        {
130            available = false;
131            indexEntry.setObject( null );
132            indexEntry.setId( null );
133            indexEntry.setValue( null );
134        }
135    
136    
137        public void afterLast() throws Exception
138        {
139            checkNotClosed( "afterLast()" );
140    
141            // to keep the cursor always *after* the last matched tuple
142            // This fixes an issue if the last matched tuple is also the last record present in the 
143            // index. In this case the wrapped cursor is positioning on the last tuple instead of positioning after that
144            wrapped.afterLast();
145            clear();
146        }
147    
148    
149        public boolean first() throws Exception
150        {
151            beforeFirst();
152            return next();
153        }
154    
155    
156        private boolean evaluateCandidate( IndexEntry<String, ServerEntry, ID> indexEntry ) throws Exception
157        {
158            if ( hasIndex )
159            {
160                return evaluator.getPattern().matcher( indexEntry.getValue() ).matches();
161            }
162            else
163            {
164                return evaluator.evaluate( indexEntry );
165            }
166        }
167    
168    
169        public boolean last() throws Exception
170        {
171            afterLast();
172            return previous();
173        }
174    
175    
176        public boolean previous() throws Exception
177        {
178            while ( wrapped.previous() )
179            {
180                checkNotClosed( "previous()" );
181                IndexEntry<String, ServerEntry, ID> entry = wrapped.get();
182                if ( evaluateCandidate( entry ) )
183                {
184                    available = true;
185                    this.indexEntry.setId( entry.getId() );
186                    this.indexEntry.setValue( entry.getValue() );
187                    this.indexEntry.setObject( entry.getObject() );
188                    return true;
189                }
190            }
191    
192            clear();
193            return false;
194        }
195    
196    
197        public boolean next() throws Exception
198        {
199            while ( wrapped.next() )
200            {
201                checkNotClosed( "next()" );
202                IndexEntry<String, ServerEntry, ID> entry = wrapped.get();
203                if ( evaluateCandidate( entry ) )
204                {
205                    available = true;
206                    this.indexEntry.setId( entry.getId() );
207                    this.indexEntry.setValue( entry.getValue() );
208                    this.indexEntry.setObject( entry.getObject() );
209                    return true;
210                }
211            }
212    
213            clear();
214            return false;
215        }
216    
217    
218        public IndexEntry<String, ServerEntry, ID> get() throws Exception
219        {
220            checkNotClosed( "get()" );
221            if ( available )
222            {
223                return indexEntry;
224            }
225    
226            throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
227        }
228    
229    
230        public boolean isElementReused()
231        {
232            return wrapped.isElementReused();
233        }
234    
235    
236        public void close() throws Exception
237        {
238            super.close();
239            wrapped.close();
240            clear();
241        }
242    }