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 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-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.util.Collections; 035import java.util.HashMap; 036import java.util.HashSet; 037import java.util.LinkedHashMap; 038import java.util.LinkedList; 039import java.util.Set; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.slf4j.LocalizedLogger; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.forgerock.opendj.ldap.ConditionResult; 045import org.forgerock.opendj.ldap.ResultCode; 046import org.forgerock.opendj.ldap.SearchScope; 047import org.opends.server.admin.std.server.MemoryBackendCfg; 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.AttributeType; 058import org.opends.server.types.BackupConfig; 059import org.opends.server.types.BackupDirectory; 060import org.opends.server.types.Control; 061import org.opends.server.types.DN; 062import org.opends.server.types.DirectoryException; 063import org.opends.server.types.Entry; 064import org.opends.server.types.IndexType; 065import org.opends.server.types.InitializationException; 066import org.opends.server.types.LDIFExportConfig; 067import org.opends.server.types.LDIFImportConfig; 068import org.opends.server.types.LDIFImportResult; 069import org.opends.server.types.RestoreConfig; 070import org.opends.server.types.SearchFilter; 071import org.opends.server.util.LDIFException; 072import org.opends.server.util.LDIFReader; 073import org.opends.server.util.LDIFWriter; 074 075/** 076 * This class defines a very simple backend that stores its information in 077 * memory. This is primarily intended for testing purposes with small data 078 * sets, as it does not have any indexing mechanism such as would be required to 079 * achieve high performance with large data sets. It is also heavily 080 * synchronized for simplicity at the expense of performance, rather than 081 * providing a more fine-grained locking mechanism. 082 * <BR><BR> 083 * Entries stored in this backend are held in a 084 * <CODE>LinkedHashMap<DN,Entry></CODE> object, which ensures that the 085 * order in which you iterate over the entries is the same as the order in which 086 * they were inserted. By combining this with the constraint that no entry can 087 * be added before its parent, you can ensure that iterating through the entries 088 * will always process the parent entries before their children, which is 089 * important for both search result processing and LDIF exports. 090 * <BR><BR> 091 * As mentioned above, no data indexing is performed, so all non-baseObject 092 * searches require iteration through the entire data set. If this is to become 093 * a more general-purpose backend, then additional 094 * <CODE>HashMap<ByteString,Set<DN>></CODE> objects could be used 095 * to provide that capability. 096 * <BR><BR> 097 * There is actually one index that does get maintained within this backend, 098 * which is a mapping between the DN of an entry and the DNs of any immediate 099 * children of that entry. This is needed to efficiently determine whether an 100 * entry has any children (which must not be the case for delete operations). 101 */ 102public class MemoryBackend 103 extends Backend<MemoryBackendCfg> 104{ 105 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 106 107 108 109 /** The base DNs for this backend. */ 110 private DN[] baseDNs; 111 112 /** The mapping between parent DNs and their immediate children. */ 113 private HashMap<DN,HashSet<DN>> childDNs; 114 115 /** The base DNs for this backend, in a hash set. */ 116 private HashSet<DN> baseDNSet; 117 118 /** The set of supported controls for this backend. */ 119 private final Set<String> supportedControls = 120 Collections.singleton(OID_SUBTREE_DELETE_CONTROL); 121 122 /** The mapping between entry DNs and the corresponding entries. */ 123 private LinkedHashMap<DN,Entry> entryMap; 124 125 126 127 /** 128 * Creates a new backend with the provided information. All backend 129 * implementations must implement a default constructor that use 130 * <CODE>super()</CODE> to invoke this constructor. 131 */ 132 public MemoryBackend() 133 { 134 super(); 135 136 // Perform all initialization in initializeBackend. 137 } 138 139 140 /** 141 * Set the base DNs for this backend. This is used by the unit tests 142 * to set the base DNs without having to provide a configuration 143 * object when initializing the backend. 144 * @param baseDNs The set of base DNs to be served by this memory backend. 145 */ 146 public void setBaseDNs(DN[] baseDNs) 147 { 148 this.baseDNs = baseDNs; 149 } 150 151 /** {@inheritDoc} */ 152 @Override 153 public void configureBackend(MemoryBackendCfg config, ServerContext serverContext) throws ConfigException 154 { 155 if (config != null) 156 { 157 MemoryBackendCfg cfg = config; 158 DN[] baseDNs = new DN[cfg.getBaseDN().size()]; 159 cfg.getBaseDN().toArray(baseDNs); 160 setBaseDNs(baseDNs); 161 } 162 } 163 164 /** {@inheritDoc} */ 165 @Override 166 public synchronized void openBackend() 167 throws ConfigException, InitializationException 168 { 169 // We won't support anything other than exactly one base DN in this 170 // implementation. If we were to add such support in the future, we would 171 // likely want to separate the data for each base DN into a separate entry 172 // map. 173 if (baseDNs == null || baseDNs.length != 1) 174 { 175 LocalizableMessage message = ERR_MEMORYBACKEND_REQUIRE_EXACTLY_ONE_BASE.get(); 176 throw new ConfigException(message); 177 } 178 179 baseDNSet = new HashSet<>(); 180 Collections.addAll(baseDNSet, baseDNs); 181 182 entryMap = new LinkedHashMap<>(); 183 childDNs = new HashMap<>(); 184 185 for (DN dn : baseDNs) 186 { 187 try 188 { 189 DirectoryServer.registerBaseDN(dn, this, false); 190 } 191 catch (Exception e) 192 { 193 logger.traceException(e); 194 195 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 196 dn, getExceptionMessage(e)); 197 throw new InitializationException(message, e); 198 } 199 } 200 } 201 202 203 204 /** 205 * Removes any data that may have been stored in this backend. 206 */ 207 public synchronized void clearMemoryBackend() 208 { 209 entryMap.clear(); 210 childDNs.clear(); 211 } 212 213 /** {@inheritDoc} */ 214 @Override 215 public synchronized void closeBackend() 216 { 217 clearMemoryBackend(); 218 219 for (DN dn : baseDNs) 220 { 221 try 222 { 223 DirectoryServer.deregisterBaseDN(dn); 224 } 225 catch (Exception e) 226 { 227 logger.traceException(e); 228 } 229 } 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public DN[] getBaseDNs() 235 { 236 return baseDNs; 237 } 238 239 /** {@inheritDoc} */ 240 @Override 241 public synchronized long getEntryCount() 242 { 243 if (entryMap != null) 244 { 245 return entryMap.size(); 246 } 247 248 return -1; 249 } 250 251 /** {@inheritDoc} */ 252 @Override 253 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 254 { 255 // All searches in this backend will always be considered indexed. 256 return true; 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public synchronized ConditionResult hasSubordinates(DN entryDN) 262 throws DirectoryException 263 { 264 long ret = getNumberOfSubordinates(entryDN, false); 265 if(ret < 0) 266 { 267 return ConditionResult.UNDEFINED; 268 } 269 return ConditionResult.valueOf(ret != 0); 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException { 275 checkNotNull(baseDN, "baseDN must not be null"); 276 return getNumberOfSubordinates(baseDN, true) + 1; 277 } 278 279 /** {@inheritDoc} */ 280 @Override 281 public long getNumberOfChildren(DN parentDN) throws DirectoryException { 282 checkNotNull(parentDN, "parentDN must not be null"); 283 return getNumberOfSubordinates(parentDN, false); 284 } 285 286 private synchronized long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException 287 { 288 // Try to look up the immediate children for the DN 289 final Set<DN> children = childDNs.get(entryDN); 290 if (children == null) 291 { 292 if(entryMap.get(entryDN) != null) 293 { 294 // The entry does exist but just no children. 295 return 0; 296 } 297 return -1; 298 } 299 300 if(!includeSubtree) 301 { 302 return children.size(); 303 } 304 long count = 0; 305 for (DN child : children) 306 { 307 count += getNumberOfSubordinates(child, true); 308 count++; 309 } 310 return count; 311 } 312 313 /** {@inheritDoc} */ 314 @Override 315 public synchronized Entry getEntry(DN entryDN) 316 { 317 Entry entry = entryMap.get(entryDN); 318 if (entry != null) 319 { 320 entry = entry.duplicate(true); 321 } 322 323 return entry; 324 } 325 326 /** {@inheritDoc} */ 327 @Override 328 public synchronized boolean entryExists(DN entryDN) 329 { 330 return entryMap.containsKey(entryDN); 331 } 332 333 /** {@inheritDoc} */ 334 @Override 335 public synchronized void addEntry(Entry entry, AddOperation addOperation) 336 throws DirectoryException 337 { 338 Entry e = entry.duplicate(false); 339 340 // See if the target entry already exists. If so, then fail. 341 DN entryDN = e.getName(); 342 if (entryMap.containsKey(entryDN)) 343 { 344 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, 345 ERR_MEMORYBACKEND_ENTRY_ALREADY_EXISTS.get(entryDN)); 346 } 347 348 349 // If the entry is one of the base DNs, then add it. 350 if (baseDNSet.contains(entryDN)) 351 { 352 entryMap.put(entryDN, e); 353 return; 354 } 355 356 357 // Get the parent DN and ensure that it exists in the backend. 358 DN parentDN = entryDN.getParentDNInSuffix(); 359 if (parentDN == null) 360 { 361 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 362 ERR_MEMORYBACKEND_ENTRY_DOESNT_BELONG.get(entryDN)); 363 } 364 else if (! entryMap.containsKey(parentDN)) 365 { 366 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 367 ERR_MEMORYBACKEND_PARENT_DOESNT_EXIST.get(entryDN, parentDN)); 368 } 369 370 entryMap.put(entryDN, e); 371 HashSet<DN> children = childDNs.get(parentDN); 372 if (children == null) 373 { 374 children = new HashSet<>(); 375 childDNs.put(parentDN, children); 376 } 377 378 children.add(entryDN); 379 } 380 381 /** {@inheritDoc} */ 382 @Override 383 public synchronized void deleteEntry(DN entryDN, 384 DeleteOperation deleteOperation) 385 throws DirectoryException 386 { 387 // Make sure the entry exists. If not, then throw an exception. 388 if (! entryMap.containsKey(entryDN)) 389 { 390 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 391 ERR_BACKEND_ENTRY_DOESNT_EXIST.get(entryDN, getBackendID())); 392 } 393 394 395 // Check to see if the entry contains a subtree delete control. 396 boolean subtreeDelete = deleteOperation != null 397 && deleteOperation.getRequestControl(SubtreeDeleteControl.DECODER) != null; 398 399 HashSet<DN> children = childDNs.get(entryDN); 400 if (subtreeDelete) 401 { 402 if (children != null) 403 { 404 HashSet<DN> childrenCopy = new HashSet<>(children); 405 for (DN childDN : childrenCopy) 406 { 407 try 408 { 409 deleteEntry(childDN, null); 410 } 411 catch (Exception e) 412 { 413 // This shouldn't happen, but we want the delete to continue anyway 414 // so just ignore it if it does for some reason. 415 logger.traceException(e); 416 } 417 } 418 } 419 } 420 else 421 { 422 // Make sure the entry doesn't have any children. If it does, then throw 423 // an exception. 424 if (children != null && !children.isEmpty()) 425 { 426 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, 427 ERR_MEMORYBACKEND_CANNOT_DELETE_ENTRY_WITH_CHILDREN.get(entryDN)); 428 } 429 } 430 431 432 // Remove the entry from the backend. Also remove the reference to it from 433 // its parent, if applicable. 434 childDNs.remove(entryDN); 435 entryMap.remove(entryDN); 436 437 DN parentDN = entryDN.getParentDNInSuffix(); 438 if (parentDN != null) 439 { 440 HashSet<DN> parentsChildren = childDNs.get(parentDN); 441 if (parentsChildren != null) 442 { 443 parentsChildren.remove(entryDN); 444 if (parentsChildren.isEmpty()) 445 { 446 childDNs.remove(parentDN); 447 } 448 } 449 } 450 } 451 452 /** {@inheritDoc} */ 453 @Override 454 public synchronized void replaceEntry(Entry oldEntry, Entry newEntry, 455 ModifyOperation modifyOperation) throws DirectoryException 456 { 457 Entry e = newEntry.duplicate(false); 458 459 // Make sure the entry exists. If not, then throw an exception. 460 DN entryDN = e.getName(); 461 if (! entryMap.containsKey(entryDN)) 462 { 463 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 464 ERR_BACKEND_ENTRY_DOESNT_EXIST.get(entryDN, getBackendID())); 465 } 466 467 468 // Replace the old entry with the new one. 469 entryMap.put(entryDN, e); 470 } 471 472 /** {@inheritDoc} */ 473 @Override 474 public synchronized void renameEntry(DN currentDN, Entry entry, 475 ModifyDNOperation modifyDNOperation) 476 throws DirectoryException 477 { 478 Entry e = entry.duplicate(false); 479 480 // Make sure that the target entry exists. 481 if (! entryMap.containsKey(currentDN)) 482 { 483 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 484 ERR_BACKEND_ENTRY_DOESNT_EXIST.get(currentDN, getBackendID())); 485 } 486 487 488 // Make sure that the target entry doesn't have any children. 489 HashSet<DN> children = childDNs.get(currentDN); 490 if (children != null) 491 { 492 if (children.isEmpty()) 493 { 494 childDNs.remove(currentDN); 495 } 496 else 497 { 498 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, 499 ERR_MEMORYBACKEND_CANNOT_RENAME_ENRY_WITH_CHILDREN.get(currentDN)); 500 } 501 } 502 503 504 // Make sure that no entry exists with the new DN. 505 if (entryMap.containsKey(e.getName())) 506 { 507 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, 508 ERR_MEMORYBACKEND_ENTRY_ALREADY_EXISTS.get(e.getName())); 509 } 510 511 512 // Make sure that the new DN is in this backend. 513 boolean matchFound = false; 514 for (DN dn : baseDNs) 515 { 516 if (dn.isAncestorOf(e.getName())) 517 { 518 matchFound = true; 519 break; 520 } 521 } 522 523 if (! matchFound) 524 { 525 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 526 ERR_MEMORYBACKEND_CANNOT_RENAME_TO_ANOTHER_BACKEND.get(currentDN)); 527 } 528 529 530 // Make sure that the parent of the new entry exists. 531 DN parentDN = e.getName().getParentDNInSuffix(); 532 if (parentDN == null || !entryMap.containsKey(parentDN)) 533 { 534 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 535 ERR_MEMORYBACKEND_RENAME_PARENT_DOESNT_EXIST.get(currentDN, parentDN)); 536 } 537 538 539 // Delete the current entry and add the new one. 540 deleteEntry(currentDN, null); 541 addEntry(e, null); 542 } 543 544 /** {@inheritDoc} */ 545 @Override 546 public synchronized void search(SearchOperation searchOperation) 547 throws DirectoryException 548 { 549 // Get the base DN, scope, and filter for the search. 550 DN baseDN = searchOperation.getBaseDN(); 551 SearchScope scope = searchOperation.getScope(); 552 SearchFilter filter = searchOperation.getFilter(); 553 554 555 // Make sure the base entry exists if it's supposed to be in this backend. 556 Entry baseEntry = entryMap.get(baseDN); 557 if (baseEntry == null && handlesEntry(baseDN)) 558 { 559 DN matchedDN = baseDN.getParentDNInSuffix(); 560 while (matchedDN != null) 561 { 562 if (entryMap.containsKey(matchedDN)) 563 { 564 break; 565 } 566 567 matchedDN = matchedDN.getParentDNInSuffix(); 568 } 569 570 LocalizableMessage message = 571 ERR_BACKEND_ENTRY_DOESNT_EXIST.get(baseDN, getBackendID()); 572 throw new DirectoryException( 573 ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); 574 } 575 576 if (baseEntry != null) 577 { 578 baseEntry = baseEntry.duplicate(true); 579 } 580 581 582 // If it's a base-level search, then just get that entry and return it if it 583 // matches the filter. 584 if (scope == SearchScope.BASE_OBJECT) 585 { 586 if (filter.matchesEntry(baseEntry)) 587 { 588 searchOperation.returnEntry(baseEntry, new LinkedList<Control>()); 589 } 590 } 591 else 592 { 593 // Walk through all entries and send the ones that match. 594 for (Entry e : entryMap.values()) 595 { 596 e = e.duplicate(true); 597 if (e.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(e)) 598 { 599 searchOperation.returnEntry(e, new LinkedList<Control>()); 600 } 601 } 602 } 603 } 604 605 /** {@inheritDoc} */ 606 @Override 607 public Set<String> getSupportedControls() 608 { 609 return supportedControls; 610 } 611 612 /** {@inheritDoc} */ 613 @Override 614 public Set<String> getSupportedFeatures() 615 { 616 return Collections.emptySet(); 617 } 618 619 /** {@inheritDoc} */ 620 @Override 621 public boolean supports(BackendOperation backendOperation) 622 { 623 switch (backendOperation) 624 { 625 case LDIF_EXPORT: 626 case LDIF_IMPORT: 627 return true; 628 629 default: 630 return false; 631 } 632 } 633 634 /** {@inheritDoc} */ 635 @Override 636 public synchronized void exportLDIF(LDIFExportConfig exportConfig) 637 throws DirectoryException 638 { 639 // Create the LDIF writer. 640 LDIFWriter ldifWriter; 641 try 642 { 643 ldifWriter = new LDIFWriter(exportConfig); 644 } 645 catch (Exception e) 646 { 647 logger.traceException(e); 648 649 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 650 ERR_MEMORYBACKEND_CANNOT_CREATE_LDIF_WRITER.get(e), e); 651 } 652 653 654 // Walk through all the entries and write them to LDIF. 655 DN entryDN = null; 656 try 657 { 658 for (Entry entry : entryMap.values()) 659 { 660 entryDN = entry.getName(); 661 ldifWriter.writeEntry(entry); 662 } 663 } 664 catch (Exception e) 665 { 666 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 667 ERR_MEMORYBACKEND_CANNOT_WRITE_ENTRY_TO_LDIF.get(entryDN, e), e); 668 } 669 finally 670 { 671 close(ldifWriter); 672 } 673 } 674 675 /** {@inheritDoc} */ 676 @Override 677 public synchronized LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 678 throws DirectoryException 679 { 680 clearMemoryBackend(); 681 682 LDIFReader reader; 683 try 684 { 685 reader = new LDIFReader(importConfig); 686 } 687 catch (Exception e) 688 { 689 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 690 ERR_MEMORYBACKEND_CANNOT_CREATE_LDIF_READER.get(e), e); 691 } 692 693 694 try 695 { 696 while (true) 697 { 698 Entry e = null; 699 try 700 { 701 e = reader.readEntry(); 702 if (e == null) 703 { 704 break; 705 } 706 } 707 catch (LDIFException le) 708 { 709 if (! le.canContinueReading()) 710 { 711 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 712 ERR_MEMORYBACKEND_ERROR_READING_LDIF.get(e), le); 713 } 714 else 715 { 716 continue; 717 } 718 } 719 720 try 721 { 722 addEntry(e, null); 723 } 724 catch (DirectoryException de) 725 { 726 reader.rejectLastEntry(de.getMessageObject()); 727 } 728 } 729 730 return new LDIFImportResult(reader.getEntriesRead(), 731 reader.getEntriesRejected(), 732 reader.getEntriesIgnored()); 733 } 734 catch (DirectoryException de) 735 { 736 throw de; 737 } 738 catch (Exception e) 739 { 740 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 741 ERR_MEMORYBACKEND_ERROR_DURING_IMPORT.get(e), e); 742 } 743 finally 744 { 745 reader.close(); 746 } 747 } 748 749 /** {@inheritDoc} */ 750 @Override 751 public void createBackup(BackupConfig backupConfig) 752 throws DirectoryException 753 { 754 LocalizableMessage message = ERR_MEMORYBACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 755 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 756 } 757 758 /** {@inheritDoc} */ 759 @Override 760 public void removeBackup(BackupDirectory backupDirectory, 761 String backupID) 762 throws DirectoryException 763 { 764 LocalizableMessage message = ERR_MEMORYBACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 765 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 766 } 767 768 /** {@inheritDoc} */ 769 @Override 770 public void restoreBackup(RestoreConfig restoreConfig) 771 throws DirectoryException 772 { 773 LocalizableMessage message = ERR_MEMORYBACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get(); 774 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 775 } 776}