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 */ 020package org.apache.directory.server.xdbm.search.cursor; 021 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Set; 028 029import org.apache.directory.api.ldap.model.constants.Loggers; 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.exception.LdapException; 034import org.apache.directory.api.ldap.model.filter.ExprNode; 035import org.apache.directory.server.core.api.partition.PartitionTxn; 036import org.apache.directory.server.i18n.I18n; 037import org.apache.directory.server.xdbm.AbstractIndexCursor; 038import org.apache.directory.server.xdbm.IndexEntry; 039import org.apache.directory.server.xdbm.search.Evaluator; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043 044/** 045 * A Cursor returning candidates satisfying a logical disjunction expression. 046 * 047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 048 */ 049public class OrCursor<V> extends AbstractIndexCursor<V> 050{ 051 /** A dedicated log for cursors */ 052 private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() ); 053 054 /** Speedup for logs */ 055 private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled(); 056 057 private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_722 ); 058 private final List<Cursor<IndexEntry<V, String>>> cursors; 059 private final List<Evaluator<? extends ExprNode>> evaluators; 060 private final List<Set<String>> blacklists; 061 private int cursorIndex = -1; 062 063 /** The candidate we have fetched in the next/previous call */ 064 private IndexEntry<V, String> prefetched; 065 066 067 /** 068 * Creates a new instance of an OrCursor 069 * 070 * @param partitionTxn The transaction to use 071 * @param cursors The encapsulated Cursors 072 * @param evaluators The list of evaluators associated with the elements 073 */ 074 // TODO - do same evaluator fail fast optimization that we do in AndCursor 075 public OrCursor( PartitionTxn partitionTxn, List<Cursor<IndexEntry<V, String>>> cursors, 076 List<Evaluator<? extends ExprNode>> evaluators ) 077 { 078 if ( IS_DEBUG ) 079 { 080 LOG_CURSOR.debug( "Creating OrCursor {}", this ); 081 } 082 083 if ( cursors.size() <= 1 ) 084 { 085 throw new IllegalArgumentException( I18n.err( I18n.ERR_723 ) ); 086 } 087 088 this.cursors = cursors; 089 this.evaluators = evaluators; 090 this.blacklists = new ArrayList<>(); 091 this.partitionTxn = partitionTxn; 092 093 for ( int i = 0; i < cursors.size(); i++ ) 094 { 095 this.blacklists.add( new HashSet<String>() ); 096 } 097 098 this.cursorIndex = 0; 099 } 100 101 102 /** 103 * {@inheritDoc} 104 */ 105 protected String getUnsupportedMessage() 106 { 107 return UNSUPPORTED_MSG; 108 } 109 110 111 /** 112 * {@inheritDoc} 113 */ 114 public void beforeFirst() throws LdapException, CursorException 115 { 116 checkNotClosed(); 117 cursorIndex = 0; 118 cursors.get( cursorIndex ).beforeFirst(); 119 setAvailable( false ); 120 prefetched = null; 121 } 122 123 124 /** 125 * {@inheritDoc} 126 */ 127 public void afterLast() throws LdapException, CursorException 128 { 129 checkNotClosed(); 130 cursorIndex = cursors.size() - 1; 131 cursors.get( cursorIndex ).afterLast(); 132 setAvailable( false ); 133 prefetched = null; 134 } 135 136 137 /** 138 * {@inheritDoc} 139 */ 140 public boolean first() throws LdapException, CursorException 141 { 142 beforeFirst(); 143 144 return setAvailable( next() ); 145 } 146 147 148 /** 149 * {@inheritDoc} 150 */ 151 public boolean last() throws LdapException, CursorException 152 { 153 afterLast(); 154 155 return setAvailable( previous() ); 156 } 157 158 159 private boolean isBlackListed( String id ) 160 { 161 return blacklists.get( cursorIndex ).contains( id ); 162 } 163 164 165 /** 166 * The first sub-expression Cursor to advance to an entry adds the entry 167 * to the blacklists of other Cursors that might return that entry. 168 * 169 * @param indexEntry the index entry to blacklist 170 * @throws Exception if there are problems accessing underlying db 171 */ 172 private void blackListIfDuplicate( PartitionTxn partitionTxn, IndexEntry<?, String> indexEntry ) throws LdapException 173 { 174 for ( int ii = 0; ii < evaluators.size(); ii++ ) 175 { 176 if ( ii == cursorIndex ) 177 { 178 continue; 179 } 180 181 if ( evaluators.get( ii ).evaluate( partitionTxn, indexEntry ) ) 182 { 183 blacklists.get( ii ).add( indexEntry.getId() ); 184 } 185 } 186 } 187 188 189 /** 190 * {@inheritDoc} 191 */ 192 public boolean previous() throws LdapException, CursorException 193 { 194 while ( cursors.get( cursorIndex ).previous() ) 195 { 196 checkNotClosed(); 197 IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get(); 198 199 if ( !isBlackListed( candidate.getId() ) ) 200 { 201 blackListIfDuplicate( partitionTxn, candidate ); 202 203 prefetched = candidate; 204 return setAvailable( true ); 205 } 206 } 207 208 while ( cursorIndex > 0 ) 209 { 210 checkNotClosed(); 211 cursorIndex--; 212 cursors.get( cursorIndex ).afterLast(); 213 214 while ( cursors.get( cursorIndex ).previous() ) 215 { 216 checkNotClosed(); 217 IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get(); 218 219 if ( !isBlackListed( candidate.getId() ) ) 220 { 221 blackListIfDuplicate( partitionTxn, candidate ); 222 223 prefetched = candidate; 224 return setAvailable( true ); 225 } 226 } 227 } 228 229 prefetched = null; 230 231 return setAvailable( false ); 232 } 233 234 235 /** 236 * {@inheritDoc} 237 */ 238 public boolean next() throws LdapException, CursorException 239 { 240 while ( cursors.get( cursorIndex ).next() ) 241 { 242 checkNotClosed(); 243 IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get(); 244 245 if ( !isBlackListed( candidate.getId() ) ) 246 { 247 blackListIfDuplicate( partitionTxn, candidate ); 248 249 prefetched = candidate; 250 251 return setAvailable( true ); 252 } 253 } 254 255 while ( cursorIndex < cursors.size() - 1 ) 256 { 257 checkNotClosed(); 258 cursorIndex++; 259 cursors.get( cursorIndex ).beforeFirst(); 260 261 while ( cursors.get( cursorIndex ).next() ) 262 { 263 checkNotClosed(); 264 IndexEntry<V, String> candidate = cursors.get( cursorIndex ).get(); 265 266 if ( !isBlackListed( candidate.getId() ) ) 267 { 268 blackListIfDuplicate( partitionTxn, candidate ); 269 270 prefetched = candidate; 271 272 return setAvailable( true ); 273 } 274 } 275 } 276 277 prefetched = null; 278 279 return setAvailable( false ); 280 } 281 282 283 /** 284 * {@inheritDoc} 285 */ 286 public IndexEntry<V, String> get() throws CursorException 287 { 288 checkNotClosed(); 289 290 if ( available() ) 291 { 292 return prefetched; 293 } 294 295 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) ); 296 } 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public void close() throws IOException 304 { 305 if ( IS_DEBUG ) 306 { 307 LOG_CURSOR.debug( "Closing OrCursor {}", this ); 308 } 309 310 super.close(); 311 312 for ( Cursor<?> cursor : cursors ) 313 { 314 cursor.close(); 315 } 316 } 317 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override 323 public void close( Exception cause ) throws IOException 324 { 325 if ( IS_DEBUG ) 326 { 327 LOG_CURSOR.debug( "Closing OrCursor {}", this ); 328 } 329 330 super.close( cause ); 331 332 for ( Cursor<?> cursor : cursors ) 333 { 334 cursor.close( cause ); 335 } 336 } 337 338 339 /** 340 * Dumps the evaluators 341 */ 342 private String dumpEvaluators( String tabs ) 343 { 344 StringBuilder sb = new StringBuilder(); 345 346 for ( Evaluator<? extends ExprNode> evaluator : evaluators ) 347 { 348 sb.append( evaluator.toString( tabs + " >>" ) ); 349 } 350 351 return sb.toString(); 352 } 353 354 355 /** 356 * Dumps the cursors 357 */ 358 private String dumpCursors( String tabs ) 359 { 360 StringBuilder sb = new StringBuilder(); 361 362 for ( Cursor<IndexEntry<V, String>> cursor : cursors ) 363 { 364 sb.append( cursor.toString( tabs + " " ) ); 365 sb.append( "\n" ); 366 } 367 368 return sb.toString(); 369 } 370 371 372 /** 373 * @see Object#toString() 374 */ 375 @Override 376 public String toString( String tabs ) 377 { 378 StringBuilder sb = new StringBuilder(); 379 380 sb.append( tabs ).append( "OrCursor (" ); 381 382 if ( available() ) 383 { 384 sb.append( "available)" ); 385 } 386 else 387 { 388 sb.append( "absent)" ); 389 } 390 391 sb.append( "#" ).append( cursorIndex ).append( " : \n" ); 392 393 if ( ( evaluators != null ) && !evaluators.isEmpty() ) 394 { 395 sb.append( dumpEvaluators( tabs ) ); 396 } 397 398 if ( ( cursors != null ) && !cursors.isEmpty() ) 399 { 400 sb.append( dumpCursors( tabs ) ).append( '\n' ); 401 } 402 403 return sb.toString(); 404 } 405 406 407 /** 408 * @see Object#toString() 409 */ 410 public String toString() 411 { 412 return toString( "" ); 413 } 414}