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