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.IndexEntry;
025 import org.apache.directory.server.xdbm.Store;
026 import org.apache.directory.server.xdbm.AbstractIndexCursor;
027 import org.apache.directory.server.xdbm.IndexCursor;
028 import org.apache.directory.shared.ldap.cursor.Cursor;
029 import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException;
030 import org.apache.directory.shared.ldap.entry.ServerEntry;
031
032
033 /**
034 * A Cursor over entries satisfying one level scope constraints with alias
035 * dereferencing considerations when enabled during search.
036 *
037 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
038 * @version $Rev$
039 */
040 public class OneLevelScopeCursor<ID> extends AbstractIndexCursor<ID, ServerEntry, ID>
041 {
042 /** Error message for unsupported operations */
043 private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_719 );
044
045 /** The entry database/store */
046 private final Store<ServerEntry, ID> db;
047
048 /** A onelevel ScopeNode Evaluator */
049 @SuppressWarnings("unchecked")
050 private final OneLevelScopeEvaluator evaluator;
051
052 /** A Cursor over the entries in the scope of the search base */
053 private final IndexCursor<ID, ServerEntry, ID> scopeCursor;
054
055 /** A Cursor over entries brought into scope by alias dereferencing */
056 private final Cursor<IndexEntry<ID, ServerEntry, ID>> dereferencedCursor;
057
058 /** Currently active Cursor: we switch between two cursors */
059 private Cursor<IndexEntry<ID, ServerEntry, ID>> cursor;
060
061 /** Whether or not this Cursor is positioned so an entry is available */
062 private boolean available = false;
063
064
065 /**
066 * Creates a Cursor over entries satisfying one level scope criteria.
067 *
068 * @param db the entry store
069 * @param evaluator an IndexEntry (candidate) evaluator
070 * @throws Exception on db access failures
071 */
072 //@SuppressWarnings("unchecked")
073 public OneLevelScopeCursor( Store<ServerEntry, ID> db, OneLevelScopeEvaluator<ServerEntry, ID> evaluator )
074 throws Exception
075 {
076 this.db = db;
077 this.evaluator = evaluator;
078 scopeCursor = db.getOneLevelIndex().forwardCursor( evaluator.getBaseId() );
079
080 if ( evaluator.isDereferencing() )
081 {
082 dereferencedCursor = db.getOneAliasIndex().forwardCursor( evaluator.getBaseId() );
083 }
084 else
085 {
086 dereferencedCursor = null;
087 }
088 }
089
090
091 public boolean available()
092 {
093 return available;
094 }
095
096
097 public void beforeValue( ID id, ID value ) throws Exception
098 {
099 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
100 }
101
102
103 public void afterValue( ID id, ID value ) throws Exception
104 {
105 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
106 }
107
108
109 public void before( IndexEntry<ID, ServerEntry, ID> element ) throws Exception
110 {
111 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
112 }
113
114
115 public void after( IndexEntry<ID, ServerEntry, ID> element ) throws Exception
116 {
117 throw new UnsupportedOperationException( UNSUPPORTED_MSG );
118 }
119
120
121 public void beforeFirst() throws Exception
122 {
123 checkNotClosed( "beforeFirst()" );
124 cursor = scopeCursor;
125 cursor.beforeFirst();
126 available = false;
127 }
128
129
130 public void afterLast() throws Exception
131 {
132 checkNotClosed( "afterLast()" );
133 if ( evaluator.isDereferencing() )
134 {
135 cursor = dereferencedCursor;
136 }
137 else
138 {
139 cursor = scopeCursor;
140 }
141
142 cursor.afterLast();
143 available = false;
144 }
145
146
147 public boolean first() throws Exception
148 {
149 beforeFirst();
150 return next();
151 }
152
153
154 public boolean last() throws Exception
155 {
156 afterLast();
157 return previous();
158 }
159
160
161 public boolean previous() throws Exception
162 {
163 checkNotClosed( "previous()" );
164 // if the cursor has not been set - position it after last element
165 if ( cursor == null )
166 {
167 afterLast();
168 }
169
170 // if we're using the scopeCursor (1st Cursor) then return result as is
171 if ( cursor == scopeCursor )
172 {
173 /*
174 * If dereferencing is enabled then we must ignore alias entries, not
175 * returning them as part of the results.
176 */
177 if ( evaluator.isDereferencing() )
178 {
179 // advance until nothing is available or until we find a non-alias
180 do
181 {
182 checkNotClosed( "previous()" );
183 available = cursor.previous();
184
185 if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
186 {
187 break;
188 }
189 }
190 while ( available );
191 }
192 else
193 {
194 available = cursor.previous();
195 }
196
197 return available;
198 }
199
200 /*
201 * Below here we are using the dereferencedCursor so if nothing is
202 * available after an advance backwards we need to switch to the
203 * scopeCursor and try a previous call after positioning past it's
204 * last element.
205 */
206 available = cursor.previous();
207 if ( !available )
208 {
209 cursor = scopeCursor;
210 cursor.afterLast();
211
212 // advance until nothing is available or until we find a non-alias
213 do
214 {
215 checkNotClosed( "previous()" );
216 available = cursor.previous();
217
218 if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
219 {
220 break;
221 }
222 }
223 while ( available );
224
225 return available;
226 }
227
228 return true;
229 }
230
231
232 public boolean next() throws Exception
233 {
234 checkNotClosed( "next()" );
235 // if the cursor hasn't been set position it before the first element
236 if ( cursor == null )
237 {
238 beforeFirst();
239 }
240
241 /*
242 * If dereferencing is enabled then we must ignore alias entries, not
243 * returning them as part of the results.
244 */
245 if ( evaluator.isDereferencing() )
246 {
247 // advance until nothing is available or until we find a non-alias
248 do
249 {
250 checkNotClosed( "next()" );
251 available = cursor.next();
252
253 if ( available && db.getAliasIndex().reverseLookup( cursor.get().getId() ) == null )
254 {
255 break;
256 }
257 }
258 while ( available );
259 }
260 else
261 {
262 available = cursor.next();
263 }
264
265 // if we're using dereferencedCursor (2nd) then we return the result
266 if ( cursor == dereferencedCursor )
267 {
268 return available;
269 }
270
271 /*
272 * Below here we are using the scopeCursor so if nothing is
273 * available after an advance forward we need to switch to the
274 * dereferencedCursor and try a previous call after positioning past
275 * it's last element.
276 */
277 if ( !available )
278 {
279 if ( dereferencedCursor != null )
280 {
281 cursor = dereferencedCursor;
282 cursor.beforeFirst();
283 return available = cursor.next();
284 }
285
286 return false;
287 }
288
289 return true;
290 }
291
292
293 public IndexEntry<ID, ServerEntry, ID> get() throws Exception
294 {
295 checkNotClosed( "get()" );
296 if ( available )
297 {
298 return cursor.get();
299 }
300
301 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) );
302 }
303
304
305 public boolean isElementReused()
306 {
307 return scopeCursor.isElementReused() || ( dereferencedCursor != null && dereferencedCursor.isElementReused() );
308 }
309 }