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