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