001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.server.util.ServerConstants.*; 022import static org.opends.server.util.StaticUtils.*; 023 024import java.io.File; 025import java.io.IOException; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.LinkedHashMap; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.concurrent.locks.ReentrantReadWriteLock; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.config.server.ConfigChangeResult; 039import org.forgerock.opendj.config.server.ConfigException; 040import org.forgerock.opendj.config.server.ConfigurationChangeListener; 041import org.forgerock.opendj.ldap.ConditionResult; 042import org.forgerock.opendj.ldap.DN; 043import org.forgerock.opendj.ldap.ResultCode; 044import org.forgerock.opendj.ldap.SearchScope; 045import org.forgerock.opendj.ldap.schema.AttributeType; 046import org.forgerock.opendj.server.config.server.LDIFBackendCfg; 047import org.opends.server.api.AlertGenerator; 048import org.opends.server.api.Backend; 049import org.opends.server.controls.SubtreeDeleteControl; 050import org.opends.server.core.AddOperation; 051import org.opends.server.core.DeleteOperation; 052import org.opends.server.core.DirectoryServer; 053import org.opends.server.core.ModifyDNOperation; 054import org.opends.server.core.ModifyOperation; 055import org.opends.server.core.SearchOperation; 056import org.opends.server.core.ServerContext; 057import org.opends.server.types.BackupConfig; 058import org.opends.server.types.BackupDirectory; 059import org.opends.server.types.Control; 060import org.opends.server.types.DirectoryException; 061import org.opends.server.types.Entry; 062import org.opends.server.types.ExistingFileBehavior; 063import org.opends.server.types.IndexType; 064import org.opends.server.types.InitializationException; 065import org.opends.server.types.LDIFExportConfig; 066import org.opends.server.types.LDIFImportConfig; 067import org.opends.server.types.LDIFImportResult; 068import org.opends.server.types.RestoreConfig; 069import org.opends.server.types.SearchFilter; 070import org.opends.server.util.LDIFException; 071import org.opends.server.util.LDIFReader; 072import org.opends.server.util.LDIFWriter; 073import org.opends.server.util.StaticUtils; 074 075/** 076 * This class provides a backend implementation that stores the underlying data 077 * in an LDIF file. When the backend is initialized, the contents of the 078 * backend are read into memory and all read operations are performed purely 079 * from memory. Write operations cause the underlying LDIF file to be 080 * re-written on disk. 081 */ 082public class LDIFBackend 083 extends Backend<LDIFBackendCfg> 084 implements ConfigurationChangeListener<LDIFBackendCfg>, AlertGenerator 085{ 086 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 087 088 /** The base DNs for this backend. */ 089 private Set<DN> baseDNs; 090 091 /** The mapping between parent DNs and their immediate children. */ 092 private final Map<DN, Set<DN>> childDNs = new HashMap<>(); 093 094 /** The set of supported controls for this backend. */ 095 private final Set<String> supportedControls = 096 Collections.singleton(OID_SUBTREE_DELETE_CONTROL); 097 098 /** The current configuration for this backend. */ 099 private LDIFBackendCfg currentConfig; 100 101 /** The mapping between entry DNs and the corresponding entries. */ 102 private final Map<DN, Entry> entryMap = new LinkedHashMap<>(); 103 104 /** A read-write lock used to protect access to this backend. */ 105 private final ReentrantReadWriteLock backendLock = new ReentrantReadWriteLock(); 106 107 /** The path to the LDIF file containing the data for this backend. */ 108 private String ldifFilePath; 109 110 /** 111 * Creates a new backend with the provided information. All backend 112 * implementations must implement a default constructor that use 113 * <CODE>super()</CODE> to invoke this constructor. 114 */ 115 public LDIFBackend() 116 { 117 } 118 119 @Override 120 public void openBackend() 121 throws ConfigException, InitializationException 122 { 123 // We won't support anything other than exactly one base DN in this implementation. 124 // If we were to add such support in the future, we would likely want 125 // to separate the data for each base DN into a separate entry map. 126 if (baseDNs == null || baseDNs.size() != 1) 127 { 128 throw new ConfigException(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(currentConfig.dn())); 129 } 130 131 for (DN dn : baseDNs) 132 { 133 try 134 { 135 DirectoryServer.registerBaseDN(dn, this, 136 currentConfig.isIsPrivateBackend()); 137 } 138 catch (Exception e) 139 { 140 logger.traceException(e); 141 142 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 143 dn, getExceptionMessage(e)); 144 throw new InitializationException(message, e); 145 } 146 } 147 148 DirectoryServer.registerAlertGenerator(this); 149 150 readLDIF(); 151 } 152 153 /** 154 * Reads the contents of the LDIF backing file into memory. 155 * 156 * @throws InitializationException If a problem occurs while reading the 157 * LDIF file. 158 */ 159 private void readLDIF() 160 throws InitializationException 161 { 162 File ldifFile = getFileForPath(ldifFilePath); 163 if (! ldifFile.exists()) 164 { 165 // This is fine. We will just start with an empty backend. 166 if (logger.isTraceEnabled()) 167 { 168 logger.trace("LDIF backend starting empty because LDIF file " + 169 ldifFilePath + " does not exist"); 170 } 171 172 entryMap.clear(); 173 childDNs.clear(); 174 return; 175 } 176 177 try 178 { 179 importLDIF(new LDIFImportConfig(ldifFile.getAbsolutePath()), false); 180 } 181 catch (DirectoryException de) 182 { 183 throw new InitializationException(de.getMessageObject(), de); 184 } 185 } 186 187 /** 188 * Writes the current set of entries to the target LDIF file. The new LDIF 189 * will first be created as a temporary file and then renamed into place. The 190 * caller must either hold the write lock for this backend, or must ensure 191 * that it's in some other state that guarantees exclusive access to the data. 192 * 193 * @throws DirectoryException If a problem occurs that prevents the updated 194 * LDIF from being written. 195 */ 196 private void writeLDIF() 197 throws DirectoryException 198 { 199 File ldifFile = getFileForPath(ldifFilePath); 200 File tempFile = new File(ldifFile.getAbsolutePath() + ".new"); 201 File oldFile = new File(ldifFile.getAbsolutePath() + ".old"); 202 203 // Write the new data to a temporary file. 204 LDIFWriter writer; 205 try 206 { 207 LDIFExportConfig exportConfig = 208 new LDIFExportConfig(tempFile.getAbsolutePath(), 209 ExistingFileBehavior.OVERWRITE); 210 writer = new LDIFWriter(exportConfig); 211 } 212 catch (Exception e) 213 { 214 logger.traceException(e); 215 216 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_CREATING_FILE.get( 217 tempFile.getAbsolutePath(), 218 currentConfig.dn(), 219 stackTraceToSingleLineString(e)); 220 DirectoryServer.sendAlertNotification(this, 221 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m); 222 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 223 m, e); 224 } 225 226 for (Entry entry : entryMap.values()) 227 { 228 try 229 { 230 writer.writeEntry(entry); 231 } 232 catch (Exception e) 233 { 234 logger.traceException(e); 235 236 StaticUtils.close(writer); 237 238 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_WRITING_FILE.get( 239 tempFile.getAbsolutePath(), 240 currentConfig.dn(), 241 stackTraceToSingleLineString(e)); 242 DirectoryServer.sendAlertNotification(this, 243 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m); 244 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 245 m, e); 246 } 247 } 248 249 // On Linux the final write() on a file can actually fail but not throw an Exception. 250 // The close() will throw an Exception in this case so we MUST check for Exceptions 251 // here. 252 try 253 { 254 writer.close(); 255 } 256 catch (Exception e) 257 { 258 logger.traceException(e); 259 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_CLOSING_FILE.get( 260 tempFile.getAbsolutePath(), 261 currentConfig.dn(), 262 stackTraceToSingleLineString(e)); 263 DirectoryServer.sendAlertNotification(this, 264 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m); 265 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 266 m, e); 267 } 268 269 // Extra sanity check 270 if (!entryMap.isEmpty() && tempFile.exists() && tempFile.length() == 0) 271 { 272 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_EMPTY_FILE.get( 273 tempFile.getAbsolutePath(), 274 currentConfig.dn()); 275 DirectoryServer.sendAlertNotification(this, 276 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m); 277 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m); 278 } 279 280 if (tempFile.exists()) 281 { 282 // Rename the existing "live" file out of the way and move the new file 283 // into place. 284 try 285 { 286 oldFile.delete(); 287 } 288 catch (Exception e) 289 { 290 logger.traceException(e); 291 } 292 } 293 294 try 295 { 296 if (ldifFile.exists()) 297 { 298 ldifFile.renameTo(oldFile); 299 } 300 } 301 catch (Exception e) 302 { 303 logger.traceException(e); 304 } 305 306 try 307 { 308 tempFile.renameTo(ldifFile); 309 } 310 catch (Exception e) 311 { 312 logger.traceException(e); 313 314 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_RENAMING_FILE.get( 315 tempFile.getAbsolutePath(), 316 ldifFile.getAbsolutePath(), 317 currentConfig.dn(), 318 stackTraceToSingleLineString(e)); 319 DirectoryServer.sendAlertNotification(this, 320 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m); 321 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 322 m, e); 323 } 324 } 325 326 @Override 327 public void closeBackend() 328 { 329 backendLock.writeLock().lock(); 330 331 try 332 { 333 currentConfig.removeLDIFChangeListener(this); 334 DirectoryServer.deregisterAlertGenerator(this); 335 336 for (DN dn : baseDNs) 337 { 338 try 339 { 340 DirectoryServer.deregisterBaseDN(dn); 341 } 342 catch (Exception e) 343 { 344 logger.traceException(e); 345 } 346 } 347 } 348 finally 349 { 350 backendLock.writeLock().unlock(); 351 } 352 } 353 354 @Override 355 public Set<DN> getBaseDNs() 356 { 357 return baseDNs; 358 } 359 360 @Override 361 public long getEntryCount() 362 { 363 backendLock.readLock().lock(); 364 365 try 366 { 367 if (entryMap != null) 368 { 369 return entryMap.size(); 370 } 371 372 return -1; 373 } 374 finally 375 { 376 backendLock.readLock().unlock(); 377 } 378 } 379 380 @Override 381 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 382 { 383 // All searches in this backend will always be considered indexed. 384 return true; 385 } 386 387 @Override 388 public ConditionResult hasSubordinates(DN entryDN) 389 throws DirectoryException 390 { 391 backendLock.readLock().lock(); 392 393 try 394 { 395 Set<DN> childDNSet = childDNs.get(entryDN); 396 if (childDNSet == null || childDNSet.isEmpty()) 397 { 398 // It could be that the entry doesn't exist, in which case we should 399 // throw an exception. 400 if (entryMap.containsKey(entryDN)) 401 { 402 return ConditionResult.FALSE; 403 } 404 else 405 { 406 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 407 ERR_LDIF_BACKEND_HAS_SUBORDINATES_NO_SUCH_ENTRY.get(entryDN)); 408 } 409 } 410 else 411 { 412 return ConditionResult.TRUE; 413 } 414 } 415 finally 416 { 417 backendLock.readLock().unlock(); 418 } 419 } 420 421 @Override 422 public long getNumberOfChildren(DN parentDN) throws DirectoryException 423 { 424 checkNotNull(parentDN, "parentDN must not be null"); 425 return getNumberOfSubordinates(parentDN, false); 426 } 427 428 @Override 429 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 430 { 431 checkNotNull(baseDN, "baseDN must not be null"); 432 if (!baseDNs.contains(baseDN)) 433 { 434 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 435 ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY.get(baseDN)); 436 } 437 final int baseDNIfExists = childDNs.containsKey(baseDN) ? 1 : 0; 438 return getNumberOfSubordinates(baseDN, true) + baseDNIfExists; 439 } 440 441 private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException 442 { 443 backendLock.readLock().lock(); 444 445 try 446 { 447 Set<DN> childDNSet = childDNs.get(entryDN); 448 if (childDNSet == null || childDNSet.isEmpty()) 449 { 450 // It could be that the entry doesn't exist, in which case we should 451 // throw an exception. 452 if (entryMap.containsKey(entryDN)) 453 { 454 return 0L; 455 } 456 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY 457 .get(entryDN)); 458 } 459 460 if (!includeSubtree) 461 { 462 return childDNSet.size(); 463 } 464 465 long count = 0; 466 for (DN childDN : childDNSet) 467 { 468 count += getNumberOfSubordinates(childDN, true); 469 count++; 470 } 471 return count; 472 } 473 finally 474 { 475 backendLock.readLock().unlock(); 476 } 477 } 478 479 @Override 480 public Entry getEntry(DN entryDN) 481 { 482 backendLock.readLock().lock(); 483 484 try 485 { 486 return entryMap.get(entryDN); 487 } 488 finally 489 { 490 backendLock.readLock().unlock(); 491 } 492 } 493 494 @Override 495 public boolean entryExists(DN entryDN) 496 { 497 backendLock.readLock().lock(); 498 499 try 500 { 501 return entryMap.containsKey(entryDN); 502 } 503 finally 504 { 505 backendLock.readLock().unlock(); 506 } 507 } 508 509 @Override 510 public void addEntry(Entry entry, AddOperation addOperation) 511 throws DirectoryException 512 { 513 backendLock.writeLock().lock(); 514 515 try 516 { 517 // Make sure that the target entry does not already exist, but that its 518 // parent does exist (or that the entry being added is the base DN). 519 DN entryDN = entry.getName(); 520 if (entryMap.containsKey(entryDN)) 521 { 522 LocalizableMessage m = ERR_LDIF_BACKEND_ADD_ALREADY_EXISTS.get(entryDN); 523 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m); 524 } 525 526 if (baseDNs.contains(entryDN)) 527 { 528 entryMap.put(entryDN, entry.duplicate(false)); 529 writeLDIF(); 530 return; 531 } 532 else 533 { 534 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 535 if (parentDN != null && entryMap.containsKey(parentDN)) 536 { 537 entryMap.put(entryDN, entry.duplicate(false)); 538 539 Set<DN> childDNSet = childDNs.get(parentDN); 540 if (childDNSet == null) 541 { 542 childDNSet = new HashSet<>(); 543 childDNs.put(parentDN, childDNSet); 544 } 545 childDNSet.add(entryDN); 546 writeLDIF(); 547 return; 548 } 549 else 550 { 551 LocalizableMessage m = ERR_LDIF_BACKEND_ADD_MISSING_PARENT.get(entryDN); 552 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, findMatchedDN(parentDN), null); 553 } 554 } 555 } 556 finally 557 { 558 backendLock.writeLock().unlock(); 559 } 560 } 561 562 private DN findMatchedDN(DN parentDN) 563 { 564 if (parentDN != null) 565 { 566 while (true) 567 { 568 parentDN = DirectoryServer.getParentDNInSuffix(parentDN); 569 if (parentDN == null) 570 { 571 return null; 572 } 573 else if (entryMap.containsKey(parentDN)) 574 { 575 return parentDN; 576 } 577 } 578 } 579 return null; 580 } 581 582 @Override 583 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 584 throws DirectoryException 585 { 586 backendLock.writeLock().lock(); 587 588 try 589 { 590 // Get the DN of the target entry's parent, if it exists. We'll need to 591 // also remove the reference to the target entry from the parent's set of 592 // children. 593 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 594 595 // Make sure that the target entry exists. If not, then fail. 596 if (! entryMap.containsKey(entryDN)) 597 { 598 DN matchedDN = null; 599 while (parentDN != null) 600 { 601 if (entryMap.containsKey(parentDN)) 602 { 603 matchedDN = parentDN; 604 break; 605 } 606 607 parentDN = DirectoryServer.getParentDNInSuffix(parentDN); 608 } 609 610 LocalizableMessage m = ERR_LDIF_BACKEND_DELETE_NO_SUCH_ENTRY.get(entryDN); 611 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null); 612 } 613 614 // See if the target entry has any children. If so, then we'll only 615 // delete it if the request contains the subtree delete control (in 616 // which case we'll delete the entire subtree). 617 Set<DN> childDNSet = childDNs.get(entryDN); 618 if (childDNSet == null || childDNSet.isEmpty()) 619 { 620 entryMap.remove(entryDN); 621 childDNs.remove(entryDN); 622 623 if (parentDN != null) 624 { 625 Set<DN> parentChildren = childDNs.get(parentDN); 626 if (parentChildren != null) 627 { 628 parentChildren.remove(entryDN); 629 if (parentChildren.isEmpty()) 630 { 631 childDNs.remove(parentDN); 632 } 633 } 634 } 635 } 636 else 637 { 638 boolean subtreeDelete = deleteOperation != null 639 && deleteOperation 640 .getRequestControl(SubtreeDeleteControl.DECODER) != null; 641 642 if (! subtreeDelete) 643 { 644 LocalizableMessage m = ERR_LDIF_BACKEND_DELETE_NONLEAF.get(entryDN); 645 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, m); 646 } 647 648 entryMap.remove(entryDN); 649 childDNs.remove(entryDN); 650 651 if (parentDN != null) 652 { 653 Set<DN> parentChildren = childDNs.get(parentDN); 654 if (parentChildren != null) 655 { 656 parentChildren.remove(entryDN); 657 if (parentChildren.isEmpty()) 658 { 659 childDNs.remove(parentDN); 660 } 661 } 662 } 663 664 for (DN childDN : childDNSet) 665 { 666 subtreeDelete(childDN); 667 } 668 } 669 670 writeLDIF(); 671 } 672 finally 673 { 674 backendLock.writeLock().unlock(); 675 } 676 } 677 678 /** 679 * Removes the specified entry and any subordinates that it may have from 680 * the backend. This method assumes that the caller holds the backend write 681 * lock. 682 * 683 * @param entryDN The DN of the entry to remove, along with all of its 684 * subordinate entries. 685 */ 686 private void subtreeDelete(DN entryDN) 687 { 688 entryMap.remove(entryDN); 689 Set<DN> childDNSet = childDNs.remove(entryDN); 690 if (childDNSet != null) 691 { 692 for (DN childDN : childDNSet) 693 { 694 subtreeDelete(childDN); 695 } 696 } 697 } 698 699 @Override 700 public void replaceEntry(Entry oldEntry, Entry newEntry, 701 ModifyOperation modifyOperation) throws DirectoryException 702 { 703 backendLock.writeLock().lock(); 704 705 try 706 { 707 // Make sure that the target entry exists. If not, then fail. 708 DN entryDN = newEntry.getName(); 709 if (! entryMap.containsKey(entryDN)) 710 { 711 DN matchedDN = null; 712 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 713 while (parentDN != null) 714 { 715 if (entryMap.containsKey(parentDN)) 716 { 717 matchedDN = parentDN; 718 break; 719 } 720 721 parentDN = DirectoryServer.getParentDNInSuffix(parentDN); 722 } 723 724 LocalizableMessage m = ERR_LDIF_BACKEND_MODIFY_NO_SUCH_ENTRY.get(entryDN); 725 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null); 726 } 727 728 entryMap.put(entryDN, newEntry.duplicate(false)); 729 writeLDIF(); 730 return; 731 } 732 finally 733 { 734 backendLock.writeLock().unlock(); 735 } 736 } 737 738 @Override 739 public void renameEntry(DN currentDN, Entry entry, 740 ModifyDNOperation modifyDNOperation) 741 throws DirectoryException 742 { 743 backendLock.writeLock().lock(); 744 745 try 746 { 747 // Make sure that the original entry exists and that the new entry doesn't 748 // exist but its parent does. 749 DN newDN = entry.getName(); 750 if (! entryMap.containsKey(currentDN)) 751 { 752 DN matchedDN = null; 753 DN parentDN = DirectoryServer.getParentDNInSuffix(currentDN); 754 while (parentDN != null) 755 { 756 if (entryMap.containsKey(parentDN)) 757 { 758 matchedDN = parentDN; 759 break; 760 } 761 762 parentDN = DirectoryServer.getParentDNInSuffix(parentDN); 763 } 764 765 LocalizableMessage m = ERR_LDIF_BACKEND_MODDN_NO_SUCH_SOURCE_ENTRY.get(currentDN); 766 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null); 767 } 768 769 if (entryMap.containsKey(newDN)) 770 { 771 LocalizableMessage m = ERR_LDIF_BACKEND_MODDN_TARGET_ENTRY_ALREADY_EXISTS.get(newDN); 772 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m); 773 } 774 775 DN newParentDN = DirectoryServer.getParentDNInSuffix(newDN); 776 if (! entryMap.containsKey(newParentDN)) 777 { 778 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 779 ERR_LDIF_BACKEND_MODDN_NEW_PARENT_DOESNT_EXIST.get(newParentDN)); 780 } 781 782 // Remove the entry from the list of children for the old parent and 783 // add the new entry DN to the set of children for the new parent. 784 DN oldParentDN = DirectoryServer.getParentDNInSuffix(currentDN); 785 Set<DN> parentChildDNs = childDNs.get(oldParentDN); 786 if (parentChildDNs != null) 787 { 788 parentChildDNs.remove(currentDN); 789 if (parentChildDNs.isEmpty() 790 && modifyDNOperation.getNewSuperior() != null) 791 { 792 childDNs.remove(oldParentDN); 793 } 794 } 795 796 parentChildDNs = childDNs.get(newParentDN); 797 if (parentChildDNs == null) 798 { 799 parentChildDNs = new HashSet<>(); 800 childDNs.put(newParentDN, parentChildDNs); 801 } 802 parentChildDNs.add(newDN); 803 804 // If the entry has children, then we'll need to work on the whole 805 // subtree. Otherwise, just work on the target entry. 806 Set<DN> childDNSet = childDNs.remove(currentDN); 807 entryMap.remove(currentDN); 808 entryMap.put(newDN, entry.duplicate(false)); 809 if (childDNSet != null && !childDNSet.isEmpty()) 810 { 811 for (DN childDN : childDNSet) 812 { 813 subtreeRename(childDN, newDN); 814 } 815 } 816 writeLDIF(); 817 } 818 finally 819 { 820 backendLock.writeLock().unlock(); 821 } 822 } 823 824 /** 825 * Moves the specified entry and all of its children so that they are 826 * appropriately placed below the given new parent DN. This method assumes 827 * that the caller holds the backend write lock. 828 * 829 * @param entryDN The DN of the entry to move/rename. 830 * @param newParentDN The DN of the new parent under which the entry should 831 * be placed. 832 */ 833 private void subtreeRename(DN entryDN, DN newParentDN) 834 { 835 Set<DN> childDNSet = childDNs.remove(entryDN); 836 DN newEntryDN = newParentDN.child(entryDN.rdn()); 837 838 Entry oldEntry = entryMap.remove(entryDN); 839 if (oldEntry == null) 840 { 841 // This should never happen. 842 if (logger.isTraceEnabled()) 843 { 844 logger.trace("Subtree rename encountered entry DN " + 845 entryDN + " for nonexistent entry."); 846 } 847 return; 848 } 849 850 Entry newEntry = oldEntry.duplicate(false); 851 newEntry.setDN(newEntryDN); 852 entryMap.put(newEntryDN, newEntry); 853 854 Set<DN> parentChildren = childDNs.get(newParentDN); 855 if (parentChildren == null) 856 { 857 parentChildren = new HashSet<>(); 858 childDNs.put(newParentDN, parentChildren); 859 } 860 parentChildren.add(newEntryDN); 861 862 if (childDNSet != null) 863 { 864 for (DN childDN : childDNSet) 865 { 866 subtreeRename(childDN, newEntryDN); 867 } 868 } 869 } 870 871 @Override 872 public void search(SearchOperation searchOperation) 873 throws DirectoryException 874 { 875 backendLock.readLock().lock(); 876 877 try 878 { 879 // Get the base DN, scope, and filter for the search. 880 DN baseDN = searchOperation.getBaseDN(); 881 SearchScope scope = searchOperation.getScope(); 882 SearchFilter filter = searchOperation.getFilter(); 883 884 // Make sure the base entry exists if it's supposed to be in this backend. 885 Entry baseEntry = entryMap.get(baseDN); 886 if (baseEntry == null && handlesEntry(baseDN)) 887 { 888 DN matchedDN = DirectoryServer.getParentDNInSuffix(baseDN); 889 while (matchedDN != null) 890 { 891 if (entryMap.containsKey(matchedDN)) 892 { 893 break; 894 } 895 896 matchedDN = DirectoryServer.getParentDNInSuffix(matchedDN); 897 } 898 899 LocalizableMessage m = ERR_LDIF_BACKEND_SEARCH_NO_SUCH_BASE.get(baseDN); 900 throw new DirectoryException( 901 ResultCode.NO_SUCH_OBJECT, m, matchedDN, null); 902 } 903 904 if (baseEntry != null) 905 { 906 baseEntry = baseEntry.duplicate(true); 907 } 908 909 // If it's a base-level search, then just get that entry and return it if 910 // it matches the filter. 911 if (scope == SearchScope.BASE_OBJECT) 912 { 913 if (filter.matchesEntry(baseEntry)) 914 { 915 searchOperation.returnEntry(baseEntry, new LinkedList<Control>()); 916 } 917 } 918 else 919 { 920 // Walk through all entries and send the ones that match. 921 for (Entry e : entryMap.values()) 922 { 923 e = e.duplicate(true); 924 if (e.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(e)) 925 { 926 searchOperation.returnEntry(e, new LinkedList<Control>()); 927 } 928 } 929 } 930 } 931 finally 932 { 933 backendLock.readLock().unlock(); 934 } 935 } 936 937 @Override 938 public Set<String> getSupportedControls() 939 { 940 return supportedControls; 941 } 942 943 @Override 944 public Set<String> getSupportedFeatures() 945 { 946 return Collections.emptySet(); 947 } 948 949 @Override 950 public boolean supports(BackendOperation backendOperation) 951 { 952 switch (backendOperation) 953 { 954 case LDIF_EXPORT: 955 case LDIF_IMPORT: 956 return true; 957 958 default: 959 return false; 960 } 961 } 962 963 @Override 964 public void exportLDIF(LDIFExportConfig exportConfig) 965 throws DirectoryException 966 { 967 backendLock.readLock().lock(); 968 969 try (LDIFWriter ldifWriter = newLDIFWriter(exportConfig)) 970 { 971 // Walk through all the entries and write them to LDIF. 972 for (Entry entry : entryMap.values()) 973 { 974 DN entryDN = entry.getName(); 975 try 976 { 977 ldifWriter.writeEntry(entry); 978 } 979 catch (Exception e) 980 { 981 LocalizableMessage m = 982 ERR_LDIF_BACKEND_CANNOT_WRITE_ENTRY_TO_LDIF.get(entryDN, stackTraceToSingleLineString(e)); 983 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, e); 984 } 985 } 986 } 987 catch (IOException ignoreOnClose) 988 { 989 logger.traceException(ignoreOnClose); 990 } 991 finally 992 { 993 backendLock.readLock().unlock(); 994 } 995 } 996 997 private LDIFWriter newLDIFWriter(LDIFExportConfig exportConfig) throws DirectoryException 998 { 999 try 1000 { 1001 return new LDIFWriter(exportConfig); 1002 } 1003 catch (Exception e) 1004 { 1005 logger.traceException(e); 1006 LocalizableMessage m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_WRITER.get(stackTraceToSingleLineString(e)); 1007 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, e); 1008 } 1009 } 1010 1011 @Override 1012 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 1013 throws DirectoryException 1014 { 1015 return importLDIF(importConfig, true); 1016 } 1017 1018 /** 1019 * Processes an LDIF import operation, optionally writing the resulting LDIF 1020 * to disk. 1021 * 1022 * @param importConfig The LDIF import configuration. 1023 * @param writeLDIF Indicates whether the LDIF backing file for this 1024 * backend should be updated when the import is 1025 * complete. This should only be {@code false} when 1026 * reading the LDIF as the backend is coming online. 1027 */ 1028 private LDIFImportResult importLDIF(LDIFImportConfig importConfig, 1029 boolean writeLDIF) 1030 throws DirectoryException 1031 { 1032 backendLock.writeLock().lock(); 1033 1034 try (LDIFReader reader = newLDIFReader(importConfig)) 1035 { 1036 entryMap.clear(); 1037 childDNs.clear(); 1038 1039 try 1040 { 1041 while (true) 1042 { 1043 Entry e = null; 1044 try 1045 { 1046 e = reader.readEntry(); 1047 if (e == null) 1048 { 1049 break; 1050 } 1051 } 1052 catch (LDIFException le) 1053 { 1054 if (! le.canContinueReading()) 1055 { 1056 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get( 1057 stackTraceToSingleLineString(le)); 1058 throw new DirectoryException( 1059 DirectoryServer.getServerErrorResultCode(), m, le); 1060 } 1061 continue; 1062 } 1063 1064 // Make sure that we don't already have an entry with the same DN. If 1065 // a duplicate is encountered, then log a message and continue. 1066 DN entryDN = e.getName(); 1067 if (entryMap.containsKey(entryDN)) 1068 { 1069 LocalizableMessage m = 1070 ERR_LDIF_BACKEND_DUPLICATE_ENTRY.get(ldifFilePath, currentConfig.dn(), entryDN); 1071 logger.error(m); 1072 reader.rejectLastEntry(m); 1073 continue; 1074 } 1075 1076 // If the entry DN is a base DN, then add it with no more processing. 1077 if (baseDNs.contains(entryDN)) 1078 { 1079 entryMap.put(entryDN, e); 1080 continue; 1081 } 1082 1083 // Make sure that the parent exists. If not, then reject the entry. 1084 if (!isBelowBaseDN(entryDN)) 1085 { 1086 LocalizableMessage m = ERR_LDIF_BACKEND_ENTRY_OUT_OF_SCOPE.get( 1087 ldifFilePath, currentConfig.dn(), entryDN); 1088 logger.error(m); 1089 reader.rejectLastEntry(m); 1090 continue; 1091 } 1092 1093 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 1094 if (parentDN == null || !entryMap.containsKey(parentDN)) 1095 { 1096 LocalizableMessage m = ERR_LDIF_BACKEND_MISSING_PARENT.get( 1097 ldifFilePath, currentConfig.dn(), entryDN); 1098 logger.error(m); 1099 reader.rejectLastEntry(m); 1100 continue; 1101 } 1102 1103 // The entry does not exist but its parent does, so add it and update 1104 // the set of children for the parent. 1105 entryMap.put(entryDN, e); 1106 1107 Set<DN> childDNSet = childDNs.get(parentDN); 1108 if (childDNSet == null) 1109 { 1110 childDNSet = new HashSet<>(); 1111 childDNs.put(parentDN, childDNSet); 1112 } 1113 1114 childDNSet.add(entryDN); 1115 } 1116 1117 if (writeLDIF) 1118 { 1119 writeLDIF(); 1120 } 1121 1122 return new LDIFImportResult(reader.getEntriesRead(), 1123 reader.getEntriesRejected(), 1124 reader.getEntriesIgnored()); 1125 } 1126 catch (DirectoryException de) 1127 { 1128 throw de; 1129 } 1130 catch (Exception e) 1131 { 1132 LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(stackTraceToSingleLineString(e)); 1133 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, e); 1134 } 1135 } 1136 finally 1137 { 1138 backendLock.writeLock().unlock(); 1139 } 1140 } 1141 1142 private boolean isBelowBaseDN(DN entryDN) 1143 { 1144 for (DN baseDN : baseDNs) 1145 { 1146 if (baseDN.isSuperiorOrEqualTo(entryDN)) 1147 { 1148 return true; 1149 } 1150 } 1151 return false; 1152 } 1153 1154 private LDIFReader newLDIFReader(LDIFImportConfig importConfig) throws DirectoryException 1155 { 1156 try 1157 { 1158 return new LDIFReader(importConfig); 1159 } 1160 catch (Exception e) 1161 { 1162 LocalizableMessage m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_READER.get(stackTraceToSingleLineString(e)); 1163 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, e); 1164 } 1165 } 1166 1167 @Override 1168 public void createBackup(BackupConfig backupConfig) 1169 throws DirectoryException 1170 { 1171 LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 1172 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1173 } 1174 1175 @Override 1176 public void removeBackup(BackupDirectory backupDirectory, String backupID) 1177 throws DirectoryException 1178 { 1179 LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 1180 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1181 } 1182 1183 @Override 1184 public void restoreBackup(RestoreConfig restoreConfig) 1185 throws DirectoryException 1186 { 1187 LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 1188 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1189 } 1190 1191 @Override 1192 public void configureBackend(LDIFBackendCfg config, ServerContext serverContext) throws ConfigException 1193 { 1194 if (config != null) 1195 { 1196 currentConfig = config; 1197 currentConfig.addLDIFChangeListener(this); 1198 1199 baseDNs = currentConfig.getBaseDN(); 1200 if (baseDNs.size() != 1) 1201 { 1202 throw new ConfigException(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(currentConfig.dn())); 1203 } 1204 1205 ldifFilePath = currentConfig.getLDIFFile(); 1206 } 1207 } 1208 1209 @Override 1210 public boolean isConfigurationChangeAcceptable(LDIFBackendCfg configuration, 1211 List<LocalizableMessage> unacceptableReasons) 1212 { 1213 boolean configAcceptable = true; 1214 1215 // Make sure that there is only a single base DN. 1216 if (configuration.getBaseDN().size() != 1) 1217 { 1218 unacceptableReasons.add(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(configuration.dn())); 1219 configAcceptable = false; 1220 } 1221 1222 return configAcceptable; 1223 } 1224 1225 @Override 1226 public ConfigChangeResult applyConfigurationChange( 1227 LDIFBackendCfg configuration) 1228 { 1229 // We don't actually need to do anything in response to this. However, if 1230 // the base DNs or LDIF file are different from what we're currently using 1231 // then indicate that admin action is required. 1232 final ConfigChangeResult ccr = new ConfigChangeResult(); 1233 1234 if (ldifFilePath != null) 1235 { 1236 File currentLDIF = getFileForPath(ldifFilePath); 1237 File newLDIF = getFileForPath(configuration.getLDIFFile()); 1238 if (! currentLDIF.equals(newLDIF)) 1239 { 1240 ccr.addMessage(INFO_LDIF_BACKEND_LDIF_FILE_CHANGED.get()); 1241 ccr.setAdminActionRequired(true); 1242 } 1243 } 1244 1245 if (baseDNs != null && !baseDNs.equals(configuration.getBaseDN())) 1246 { 1247 ccr.addMessage(INFO_LDIF_BACKEND_BASE_DN_CHANGED.get()); 1248 ccr.setAdminActionRequired(true); 1249 } 1250 1251 currentConfig = configuration; 1252 return ccr; 1253 } 1254 1255 @Override 1256 public DN getComponentEntryDN() 1257 { 1258 return currentConfig.dn(); 1259 } 1260 1261 @Override 1262 public String getClassName() 1263 { 1264 return LDIFBackend.class.getName(); 1265 } 1266 1267 @Override 1268 public Map<String,String> getAlerts() 1269 { 1270 Map<String,String> alerts = new LinkedHashMap<>(); 1271 alerts.put(ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, 1272 ALERT_DESCRIPTION_LDIF_BACKEND_CANNOT_WRITE_UPDATE); 1273 return alerts; 1274 } 1275}