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 021package org.apache.directory.server.core.partition.ldif; 022 023 024import java.io.File; 025import java.io.FileNotFoundException; 026import java.io.IOException; 027import java.io.RandomAccessFile; 028import java.util.Iterator; 029import java.util.UUID; 030 031import org.apache.directory.api.ldap.model.constants.SchemaConstants; 032import org.apache.directory.api.ldap.model.cursor.Cursor; 033import org.apache.directory.api.ldap.model.entry.DefaultEntry; 034import org.apache.directory.api.ldap.model.entry.Entry; 035import org.apache.directory.api.ldap.model.entry.Modification; 036import org.apache.directory.api.ldap.model.exception.LdapException; 037import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; 038import org.apache.directory.api.ldap.model.exception.LdapOperationException; 039import org.apache.directory.api.ldap.model.exception.LdapOtherException; 040import org.apache.directory.api.ldap.model.ldif.LdifEntry; 041import org.apache.directory.api.ldap.model.ldif.LdifReader; 042import org.apache.directory.api.ldap.model.ldif.LdifUtils; 043import org.apache.directory.api.ldap.model.name.Dn; 044import org.apache.directory.api.ldap.model.name.Rdn; 045import org.apache.directory.api.ldap.model.schema.SchemaManager; 046import org.apache.directory.api.util.Strings; 047import org.apache.directory.server.core.api.DnFactory; 048import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 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.RenameOperationContext; 053import org.apache.directory.server.core.api.partition.PartitionTxn; 054import org.apache.directory.server.i18n.I18n; 055import org.apache.directory.server.xdbm.IndexEntry; 056import org.apache.directory.server.xdbm.ParentIdAndRdn; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060 061/** 062 * A Partition implementation backed by a single LDIF file. 063 * 064 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 065 */ 066public class SingleFileLdifPartition extends AbstractLdifPartition 067{ 068 /** the LDIF file holding the partition's data */ 069 private RandomAccessFile ldifFile; 070 071 /** flag to enable/disable re-writing in-memory partition data back to file, default is set to true */ 072 private volatile boolean enableRewriting = true; 073 074 /** flag used internally to detect if partition data was updated in memory but not on disk */ 075 private boolean dirty = false; 076 077 /** lock for serializing the operations on the backing LDIF file */ 078 private Object lock = new Object(); 079 080 private static final Logger LOG = LoggerFactory.getLogger( SingleFileLdifPartition.class ); 081 082 083 /** 084 * Creates a new instance of SingleFileLdifPartition. 085 * 086 * @param schemaManager The SchemaManager instance 087 * @param dnFactory The DN factory 088 */ 089 public SingleFileLdifPartition( SchemaManager schemaManager, DnFactory dnFactory ) 090 { 091 super( schemaManager, dnFactory ); 092 } 093 094 095 @Override 096 protected void doInit() throws LdapException 097 { 098 if ( !initialized ) 099 { 100 if ( getPartitionPath() == null ) 101 { 102 throw new IllegalArgumentException( "Partition path cannot be null" ); 103 } 104 105 File partitionFile = new File( getPartitionPath() ); 106 107 if ( partitionFile.exists() && !partitionFile.isFile() ) 108 { 109 throw new IllegalArgumentException( "Partition path must be a LDIF file" ); 110 } 111 112 try 113 { 114 ldifFile = new RandomAccessFile( partitionFile, "rws" ); 115 } 116 catch ( FileNotFoundException fnfe ) 117 { 118 throw new LdapOtherException( fnfe.getMessage(), fnfe ); 119 } 120 121 LOG.debug( "id is : {}", getId() ); 122 123 // Initialize the suffixDirectory : it's a composition 124 // of the workingDirectory followed by the suffix 125 if ( ( suffixDn == null ) || ( suffixDn.isEmpty() ) ) 126 { 127 String msg = I18n.err( I18n.ERR_150 ); 128 LOG.error( msg ); 129 throw new LdapInvalidDnException( msg ); 130 } 131 132 if ( !suffixDn.isSchemaAware() ) 133 { 134 suffixDn = new Dn( schemaManager, suffixDn ); 135 } 136 137 super.doInit(); 138 139 loadEntries(); 140 } 141 } 142 143 144 /** 145 * load the entries from the LDIF file if present 146 * @throws Exception 147 */ 148 private void loadEntries() throws LdapException 149 { 150 try ( RandomAccessLdifReader parser = new RandomAccessLdifReader( schemaManager ) ) 151 { 152 Iterator<LdifEntry> itr = parser.iterator(); 153 154 if ( !itr.hasNext() ) 155 { 156 return; 157 } 158 159 LdifEntry ldifEntry = itr.next(); 160 161 contextEntry = new DefaultEntry( schemaManager, ldifEntry.getEntry() ); 162 163 if ( suffixDn.equals( contextEntry.getDn() ) ) 164 { 165 addMandatoryOpAt( contextEntry ); 166 167 AddOperationContext addContext = new AddOperationContext( null, contextEntry ); 168 addContext.setPartition( this ); 169 addContext.setTransaction( this.beginWriteTransaction() ); 170 171 super.add( addContext ); 172 } 173 else 174 { 175 throw new LdapException( "The given LDIF file doesn't contain the context entry" ); 176 } 177 178 while ( itr.hasNext() ) 179 { 180 ldifEntry = itr.next(); 181 182 Entry entry = new DefaultEntry( schemaManager, ldifEntry.getEntry() ); 183 184 addMandatoryOpAt( entry ); 185 186 AddOperationContext addContext = new AddOperationContext( null, entry ); 187 addContext.setPartition( this ); 188 addContext.setTransaction( this.beginWriteTransaction() ); 189 190 super.add( addContext ); 191 } 192 } 193 catch ( IOException ioe ) 194 { 195 throw new LdapOtherException( ioe.getMessage(), ioe ); 196 } 197 } 198 199 200 //--------------------------------------------------------------------------------------------- 201 // Operations 202 //--------------------------------------------------------------------------------------------- 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public void add( AddOperationContext addContext ) throws LdapException 208 { 209 synchronized ( lock ) 210 { 211 super.add( addContext ); 212 213 if ( contextEntry == null ) 214 { 215 Entry entry = addContext.getEntry(); 216 217 if ( entry.getDn().equals( suffixDn ) ) 218 { 219 contextEntry = entry; 220 } 221 } 222 223 dirty = true; 224 rewritePartitionData( addContext.getTransaction() ); 225 } 226 } 227 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override 233 public void modify( ModifyOperationContext modifyContext ) throws LdapException 234 { 235 PartitionTxn partitionTxn = modifyContext.getTransaction(); 236 237 synchronized ( lock ) 238 { 239 try 240 { 241 Entry modifiedEntry = super.modify( partitionTxn, modifyContext.getDn(), 242 modifyContext.getModItems().toArray( new Modification[] 243 {} ) ); 244 245 // Remove the EntryDN 246 modifiedEntry.removeAttributes( entryDnAT ); 247 248 modifyContext.setAlteredEntry( modifiedEntry ); 249 } 250 catch ( Exception e ) 251 { 252 throw new LdapOperationException( e.getMessage(), e ); 253 } 254 255 dirty = true; 256 rewritePartitionData( partitionTxn ); 257 } 258 } 259 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override 265 public void rename( RenameOperationContext renameContext ) throws LdapException 266 { 267 synchronized ( lock ) 268 { 269 super.rename( renameContext ); 270 dirty = true; 271 rewritePartitionData( renameContext.getTransaction() ); 272 } 273 } 274 275 276 /** 277 * {@inheritDoc} 278 */ 279 @Override 280 public void move( MoveOperationContext moveContext ) throws LdapException 281 { 282 synchronized ( lock ) 283 { 284 super.move( moveContext ); 285 dirty = true; 286 rewritePartitionData( moveContext.getTransaction() ); 287 } 288 } 289 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 public void moveAndRename( MoveAndRenameOperationContext opContext ) throws LdapException 296 { 297 synchronized ( lock ) 298 { 299 super.moveAndRename( opContext ); 300 dirty = true; 301 rewritePartitionData( opContext.getTransaction() ); 302 } 303 } 304 305 306 @Override 307 public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException 308 { 309 synchronized ( lock ) 310 { 311 Entry deletedEntry = super.delete( partitionTxn, id ); 312 dirty = true; 313 rewritePartitionData( partitionTxn ); 314 315 return deletedEntry; 316 } 317 } 318 319 320 /** 321 * writes the partition's data to the file if {@link #enableRewriting} is set to true 322 * and partition was modified since the last write or {@link #dirty} data. 323 * 324 * @throws LdapException 325 */ 326 private void rewritePartitionData( PartitionTxn partitionTxn ) throws LdapException 327 { 328 synchronized ( lock ) 329 { 330 if ( !enableRewriting || !dirty ) 331 { 332 return; 333 } 334 335 try 336 { 337 ldifFile.setLength( 0 ); // wipe the file clean 338 339 String suffixId = getEntryId( partitionTxn, suffixDn ); 340 341 if ( suffixId == null ) 342 { 343 contextEntry = null; 344 return; 345 } 346 347 ParentIdAndRdn suffixEntry = rdnIdx.reverseLookup( partitionTxn, suffixId ); 348 349 if ( suffixEntry != null ) 350 { 351 Entry entry = master.get( partitionTxn, suffixId ); 352 353 // Don't write the EntryDN attribute 354 entry.removeAttributes( entryDnAT ); 355 356 entry.setDn( suffixDn ); 357 358 appendLdif( entry ); 359 360 appendRecursive( partitionTxn, suffixId, suffixEntry.getNbChildren() ); 361 } 362 363 dirty = false; 364 } 365 catch ( LdapException e ) 366 { 367 throw e; 368 } 369 catch ( Exception e ) 370 { 371 throw new LdapException( e ); 372 } 373 } 374 } 375 376 377 private void appendRecursive( PartitionTxn partitionTxn, String id, int nbSibbling ) throws Exception 378 { 379 // Start with the root 380 Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn ); 381 382 IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); 383 startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) ); 384 cursor.before( startingPos ); 385 int countChildren = 0; 386 387 while ( cursor.next() && ( countChildren < nbSibbling ) ) 388 { 389 IndexEntry<ParentIdAndRdn, String> element = cursor.get(); 390 String childId = element.getId(); 391 Entry entry = fetch( partitionTxn, childId ); 392 393 // Remove the EntryDn 394 entry.removeAttributes( SchemaConstants.ENTRY_DN_AT ); 395 396 appendLdif( entry ); 397 398 countChildren++; 399 400 // And now, the children 401 int nbChildren = element.getKey().getNbChildren(); 402 403 if ( nbChildren > 0 ) 404 { 405 appendRecursive( partitionTxn, childId, nbChildren ); 406 } 407 } 408 409 cursor.close(); 410 } 411 412 413 /** 414 * append data to the LDIF file 415 * 416 * @param entry the entry to be written 417 * @throws LdapException 418 */ 419 private void appendLdif( Entry entry ) throws IOException 420 { 421 synchronized ( lock ) 422 { 423 String ldif = LdifUtils.convertToLdif( entry ); 424 ldifFile.write( Strings.getBytesUtf8( ldif + "\n" ) ); 425 } 426 } 427 428 /** 429 * an LdifReader backed by a RandomAccessFile 430 */ 431 private class RandomAccessLdifReader extends LdifReader 432 { 433 private long len; 434 435 436 RandomAccessLdifReader() throws LdapException 437 { 438 try 439 { 440 len = ldifFile.length(); 441 super.init(); 442 } 443 catch ( IOException e ) 444 { 445 LdapException le = new LdapException( e.getMessage(), e ); 446 le.initCause( e ); 447 448 throw le; 449 } 450 } 451 452 453 RandomAccessLdifReader( SchemaManager schemaManager ) throws LdapException 454 { 455 try 456 { 457 this.schemaManager = schemaManager; 458 len = ldifFile.length(); 459 super.init(); 460 } 461 catch ( IOException e ) 462 { 463 LdapException le = new LdapException( e.getMessage(), e ); 464 le.initCause( e ); 465 466 throw le; 467 } 468 } 469 470 471 @Override 472 protected String getLine() throws IOException 473 { 474 if ( len == 0 ) 475 { 476 return null; 477 } 478 479 return ldifFile.readLine(); 480 } 481 } 482 483 484 /** 485 * add the CSN and UUID attributes to the entry if they are not present 486 */ 487 private void addMandatoryOpAt( Entry entry ) throws LdapException 488 { 489 if ( entry.get( SchemaConstants.ENTRY_CSN_AT ) == null ) 490 { 491 entry.add( SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString() ); 492 } 493 494 if ( entry.get( SchemaConstants.ENTRY_UUID_AT ) == null ) 495 { 496 String uuid = UUID.randomUUID().toString(); 497 entry.add( SchemaConstants.ENTRY_UUID_AT, uuid ); 498 } 499 } 500 501 502 /** 503 * {@inheritDoc} 504 */ 505 @Override 506 protected void doDestroy( PartitionTxn partitionTxn ) throws LdapException 507 { 508 super.doDestroy( partitionTxn ); 509 510 try 511 { 512 ldifFile.close(); 513 } 514 catch ( IOException ioe ) 515 { 516 throw new LdapOtherException( ioe.getMessage(), ioe ); 517 } 518 } 519 520 521 /** 522 * enable/disable the re-writing of partition data. 523 * This method internally calls the rewritePartitionData() method to save any dirty data if present 524 * 525 * @param partitionTxn The transaction to use 526 * @param enableRewriting flag to enable/disable re-writing 527 * @throws LdapException If we weren't able to save the dirty data 528 */ 529 public void setEnableRewriting( PartitionTxn partitionTxn, boolean enableRewriting ) throws LdapException 530 { 531 this.enableRewriting = enableRewriting; 532 533 // save data if found dirty 534 rewritePartitionData( partitionTxn ); 535 } 536}