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 */ 019package org.apache.directory.server.core.api.filtering; 020 021 022import java.util.Collections; 023import java.util.Iterator; 024import java.util.List; 025 026import org.apache.directory.api.i18n.I18n; 027import org.apache.directory.api.ldap.model.constants.Loggers; 028import org.apache.directory.api.ldap.model.cursor.AbstractCursor; 029import org.apache.directory.api.ldap.model.cursor.ClosureMonitor; 030import org.apache.directory.api.ldap.model.cursor.Cursor; 031import org.apache.directory.api.ldap.model.cursor.CursorException; 032import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException; 033import org.apache.directory.api.ldap.model.entry.Entry; 034import org.apache.directory.api.ldap.model.exception.LdapException; 035import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039 040/** 041 * An implementation of a Cursor based on a {@link List} of {@link Cursor}s. Optionally, the 042 * Cursor may be limited to a specific range within the list. 043 * 044 * This class is modeled based on the implementation of {@link org.apache.directory.api.ldap.model.cursor.ListCursor} 045 * 046 * WARN this is only used internally ! 047 * 048 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 049 */ 050public class CursorList extends AbstractCursor<Entry> implements EntryFilteringCursor 051{ 052 /** A dedicated log for cursors */ 053 private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() ); 054 055 /** Speedup for logs */ 056 private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled(); 057 058 /** The inner List */ 059 private final List<EntryFilteringCursor> list; 060 061 /** The starting position for the cursor in the list. It can be > 0 */ 062 private final int start; 063 064 /** The ending position for the cursor in the list. It can be < List.size() */ 065 private final int end; 066 067 /** The number of cursors in the list */ 068 private final int listSize; 069 070 /** The current position in the list */ 071 private int index; 072 073 /** The current cursor being used */ 074 private EntryFilteringCursor currentCursor; 075 076 /** the operation context */ 077 private SearchOperationContext searchContext; 078 079 /** flag to detect the closed cursor */ 080 private boolean closed; 081 082 /** The logger for this class */ 083 private static final Logger LOG = LoggerFactory.getLogger( CursorList.class ); 084 085 086 /** 087 * Creates a new ListCursor with lower (inclusive) and upper (exclusive) 088 * bounds. 089 * 090 * As with all Cursors, this ListCursor requires a successful return from 091 * advance operations (next() or previous()) to properly return values 092 * using the get() operation. 093 * 094 * @param start the lower bound index 095 * @param list the list this ListCursor operates on 096 * @param end the upper bound index 097 * @param searchContext The SearchContext instance 098 */ 099 public CursorList( int start, List<EntryFilteringCursor> list, int end, SearchOperationContext searchContext ) 100 { 101 if ( IS_DEBUG ) 102 { 103 LOG_CURSOR.debug( "Creating CursorList {}", this ); 104 } 105 106 if ( list != null ) 107 { 108 this.list = list; 109 } 110 else 111 { 112 this.list = Collections.emptyList(); 113 } 114 115 listSize = this.list.size(); 116 117 if ( ( start < 0 ) || ( start > listSize ) ) 118 { 119 throw new IllegalArgumentException( I18n.err( I18n.ERR_13105_START_INDEX_OUT_OF_RANGE, start ) ); 120 } 121 122 if ( ( end < 0 ) || ( end > listSize ) ) 123 { 124 throw new IllegalArgumentException( I18n.err( I18n.ERR_13106_END_INDEX_OUT_OF_RANGE, end ) ); 125 } 126 127 // check list is not empty list since the empty list is the only situation 128 // where we allow for start to equal the end: in other cases it makes no sense 129 if ( ( listSize > 0 ) && ( start >= end ) ) 130 { 131 throw new IllegalArgumentException( I18n.err( I18n.ERR_13107_START_INDEX_ABOVE_END_INDEX, start, end ) ); 132 } 133 134 this.start = start; 135 this.end = end; 136 this.searchContext = searchContext; 137 index = start; 138 currentCursor = this.list.get( index ); 139 } 140 141 142 /** 143 * Creates a new ListCursor without specific bounds: the bounds are 144 * acquired from the size of the list. 145 * 146 * @param list the backing for this ListCursor 147 * @param searchContext The SearchContext instance 148 */ 149 public CursorList( List<EntryFilteringCursor> list, SearchOperationContext searchContext ) 150 { 151 this( 0, list, list.size(), searchContext ); 152 } 153 154 155 /** 156 * {@inheritDoc} 157 */ 158 public boolean available() 159 { 160 if ( ( index >= 0 ) && ( index < end ) ) 161 { 162 return list.get( index ).available(); 163 } 164 165 return false; 166 } 167 168 169 /** 170 * {@inheritDoc} 171 */ 172 public void before( Entry element ) throws LdapException, CursorException 173 { 174 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13108_LIST_MAY_BE_SORTED ) ); 175 } 176 177 178 /** 179 * {@inheritDoc} 180 */ 181 public void after( Entry element ) throws LdapException, CursorException 182 { 183 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13108_LIST_MAY_BE_SORTED ) ); 184 } 185 186 187 /** 188 * {@inheritDoc} 189 */ 190 public void beforeFirst() throws LdapException, CursorException 191 { 192 index = 0; 193 currentCursor = list.get( index ); 194 currentCursor.beforeFirst(); 195 } 196 197 198 /** 199 * {@inheritDoc} 200 */ 201 public void afterLast() throws LdapException, CursorException 202 { 203 index = end - 1; 204 currentCursor = list.get( index ); 205 currentCursor.afterLast(); 206 } 207 208 209 /** 210 * {@inheritDoc} 211 */ 212 public boolean first() throws LdapException, CursorException 213 { 214 if ( listSize > 0 ) 215 { 216 index = start; 217 218 return list.get( index ).first(); 219 } 220 221 return false; 222 } 223 224 225 /** 226 * {@inheritDoc} 227 */ 228 public boolean last() throws LdapException, CursorException 229 { 230 if ( listSize > 0 ) 231 { 232 index = end - 1; 233 currentCursor = list.get( index ); 234 235 return currentCursor.last(); 236 } 237 238 return false; 239 } 240 241 242 /** 243 * {@inheritDoc} 244 */ 245 public boolean isFirst() 246 { 247 return ( listSize > 0 ) && ( index == start ) && list.get( index ).isFirst(); 248 } 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 public boolean isLast() 255 { 256 return ( listSize > 0 ) && ( index == end - 1 ) && list.get( index ).isLast(); 257 } 258 259 260 /** 261 * {@inheritDoc} 262 */ 263 public boolean isAfterLast() 264 { 265 return ( index == end ); 266 } 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 public boolean isBeforeFirst() 273 { 274 return index == -1; 275 } 276 277 278 /** 279 * {@inheritDoc} 280 */ 281 public boolean previous() throws LdapException, CursorException 282 { 283 while ( index > -1 ) 284 { 285 currentCursor = list.get( index ); 286 287 if ( currentCursor.previous() ) 288 { 289 return true; 290 } 291 else 292 { 293 index--; 294 } 295 } 296 297 return false; 298 } 299 300 301 /** 302 * {@inheritDoc} 303 */ 304 public boolean next() throws LdapException, CursorException 305 { 306 if ( listSize > 0 ) 307 { 308 if ( index == -1 ) 309 { 310 index = start; 311 } 312 313 while ( index < end ) 314 { 315 currentCursor = list.get( index ); 316 317 if ( currentCursor.next() ) 318 { 319 return true; 320 } 321 else 322 { 323 index++; 324 } 325 } 326 } 327 328 return false; 329 } 330 331 332 /** 333 * {@inheritDoc} 334 */ 335 public Entry get() throws CursorException 336 { 337 if ( ( index < start ) || ( index >= end ) ) 338 { 339 throw new CursorException( I18n.err( I18n.ERR_13109_CURSOR_NOT_POSITIONED ) ); 340 } 341 342 if ( currentCursor.available() ) 343 { 344 return currentCursor.get(); 345 } 346 347 throw new InvalidCursorPositionException(); 348 } 349 350 351 /** 352 * {@inheritDoc} 353 */ 354 public boolean addEntryFilter( EntryFilter filter ) 355 { 356 for ( EntryFilteringCursor efc : list ) 357 { 358 efc.addEntryFilter( filter ); 359 } 360 361 // returning hard coded value, shouldn't be a problem 362 return true; 363 } 364 365 366 /** 367 * {@inheritDoc} 368 */ 369 public List<EntryFilter> getEntryFilters() 370 { 371 throw new UnsupportedOperationException( "CursorList doesn't support this operation" ); 372 } 373 374 375 /** 376 * {@inheritDoc} 377 */ 378 public SearchOperationContext getOperationContext() 379 { 380 return searchContext; 381 } 382 383 384 public boolean isAbandoned() 385 { 386 return searchContext.isAbandoned(); 387 } 388 389 390 public void setAbandoned( boolean abandoned ) 391 { 392 searchContext.setAbandoned( abandoned ); 393 394 if ( abandoned ) 395 { 396 LOG.info( "Cursor has been abandoned." ); 397 } 398 } 399 400 401 /** 402 * {@inheritDoc} 403 */ 404 public void close() 405 { 406 if ( IS_DEBUG ) 407 { 408 LOG_CURSOR.debug( "Closing CursorList {}", this ); 409 } 410 411 close( null ); 412 } 413 414 415 /** 416 * {@inheritDoc} 417 */ 418 public void close( Exception reason ) 419 { 420 if ( IS_DEBUG ) 421 { 422 LOG_CURSOR.debug( "Closing CursorList {}", this ); 423 } 424 425 closed = true; 426 427 for ( EntryFilteringCursor cursor : list ) 428 { 429 try 430 { 431 if ( reason != null ) 432 { 433 cursor.close( reason ); 434 } 435 else 436 { 437 cursor.close(); 438 } 439 } 440 catch ( Exception e ) 441 { 442 LOG.warn( "Failed to close the cursor" ); 443 } 444 } 445 } 446 447 448 /** 449 * {@inheritDoc} 450 */ 451 public boolean isClosed() 452 { 453 return closed; 454 } 455 456 457 public Iterator<Entry> iterator() 458 { 459 throw new UnsupportedOperationException(); 460 } 461 462 463 /** 464 * {@inheritDoc} 465 */ 466 public void setClosureMonitor( ClosureMonitor monitor ) 467 { 468 for ( EntryFilteringCursor c : list ) 469 { 470 c.setClosureMonitor( monitor ); 471 } 472 } 473}