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