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 }