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 }