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.core.filtering; 021 022 023 import java.util.ArrayList; 024 import java.util.Collections; 025 import java.util.Iterator; 026 import java.util.List; 027 028 import org.apache.directory.server.core.entry.ClonedServerEntry; 029 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext; 030 import org.apache.directory.shared.ldap.cursor.ClosureMonitor; 031 import org.apache.directory.shared.ldap.cursor.Cursor; 032 import org.apache.directory.shared.ldap.cursor.CursorIterator; 033 import org.apache.directory.shared.ldap.cursor.InvalidCursorPositionException; 034 import org.apache.directory.shared.ldap.entry.ServerEntry; 035 import org.apache.directory.shared.ldap.exception.OperationAbandonedException; 036 import org.apache.directory.shared.ldap.schema.AttributeType; 037 import org.apache.directory.shared.ldap.schema.AttributeTypeOptions; 038 import org.apache.directory.shared.ldap.schema.UsageEnum; 039 040 import org.slf4j.Logger; 041 import org.slf4j.LoggerFactory; 042 043 044 /** 045 * A Cursor which uses a list of filters to selectively return entries and/or 046 * modify the contents of entries. Uses lazy pre-fetching on positioning 047 * operations which means adding filters after creation will not miss candidate 048 * entries. 049 * 050 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 051 * @version $Rev$, $Date$ 052 */ 053 public class BaseEntryFilteringCursor implements EntryFilteringCursor 054 { 055 /** the logger used by this class */ 056 private static final Logger log = LoggerFactory.getLogger( BaseEntryFilteringCursor.class ); 057 058 /** the underlying wrapped search results Cursor */ 059 private final Cursor<ServerEntry> wrapped; 060 061 /** the parameters associated with the search operation */ 062 private final SearchingOperationContext operationContext; 063 064 /** the list of filters to be applied */ 065 private final List<EntryFilter> filters; 066 067 /** the first accepted search result that is pre fetched */ 068 private ClonedServerEntry prefetched; 069 070 071 // ------------------------------------------------------------------------ 072 // C O N S T R U C T O R S 073 // ------------------------------------------------------------------------ 074 075 076 /** 077 * Creates a new entry filtering Cursor over an existing Cursor using a 078 * single filter initially: more can be added later after creation. 079 * 080 * @param wrapped the underlying wrapped Cursor whose entries are filtered 081 * @param searchControls the controls of search that created this Cursor 082 * @param invocation the search operation invocation creating this Cursor 083 * @param filter a single filter to be used 084 */ 085 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped, 086 SearchingOperationContext operationContext, EntryFilter filter ) 087 { 088 this( wrapped, operationContext, Collections.singletonList( filter ) ); 089 } 090 091 092 /** 093 * Creates a new entry filtering Cursor over an existing Cursor using a 094 * no filter initially: more can be added later after creation. 095 * 096 * @param wrapped the underlying wrapped Cursor whose entries are filtered 097 * @param searchControls the controls of search that created this Cursor 098 * @param invocation the search operation invocation creating this Cursor 099 * @param filter a single filter to be used 100 */ 101 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped, SearchingOperationContext operationContext ) 102 { 103 this.wrapped = wrapped; 104 this.operationContext = operationContext; 105 this.filters = new ArrayList<EntryFilter>(); 106 } 107 108 109 /** 110 * Creates a new entry filtering Cursor over an existing Cursor using a 111 * list of filters initially: more can be added later after creation. 112 * 113 * @param wrapped the underlying wrapped Cursor whose entries are filtered 114 * @param operationContext the operation context that created this Cursor 115 * @param invocation the search operation invocation creating this Cursor 116 * @param filters a list of filters to be used 117 */ 118 public BaseEntryFilteringCursor( Cursor<ServerEntry> wrapped, 119 SearchingOperationContext operationContext, List<EntryFilter> filters ) 120 { 121 this.wrapped = wrapped; 122 this.operationContext = operationContext; 123 this.filters = new ArrayList<EntryFilter>(); 124 this.filters.addAll( filters ); 125 } 126 127 128 // ------------------------------------------------------------------------ 129 // Class Specific Methods 130 // ------------------------------------------------------------------------ 131 132 133 /* (non-Javadoc) 134 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isAbandoned() 135 */ 136 public boolean isAbandoned() 137 { 138 return getOperationContext().isAbandoned(); 139 } 140 141 142 /* (non-Javadoc) 143 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#setAbandoned(boolean) 144 */ 145 public void setAbandoned( boolean abandoned ) 146 { 147 getOperationContext().setAbandoned( abandoned ); 148 149 if ( abandoned ) 150 { 151 log.info( "Cursor has been abandoned." ); 152 } 153 } 154 155 156 /* (non-Javadoc) 157 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#addEntryFilter(org.apache.directory.server.core.filtering.EntryFilter) 158 */ 159 public boolean addEntryFilter( EntryFilter filter ) 160 { 161 return filters.add( filter ); 162 } 163 164 165 /* (non-Javadoc) 166 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#removeEntryFilter(org.apache.directory.server.core.filtering.EntryFilter) 167 */ 168 public boolean removeEntryFilter( EntryFilter filter ) 169 { 170 return filters.remove( filter ); 171 } 172 173 174 /* (non-Javadoc) 175 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#getEntryFilters() 176 */ 177 public List<EntryFilter> getEntryFilters() 178 { 179 return Collections.unmodifiableList( filters ); 180 } 181 182 183 /* (non-Javadoc) 184 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#getOperationContext() 185 */ 186 public SearchingOperationContext getOperationContext() 187 { 188 return operationContext; 189 } 190 191 192 // ------------------------------------------------------------------------ 193 // Cursor Interface Methods 194 // ------------------------------------------------------------------------ 195 196 197 /* 198 * @see Cursor#after(Object) 199 */ 200 /* (non-Javadoc) 201 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#after(org.apache.directory.server.core.entry.ClonedServerEntry) 202 */ 203 public void after( ClonedServerEntry element ) throws Exception 204 { 205 throw new UnsupportedOperationException(); 206 } 207 208 209 /* 210 * @see Cursor#afterLast() 211 */ 212 /* (non-Javadoc) 213 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#afterLast() 214 */ 215 public void afterLast() throws Exception 216 { 217 wrapped.afterLast(); 218 prefetched = null; 219 } 220 221 222 /* 223 * @see Cursor#available() 224 */ 225 /* (non-Javadoc) 226 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#available() 227 */ 228 public boolean available() 229 { 230 return prefetched != null; 231 } 232 233 234 /* 235 * @see Cursor#before(java.lang.Object) 236 */ 237 /* (non-Javadoc) 238 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#before(org.apache.directory.server.core.entry.ClonedServerEntry) 239 */ 240 public void before( ClonedServerEntry element ) throws Exception 241 { 242 throw new UnsupportedOperationException(); 243 } 244 245 246 /* 247 * @see Cursor#beforeFirst() 248 */ 249 /* (non-Javadoc) 250 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#beforeFirst() 251 */ 252 public void beforeFirst() throws Exception 253 { 254 wrapped.beforeFirst(); 255 prefetched = null; 256 } 257 258 259 /* 260 * @see Cursor#close() 261 */ 262 /* (non-Javadoc) 263 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#close() 264 */ 265 public void close() throws Exception 266 { 267 wrapped.close(); 268 prefetched = null; 269 } 270 271 272 /* 273 * @see Cursor#close() 274 */ 275 /* (non-Javadoc) 276 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#close() 277 */ 278 public void close( Exception reason ) throws Exception 279 { 280 wrapped.close( reason ); 281 prefetched = null; 282 } 283 284 285 public final void setClosureMonitor( ClosureMonitor monitor ) 286 { 287 wrapped.setClosureMonitor( monitor ); 288 } 289 290 291 /* 292 * @see Cursor#first() 293 */ 294 /* (non-Javadoc) 295 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#first() 296 */ 297 public boolean first() throws Exception 298 { 299 if ( getOperationContext().isAbandoned() ) 300 { 301 log.info( "Cursor has been abandoned." ); 302 close(); 303 throw new OperationAbandonedException(); 304 } 305 306 beforeFirst(); 307 return next(); 308 } 309 310 311 /* 312 * @see Cursor#get() 313 */ 314 /* (non-Javadoc) 315 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#get() 316 */ 317 public ClonedServerEntry get() throws Exception 318 { 319 if ( available() ) 320 { 321 return prefetched; 322 } 323 324 throw new InvalidCursorPositionException(); 325 } 326 327 328 /* 329 * @see Cursor#isClosed() 330 */ 331 /* (non-Javadoc) 332 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isClosed() 333 */ 334 public boolean isClosed() throws Exception 335 { 336 return wrapped.isClosed(); 337 } 338 339 340 /* 341 * @see Cursor#isElementReused() 342 */ 343 /* (non-Javadoc) 344 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isElementReused() 345 */ 346 public boolean isElementReused() 347 { 348 return true; 349 } 350 351 352 /* 353 * @see Cursor#last() 354 */ 355 /* (non-Javadoc) 356 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#last() 357 */ 358 public boolean last() throws Exception 359 { 360 if ( getOperationContext().isAbandoned() ) 361 { 362 log.info( "Cursor has been abandoned." ); 363 close(); 364 throw new OperationAbandonedException(); 365 } 366 367 afterLast(); 368 return previous(); 369 } 370 371 372 private void filterContents( ClonedServerEntry entry ) throws Exception 373 { 374 boolean typesOnly = getOperationContext().isTypesOnly(); 375 376 boolean returnAll = ( getOperationContext().getReturningAttributes() == null || 377 ( getOperationContext().isAllOperationalAttributes() && getOperationContext().isAllUserAttributes() ) ) && ( ! typesOnly ); 378 379 if ( returnAll ) 380 { 381 return; 382 } 383 384 if ( getOperationContext().isNoAttributes() ) 385 { 386 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() ) 387 { 388 entry.remove( entry.get( at ) ); 389 } 390 391 return; 392 } 393 394 395 if ( getOperationContext().isAllUserAttributes() ) 396 { 397 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() ) 398 { 399 boolean isNotRequested = true; 400 401 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() ) 402 { 403 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) ) 404 { 405 isNotRequested = false; 406 break; 407 } 408 } 409 410 boolean isNotUserAttribute = at.getUsage() != UsageEnum.USER_APPLICATIONS; 411 412 if ( isNotRequested && isNotUserAttribute ) 413 { 414 entry.removeAttributes( at ); 415 } 416 else if( typesOnly ) 417 { 418 entry.get( at ).clear(); 419 } 420 } 421 422 return; 423 } 424 425 if ( getOperationContext().isAllOperationalAttributes() ) 426 { 427 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() ) 428 { 429 boolean isNotRequested = true; 430 431 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() ) 432 { 433 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) ) 434 { 435 isNotRequested = false; 436 break; 437 } 438 } 439 440 boolean isUserAttribute = at.getUsage() == UsageEnum.USER_APPLICATIONS; 441 442 if ( isNotRequested && isUserAttribute ) 443 { 444 entry.removeAttributes( at ); 445 } 446 else if( typesOnly ) 447 { 448 entry.get( at ).clear(); 449 } 450 } 451 452 return; 453 } 454 455 if ( getOperationContext().getReturningAttributes() != null ) 456 { 457 for ( AttributeType at : entry.getOriginalEntry().getAttributeTypes() ) 458 { 459 boolean isNotRequested = true; 460 461 for ( AttributeTypeOptions attrOptions:getOperationContext().getReturningAttributes() ) 462 { 463 if ( attrOptions.getAttributeType().equals( at ) || attrOptions.getAttributeType().isAncestorOf( at ) ) 464 { 465 isNotRequested = false; 466 break; 467 } 468 } 469 470 if ( isNotRequested ) 471 { 472 entry.removeAttributes( at ); 473 } 474 else if( typesOnly ) 475 { 476 entry.get( at ).clear(); 477 } 478 } 479 } 480 } 481 482 483 /* 484 * @see Cursor#next() 485 */ 486 /* (non-Javadoc) 487 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#next() 488 */ 489 public boolean next() throws Exception 490 { 491 if ( getOperationContext().isAbandoned() ) 492 { 493 log.info( "Cursor has been abandoned." ); 494 close(); 495 throw new OperationAbandonedException(); 496 } 497 498 ClonedServerEntry tempResult = null; 499 outer: while ( wrapped.next() ) 500 { 501 boolean accepted = true; 502 503 ServerEntry tempEntry = wrapped.get(); 504 if ( tempEntry instanceof ClonedServerEntry ) 505 { 506 tempResult = ( ClonedServerEntry ) tempEntry; 507 } 508 else 509 { 510 tempResult = new ClonedServerEntry( tempEntry ); 511 } 512 513 /* 514 * O P T I M I Z A T I O N 515 * ----------------------- 516 * 517 * Don't want to waste cycles on enabling a loop for processing 518 * filters if we have zero or one filter. 519 */ 520 521 if ( filters.isEmpty() ) 522 { 523 prefetched = tempResult; 524 filterContents( prefetched ); 525 return true; 526 } 527 528 if ( filters.size() == 1 ) 529 { 530 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) ) 531 { 532 prefetched = tempResult; 533 filterContents( prefetched ); 534 return true; 535 } 536 } 537 538 /* E N D O P T I M I Z A T I O N */ 539 540 for ( EntryFilter filter : filters ) 541 { 542 // if a filter rejects then short and continue with outer loop 543 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) ) 544 { 545 continue outer; 546 } 547 } 548 549 /* 550 * Here the entry has been accepted by all filters. 551 */ 552 prefetched = tempResult; 553 filterContents( prefetched ); 554 return true; 555 } 556 557 prefetched = null; 558 return false; 559 } 560 561 562 /* 563 * @see Cursor#previous() 564 */ 565 /* (non-Javadoc) 566 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#previous() 567 */ 568 public boolean previous() throws Exception 569 { 570 if ( getOperationContext().isAbandoned() ) 571 { 572 log.info( "Cursor has been abandoned." ); 573 close(); 574 throw new OperationAbandonedException(); 575 } 576 577 ClonedServerEntry tempResult = null; 578 outer: while ( wrapped.previous() ) 579 { 580 boolean accepted = true; 581 tempResult = new ClonedServerEntry( wrapped.get() ); 582 583 /* 584 * O P T I M I Z A T I O N 585 * ----------------------- 586 * 587 * Don't want to waste cycles on enabling a loop for processing 588 * filters if we have zero or one filter. 589 */ 590 591 if ( filters.isEmpty() ) 592 { 593 prefetched = tempResult; 594 filterContents( prefetched ); 595 return true; 596 } 597 598 if ( filters.size() == 1 ) 599 { 600 if ( filters.get( 0 ).accept( getOperationContext(), tempResult ) ) 601 { 602 prefetched = tempResult; 603 filterContents( prefetched ); 604 return true; 605 } 606 } 607 608 /* E N D O P T I M I Z A T I O N */ 609 610 for ( EntryFilter filter : filters ) 611 { 612 // if a filter rejects then short and continue with outer loop 613 if ( ! ( accepted &= filter.accept( getOperationContext(), tempResult ) ) ) 614 { 615 continue outer; 616 } 617 } 618 619 /* 620 * Here the entry has been accepted by all filters. 621 */ 622 prefetched = tempResult; 623 filterContents( prefetched ); 624 return true; 625 } 626 627 prefetched = null; 628 return false; 629 } 630 631 632 /* 633 * @see Iterable#iterator() 634 */ 635 /* (non-Javadoc) 636 * @see org.apache.directory.server.core.filtering.EntryFilteringCursor#iterator() 637 */ 638 public Iterator<ClonedServerEntry> iterator() 639 { 640 return new CursorIterator<ClonedServerEntry>( this ); 641 } 642 }