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