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.core.partition.impl.btree.mavibot; 021 022 023import java.io.File; 024import java.io.IOException; 025import java.net.URI; 026import java.util.Comparator; 027 028import org.apache.directory.api.ldap.model.constants.SchemaConstants; 029import org.apache.directory.api.ldap.model.cursor.Cursor; 030import org.apache.directory.api.ldap.model.cursor.CursorException; 031import org.apache.directory.api.ldap.model.cursor.EmptyCursor; 032import org.apache.directory.api.ldap.model.cursor.Tuple; 033import org.apache.directory.api.ldap.model.exception.LdapException; 034import org.apache.directory.api.ldap.model.exception.LdapOtherException; 035import org.apache.directory.api.ldap.model.schema.AttributeType; 036import org.apache.directory.api.ldap.model.schema.MatchingRule; 037import org.apache.directory.api.ldap.model.schema.SchemaManager; 038import org.apache.directory.api.ldap.model.schema.comparators.SerializableComparator; 039import org.apache.directory.mavibot.btree.RecordManager; 040import org.apache.directory.mavibot.btree.serializer.ByteArraySerializer; 041import org.apache.directory.mavibot.btree.serializer.ElementSerializer; 042import org.apache.directory.mavibot.btree.serializer.StringSerializer; 043import org.apache.directory.server.core.api.partition.PartitionTxn; 044import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition; 045import org.apache.directory.server.core.partition.impl.btree.IndexCursorAdaptor; 046import org.apache.directory.server.i18n.I18n; 047import org.apache.directory.server.xdbm.AbstractIndex; 048import org.apache.directory.server.xdbm.IndexEntry; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052 053/** 054 * A Mavibot based index implementation. It creates an Index for a give AttributeType. 055 * 056 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 057 */ 058public class MavibotIndex<K> extends AbstractIndex<K, String> 059{ 060 /** A logger for this class */ 061 private static final Logger LOG = LoggerFactory.getLogger( MavibotIndex.class.getSimpleName() ); 062 063 /** the key used for the forward btree name */ 064 public static final String FORWARD_BTREE = "_forward"; 065 066 /** the key used for the reverse btree name */ 067 public static final String REVERSE_BTREE = "_reverse"; 068 069 /** 070 * the forward btree where the btree key is the value of the indexed attribute and 071 * the value of the btree is the entry id of the entry containing an attribute with 072 * that value 073 */ 074 protected MavibotTable<K, String> forward; 075 076 /** 077 * the reverse btree where the btree key is the entry id of the entry containing a 078 * value for the indexed attribute, and the btree value is the value of the indexed 079 * attribute 080 */ 081 protected MavibotTable<String, K> reverse; 082 083 /** a custom working directory path when specified in configuration */ 084 protected File wkDirPath; 085 086 protected RecordManager recordMan; 087 088 089 // ------------------------------------------------------------------------ 090 // C O N S T R U C T O R S 091 // ---------------------------------------------------------------------- 092 /** 093 * Creates a JdbmIndex instance for a give AttributeId 094 * 095 * @param attributeId The Attribute ID 096 * @param withReverse If we want a reverse index to be created 097 */ 098 public MavibotIndex( String attributeId, boolean withReverse ) 099 { 100 super( attributeId, withReverse ); 101 102 initialized = false; 103 } 104 105 106 /** 107 * Initialize the index for an Attribute, with a specific working directory (may be null). 108 * 109 * @param schemaManager The schemaManager to use to get back the Attribute 110 * @param attributeType The attributeType this index is created for 111 * @throws IOException If the initialization failed 112 * @throws LdapException If the initialization failed 113 */ 114 public void init( SchemaManager schemaManager, AttributeType attributeType ) throws LdapException, IOException 115 { 116 LOG.debug( "Initializing an Index for attribute '{}'", attributeType.getName() ); 117 118 // check if the RecordManager reference is null, if yes, then throw an IllegalStateException 119 if ( recordMan == null ) 120 { 121 throw new IllegalStateException( "No RecordManager reference was set in the index " + getAttributeId() ); 122 } 123 124 this.attributeType = attributeType; 125 126 if ( attributeId == null ) 127 { 128 setAttributeId( attributeType.getName() ); 129 } 130 131 if ( this.wkDirPath == null ) 132 { 133 throw new NullPointerException( "The index working directory has not be set" ); 134 } 135 136 try 137 { 138 initTables( schemaManager ); 139 } 140 catch ( IOException e ) 141 { 142 // clean up 143 close( null ); 144 throw e; 145 } 146 147 initialized = true; 148 } 149 150 151 /** 152 * Initializes the forward and reverse tables used by this Index. 153 * 154 * @param schemaManager The server schemaManager 155 * @throws IOException if we cannot initialize the forward and reverse 156 * tables 157 */ 158 private void initTables( SchemaManager schemaManager ) throws IOException 159 { 160 MatchingRule mr = attributeType.getEquality(); 161 162 if ( mr == null ) 163 { 164 throw new IOException( I18n.err( I18n.ERR_574, attributeType.getName() ) ); 165 } 166 167 SerializableComparator<K> comp = new SerializableComparator<>( mr.getOid() ); 168 comp.setSchemaManager( schemaManager ); 169 170 /* 171 * The forward key/value map stores attribute values to master table 172 * primary keys. A value for an attribute can occur several times in 173 * different entries so the forward map can have more than one value. 174 */ 175 176 ElementSerializer<K> forwardKeySerializer = null; 177 178 if ( !attributeType.getSyntax().isHumanReadable() ) 179 { 180 forwardKeySerializer = ( ElementSerializer<K> ) new ByteArraySerializer( ( Comparator<byte[]> ) comp ); 181 } 182 else 183 { 184 forwardKeySerializer = ( ElementSerializer<K> ) new StringSerializer( ( Comparator<String> ) comp ); 185 } 186 187 boolean forwardDups = true; 188 189 String oid = attributeType.getOid(); 190 // disable duplicates for entryCSN and entryUUID attribute indices 191 if ( oid.equals( SchemaConstants.ENTRY_CSN_AT_OID ) || oid.equals( SchemaConstants.ENTRY_UUID_AT_OID ) ) 192 { 193 forwardDups = false; 194 } 195 196 String forwardTableName = attributeType.getOid() + FORWARD_BTREE; 197 forward = new MavibotTable<>( recordMan, schemaManager, forwardTableName, forwardKeySerializer, 198 StringSerializer.INSTANCE, forwardDups, AbstractBTreePartition.DEFAULT_CACHE_SIZE ); 199 200 /* 201 * Now the reverse map stores the primary key into the master table as 202 * the key and the values of attributes as the value. If an attribute 203 * is single valued according to its specification based on a schema 204 * then duplicate keys should not be allowed within the reverse table. 205 */ 206 if ( withReverse ) 207 { 208 String reverseTableName = attributeType.getOid() + REVERSE_BTREE; 209 reverse = new MavibotTable<>( recordMan, schemaManager, reverseTableName, StringSerializer.INSTANCE, 210 forwardKeySerializer, !attributeType.isSingleValued() ); 211 } 212 } 213 214 215 /** 216 * Sets the RecordManager 217 * 218 * @param rm the RecordManager instance 219 */ 220 public void setRecordManager( RecordManager rm ) 221 { 222 this.recordMan = rm; 223 } 224 225 226 // ------------------------------------------------------------------------ 227 // C O N F I G U R A T I O N M E T H O D S 228 // ------------------------------------------------------------------------ 229 230 /** 231 * Sets the working directory path to something other than the default. Sometimes more 232 * performance is gained by locating indices on separate disk spindles. 233 * 234 * @param wkDirPath optional working directory path 235 */ 236 public void setWkDirPath( URI wkDirPath ) 237 { 238 //.out.println( "IDX Defining a WorkingDir : " + wkDirPath ); 239 protect( "wkDirPath" ); 240 this.wkDirPath = new File( wkDirPath ); 241 } 242 243 244 /** 245 * Gets the working directory path to something other than the default. Sometimes more 246 * performance is gained by locating indices on separate disk spindles. 247 * 248 * @return optional working directory path 249 */ 250 public URI getWkDirPath() 251 { 252 return wkDirPath != null ? wkDirPath.toURI() : null; 253 } 254 255 256 // ------------------------------------------------------------------------ 257 // Scan Count Methods 258 // ------------------------------------------------------------------------ 259 /** 260 * {@inheritDoc} 261 */ 262 public long count( PartitionTxn partitionTxn ) throws LdapException 263 { 264 return forward.count( partitionTxn ); 265 } 266 267 268 /** 269 * {@inheritDoc} 270 */ 271 public long count( PartitionTxn partitionTxn, K attrVal ) throws LdapException 272 { 273 return forward.count( partitionTxn, attrVal ); 274 } 275 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override 281 public long greaterThanCount( PartitionTxn partitionTxn, K attrVal ) throws LdapException 282 { 283 return forward.greaterThanCount( partitionTxn, attrVal ); 284 } 285 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override 291 public long lessThanCount( PartitionTxn partitionTxn, K attrVal ) throws LdapException 292 { 293 return forward.lessThanCount( partitionTxn, attrVal ); 294 } 295 296 297 // ------------------------------------------------------------------------ 298 // Forward and Reverse Lookups 299 // ------------------------------------------------------------------------ 300 301 /** 302 * Do a lookup using the forward table 303 * 304 * @param partitionTxn The Transaction to use 305 * @param attrVal The Key we are looking for 306 * @return The found value 307 * @throws LdapException If the lookup failed 308 */ 309 public String forwardLookup( PartitionTxn partitionTxn, K attrVal ) throws LdapException 310 { 311 return forward.get( partitionTxn, attrVal ); 312 } 313 314 315 /** 316 * {@inheritDoc} 317 */ 318 public K reverseLookup( PartitionTxn partitionTxn, String id ) throws LdapException 319 { 320 if ( withReverse ) 321 { 322 return reverse.get( partitionTxn, id ); 323 } 324 else 325 { 326 return null; 327 } 328 } 329 330 331 // ------------------------------------------------------------------------ 332 // Add/Drop Methods 333 // ------------------------------------------------------------------------ 334 335 /** 336 * {@inheritDoc} 337 */ 338 public synchronized void add( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException 339 { 340 // The pair to be removed must exists 341 forward.put( partitionTxn, attrVal, id ); 342 343 if ( withReverse ) 344 { 345 reverse.put( partitionTxn, id, attrVal ); 346 } 347 } 348 349 350 /** 351 * {@inheritDoc} 352 */ 353 @Override 354 public synchronized void drop( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException 355 { 356 // The pair to be removed must exists 357 if ( forward.has( partitionTxn, attrVal, id ) ) 358 { 359 forward.remove( partitionTxn, attrVal, id ); 360 361 if ( withReverse ) 362 { 363 reverse.remove( partitionTxn, id, attrVal ); 364 } 365 } 366 } 367 368 369 /** 370 * {@inheritDoc} 371 */ 372 public void drop( PartitionTxn partitionTxn, String entryId ) throws LdapException 373 { 374 if ( withReverse ) 375 { 376 if ( isDupsEnabled() ) 377 { 378 // Build a cursor to iterate on all the keys referencing 379 // this entryId 380 Cursor<Tuple<String, K>> values = reverse.cursor( partitionTxn, entryId ); 381 382 try 383 { 384 while ( values.next() ) 385 { 386 // Remove the Key -> entryId from the index 387 forward.remove( partitionTxn, values.get().getValue(), entryId ); 388 } 389 390 values.close(); 391 } 392 catch ( CursorException | IOException e ) 393 { 394 throw new LdapOtherException( e.getMessage(), e ); 395 } 396 } 397 else 398 { 399 K key = reverse.get( partitionTxn, entryId ); 400 401 forward.remove( partitionTxn, key ); 402 } 403 404 // Remove the id -> key from the reverse index 405 reverse.remove( partitionTxn, entryId ); 406 } 407 } 408 409 410 // ------------------------------------------------------------------------ 411 // Index Cursor Operations 412 // ------------------------------------------------------------------------ 413 @SuppressWarnings("unchecked") 414 public Cursor<IndexEntry<K, String>> forwardCursor( PartitionTxn partitionTxn ) throws LdapException 415 { 416 return new IndexCursorAdaptor<>( partitionTxn, ( Cursor ) forward.cursor(), true ); 417 } 418 419 420 public Cursor<IndexEntry<K, String>> forwardCursor( PartitionTxn partitionTxn, K key ) throws LdapException 421 { 422 return new IndexCursorAdaptor<>( partitionTxn, ( Cursor ) forward.cursor( partitionTxn, key ), true ); 423 } 424 425 426 /** 427 * {@inheritDoc} 428 */ 429 @Override 430 public Cursor<K> reverseValueCursor( PartitionTxn partitionTxn, String id ) throws LdapException 431 { 432 if ( withReverse ) 433 { 434 return reverse.valueCursor( partitionTxn, id ); 435 } 436 else 437 { 438 return new EmptyCursor<>(); 439 } 440 } 441 442 443 public Cursor<String> forwardValueCursor( PartitionTxn partitionTxn, K key ) throws LdapException 444 { 445 return forward.valueCursor( partitionTxn, key ); 446 } 447 448 449 // ------------------------------------------------------------------------ 450 // Value Assertion (a.k.a Index Lookup) Methods // 451 // ------------------------------------------------------------------------ 452 /** 453 * {@inheritDoc} 454 */ 455 public boolean forward( PartitionTxn partitionTxn, K attrVal ) throws LdapException 456 { 457 return forward.has( partitionTxn, attrVal ); 458 } 459 460 461 /** 462 * {@inheritDoc} 463 */ 464 public boolean forward( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException 465 { 466 return forward.has( partitionTxn, attrVal, id ); 467 } 468 469 470 /** 471 * {@inheritDoc} 472 */ 473 @Override 474 public boolean reverse( PartitionTxn partitionTxn, String id ) throws LdapException 475 { 476 if ( withReverse ) 477 { 478 return reverse.has( partitionTxn, id ); 479 } 480 else 481 { 482 return false; 483 } 484 } 485 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override 491 public boolean reverse( PartitionTxn partitionTxn, String id, K attrVal ) throws LdapException 492 { 493 return forward.has( partitionTxn, attrVal, id ); 494 } 495 496 497 // ------------------------------------------------------------------------ 498 // Maintenance Methods 499 // ------------------------------------------------------------------------ 500 /** 501 * {@inheritDoc} 502 */ 503 @Override 504 public synchronized void close( PartitionTxn partitionTxn ) throws IOException 505 { 506 try 507 { 508 if ( forward != null ) 509 { 510 forward.close( partitionTxn ); 511 } 512 513 if ( reverse != null ) 514 { 515 reverse.close( partitionTxn ); 516 } 517 } 518 catch ( Exception e ) 519 { 520 throw new IOException( e ); 521 } 522 } 523 524 525 /** 526 * Force the flush of this index 527 * 528 * @throws IOException If the flush failed 529 */ 530 public synchronized void sync() throws IOException 531 { 532 forward.getBTree().flush(); 533 534 if ( reverse != null ) 535 { 536 reverse.getBTree().flush(); 537 } 538 } 539 540 541 /** 542 * {@inheritDoc} 543 */ 544 @Override 545 public boolean isDupsEnabled() 546 { 547 if ( withReverse ) 548 { 549 return reverse.isDupsEnabled(); 550 } 551 else 552 { 553 return false; 554 } 555 } 556 557 558 /** 559 * @see Object#toString() 560 */ 561 public String toString() 562 { 563 return "Index<" + attributeId + ">"; 564 } 565}