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