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.FilenameFilter; 025import java.io.IOException; 026import java.net.URI; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033import org.apache.directory.api.ldap.model.constants.SchemaConstants; 034import org.apache.directory.api.ldap.model.cursor.Cursor; 035import org.apache.directory.api.ldap.model.cursor.Tuple; 036import org.apache.directory.api.ldap.model.entry.Attribute; 037import org.apache.directory.api.ldap.model.entry.Entry; 038import org.apache.directory.api.ldap.model.entry.Value; 039import org.apache.directory.api.ldap.model.exception.LdapException; 040import org.apache.directory.api.ldap.model.exception.LdapOtherException; 041import org.apache.directory.api.ldap.model.schema.AttributeType; 042import org.apache.directory.api.ldap.model.schema.SchemaManager; 043import org.apache.directory.api.util.exception.MultiException; 044import org.apache.directory.mavibot.btree.RecordManager; 045import org.apache.directory.server.constants.ApacheSchemaConstants; 046import org.apache.directory.server.core.api.DnFactory; 047import org.apache.directory.server.core.api.entry.ClonedServerEntry; 048import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 049import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 050import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 051import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 052import org.apache.directory.server.core.api.interceptor.context.OperationContext; 053import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 054import org.apache.directory.server.core.api.partition.Partition; 055import org.apache.directory.server.core.api.partition.PartitionReadTxn; 056import org.apache.directory.server.core.api.partition.PartitionTxn; 057import org.apache.directory.server.core.api.partition.PartitionWriteTxn; 058import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition; 059import org.apache.directory.server.i18n.I18n; 060import org.apache.directory.server.xdbm.Index; 061import org.apache.directory.server.xdbm.search.impl.CursorBuilder; 062import org.apache.directory.server.xdbm.search.impl.DefaultOptimizer; 063import org.apache.directory.server.xdbm.search.impl.DefaultSearchEngine; 064import org.apache.directory.server.xdbm.search.impl.EvaluatorBuilder; 065import org.apache.directory.server.xdbm.search.impl.NoOpOptimizer; 066import org.slf4j.Logger; 067import org.slf4j.LoggerFactory; 068 069import com.github.benmanes.caffeine.cache.Cache; 070import com.github.benmanes.caffeine.cache.Caffeine; 071 072 073/** 074 * A Mavibot partition 075 * 076 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 077 */ 078public class MavibotPartition extends AbstractBTreePartition 079{ 080 /** static logger */ 081 private static final Logger LOG = LoggerFactory.getLogger( MavibotPartition.class ); 082 083 private static final String MAVIBOT_DB_FILE_EXTN = ".data"; 084 085 private static final FilenameFilter DB_FILTER = new FilenameFilter() 086 { 087 088 public boolean accept( File dir, String name ) 089 { 090 // really important to filter master.db and master.lg files 091 return ( name.endsWith( MAVIBOT_DB_FILE_EXTN ) && !name.startsWith( "master." ) ); 092 } 093 }; 094 095 private RecordManager recordMan; 096 097 /** the entry cache */ 098 private Cache< String, Entry > entryCache; 099 100 101 public MavibotPartition( SchemaManager schemaManager, DnFactory dnFactory ) 102 { 103 super( schemaManager, dnFactory ); 104 105 MavibotEntrySerializer.setSchemaManager( schemaManager ); 106 107 // Initialize the cache size 108 if ( cacheSize < 0 ) 109 { 110 cacheSize = DEFAULT_CACHE_SIZE; 111 LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id ); 112 } 113 else 114 { 115 LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id ); 116 } 117 } 118 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override 124 protected void doRepair() throws LdapException 125 { 126 // Nothing to do 127 } 128 129 130 /** 131 * {@inheritDoc} 132 */ 133 @Override 134 protected void doInit() throws LdapException 135 { 136 if ( !initialized ) 137 { 138 // setup optimizer and registries for parent 139 if ( !isOptimizerEnabled() ) 140 { 141 setOptimizer( new NoOpOptimizer() ); 142 } 143 else 144 { 145 setOptimizer( new DefaultOptimizer( this ) ); 146 } 147 148 EvaluatorBuilder evaluatorBuilder = new EvaluatorBuilder( this, schemaManager ); 149 CursorBuilder cursorBuilder = new CursorBuilder( this, evaluatorBuilder ); 150 151 setSearchEngine( new DefaultSearchEngine( this, cursorBuilder, evaluatorBuilder, getOptimizer() ) ); 152 153 // Create the underlying directories (only if needed) 154 File partitionDir = new File( getPartitionPath() ); 155 156 if ( !partitionDir.exists() && !partitionDir.mkdirs() ) 157 { 158 throw new LdapOtherException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, partitionDir ) ); 159 } 160 161 if ( cacheSize < 0 ) 162 { 163 cacheSize = DEFAULT_CACHE_SIZE; 164 LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id ); 165 } 166 else 167 { 168 LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id ); 169 } 170 171 recordMan = new RecordManager( partitionDir.getPath() ); 172 173 // Initialize the indexes 174 super.doInit(); 175 176 // First, check if the file storing the data exists 177 178 try 179 { 180 master = new MavibotMasterTable( recordMan, schemaManager, "master", cacheSize ); 181 } 182 catch ( IOException ioe ) 183 { 184 throw new LdapOtherException( ioe.getMessage(), ioe ); 185 } 186 187 // get all index db files first 188 File[] allIndexDbFiles = partitionDir.listFiles( DB_FILTER ); 189 190 // get the names of the db files also 191 List<String> indexDbFileNameList = Arrays.asList( partitionDir.list( DB_FILTER ) ); 192 193 // then add all index objects to a list 194 List<String> allIndices = new ArrayList<>(); 195 196 for ( Index<?, String> index : systemIndices.values() ) 197 { 198 allIndices.add( index.getAttribute().getOid() ); 199 } 200 201 List<Index<?, String>> indexToBuild = new ArrayList<>(); 202 203 // this loop is used for two purposes 204 // one for collecting all user indices 205 // two for finding a new index to be built 206 // just to avoid another iteration for determining which is the new index 207 /* FIXME the below code needs to be modified to suit Mavibot 208 for ( Index<?, Entry, String> index : userIndices.values() ) 209 { 210 String indexOid = index.getAttribute().getOid(); 211 allIndices.add( indexOid ); 212 213 // take the part after removing .db from the 214 String name = indexOid + MAVIBOT_DB_FILE_EXTN; 215 216 // if the name doesn't exist in the list of index DB files 217 // this is a new index and we need to build it 218 if ( !indexDbFileNameList.contains( name ) ) 219 { 220 indexToBuild.add( index ); 221 } 222 } 223 224 if ( indexToBuild.size() > 0 ) 225 { 226 buildUserIndex( indexToBuild ); 227 } 228 229 deleteUnusedIndexFiles( allIndices, allIndexDbFiles ); 230 */ 231 232 entryCache = Caffeine.newBuilder().maximumSize( cacheSize ).build(); 233 234 // We are done ! 235 initialized = true; 236 } 237 } 238 239 240 @Override 241 protected Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException 242 { 243 MavibotIndex<?> mavibotIndex; 244 245 if ( index instanceof MavibotRdnIndex ) 246 { 247 mavibotIndex = ( MavibotRdnIndex ) index; 248 } 249 else if ( index instanceof MavibotDnIndex ) 250 { 251 mavibotIndex = ( MavibotDnIndex ) index; 252 } 253 else if ( index instanceof MavibotIndex<?> ) 254 { 255 mavibotIndex = ( MavibotIndex<?> ) index; 256 257 if ( mavibotIndex.getWkDirPath() == null ) 258 { 259 mavibotIndex.setWkDirPath( partitionPath ); 260 } 261 } 262 else 263 { 264 LOG.debug( "Supplied index {} is not a MavibotIndex. " 265 + "Will create new MavibotIndex using copied configuration parameters.", index ); 266 mavibotIndex = new MavibotIndex( index.getAttributeId(), true ); 267 mavibotIndex.setCacheSize( index.getCacheSize() ); 268 mavibotIndex.setWkDirPath( index.getWkDirPath() ); 269 } 270 271 mavibotIndex.setRecordManager( recordMan ); 272 273 try 274 { 275 mavibotIndex.init( schemaManager, schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ) ); 276 } 277 catch ( IOException ioe ) 278 { 279 throw new LdapOtherException( ioe.getMessage(), ioe ); 280 } 281 282 return mavibotIndex; 283 } 284 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 protected synchronized void doDestroy( PartitionTxn partitionTxn ) throws LdapException 291 { 292 MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) ); 293 294 if ( !initialized ) 295 { 296 return; 297 } 298 299 try 300 { 301 super.doDestroy( partitionTxn ); 302 } 303 catch ( Exception e ) 304 { 305 errors.addThrowable( e ); 306 } 307 308 // This is specific to the MAVIBOT store : close the record manager 309 try 310 { 311 recordMan.close(); 312 LOG.debug( "Closed record manager for {} partition.", suffixDn ); 313 } 314 catch ( Throwable t ) 315 { 316 LOG.error( I18n.err( I18n.ERR_127 ), t ); 317 errors.addThrowable( t ); 318 } 319 finally 320 { 321 if ( entryCache != null ) 322 { 323 entryCache.invalidateAll(); 324 } 325 } 326 327 if ( errors.size() > 0 ) 328 { 329 throw new LdapOtherException( errors.getMessage(), errors ); 330 } 331 } 332 333 334 @Override 335 protected Index createSystemIndex( String indexOid, URI path, boolean withReverse ) throws LdapException 336 { 337 LOG.debug( "Supplied index {} is not a MavibotIndex. " 338 + "Will create new MavibotIndex using copied configuration parameters." ); 339 MavibotIndex<?> mavibotIndex; 340 341 if ( indexOid.equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) ) 342 { 343 mavibotIndex = new MavibotRdnIndex(); 344 mavibotIndex.setAttributeId( ApacheSchemaConstants.APACHE_RDN_AT_OID ); 345 } 346 else if ( indexOid.equals( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ) ) 347 { 348 mavibotIndex = new MavibotDnIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 349 mavibotIndex.setAttributeId( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 350 } 351 else 352 { 353 mavibotIndex = new MavibotIndex( indexOid, withReverse ); 354 } 355 356 mavibotIndex.setWkDirPath( path ); 357 358 return mavibotIndex; 359 } 360 361 362 /**jdbm 363 * removes any unused/removed attribute index files present under the partition's 364 * working directory 365 */ 366 private void deleteUnusedIndexFiles( List<String> allIndices, File[] dbFiles ) 367 { 368 369 } 370 371 372 /** 373 * Builds user defined indexes on a attributes by browsing all the entries present in master db 374 * 375 * @param userIndexes then user defined indexes to create 376 * @throws Exception in case of any problems while building the index 377 */ 378 private void buildUserIndex( PartitionTxn partitionTxn, List<Index<?, String>> userIndexes ) throws Exception 379 { 380 Cursor<Tuple<String, Entry>> cursor = master.cursor(); 381 cursor.beforeFirst(); 382 383 while ( cursor.next() ) 384 { 385 for ( Index index : userIndexes ) 386 { 387 AttributeType atType = index.getAttribute(); 388 389 String attributeOid = index.getAttribute().getOid(); 390 391 LOG.info( "building the index for attribute type {}", atType ); 392 393 Tuple<String, Entry> tuple = cursor.get(); 394 395 String id = tuple.getKey(); 396 Entry entry = tuple.getValue(); 397 398 Attribute entryAttr = entry.get( atType ); 399 400 if ( entryAttr != null ) 401 { 402 for ( Value value : entryAttr ) 403 { 404 index.add( partitionTxn, value.getString(), id ); 405 } 406 407 // Adds only those attributes that are indexed 408 presenceIdx.add( partitionTxn, attributeOid, id ); 409 } 410 } 411 } 412 413 cursor.close(); 414 } 415 416 417 /** 418 * {@inheritDoc}} 419 */ 420 public String getDefaultId() 421 { 422 return Partition.DEFAULT_ID; 423 } 424 425 426 /** 427 * {@inheritDoc} 428 */ 429 public String getRootId() 430 { 431 return Partition.ROOT_ID; 432 } 433 434 435 public RecordManager getRecordMan() 436 { 437 return recordMan; 438 } 439 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override 445 public Entry lookupCache( String id ) 446 { 447 return ( entryCache != null ) ? entryCache.getIfPresent( id ) : null; 448 } 449 450 451 @Override 452 public void addToCache( String id, Entry entry ) 453 { 454 if ( entryCache == null ) 455 { 456 return; 457 } 458 459 if ( entry instanceof ClonedServerEntry ) 460 { 461 entry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); 462 } 463 464 entryCache.put( id, entry ); 465 } 466 467 468 @Override 469 public void updateCache( OperationContext opCtx ) 470 { 471 if ( entryCache == null ) 472 { 473 return; 474 } 475 476 try 477 { 478 if ( opCtx instanceof ModifyOperationContext ) 479 { 480 // replace the entry 481 ModifyOperationContext modCtx = ( ModifyOperationContext ) opCtx; 482 Entry entry = modCtx.getAlteredEntry(); 483 String id = entry.get( SchemaConstants.ENTRY_UUID_AT ).getString(); 484 485 if ( entry instanceof ClonedServerEntry ) 486 { 487 entry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); 488 } 489 490 entryCache.put( id, entry ); 491 } 492 else if ( ( opCtx instanceof MoveOperationContext ) || ( opCtx instanceof MoveAndRenameOperationContext ) 493 || ( opCtx instanceof RenameOperationContext ) ) 494 { 495 // clear the cache it is not worth updating all the children 496 entryCache.invalidateAll(); 497 } 498 else if ( opCtx instanceof DeleteOperationContext ) 499 { 500 // delete the entry 501 DeleteOperationContext delCtx = ( DeleteOperationContext ) opCtx; 502 entryCache.invalidate( delCtx.getEntry().get( SchemaConstants.ENTRY_UUID_AT ).getString() ); 503 } 504 } 505 catch ( LdapException e ) 506 { 507 LOG.warn( "Failed to update entry cache", e ); 508 } 509 } 510 511 512 /** 513 * @return The set of system and user indexes 514 */ 515 public Set<Index<?, String>> getAllIndices() 516 { 517 Set<Index<?, String>> all = new HashSet<>( systemIndices.values() ); 518 all.addAll( userIndices.values() ); 519 520 return all; 521 } 522 523 524 @Override 525 public PartitionReadTxn beginReadTransaction() 526 { 527 return new PartitionReadTxn(); 528 } 529 530 531 @Override 532 public PartitionWriteTxn beginWriteTransaction() 533 { 534 return new PartitionWriteTxn(); 535 } 536}