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&lt;DN,Entry&gt;</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&lt;ByteString,Set&lt;DN&gt;&gt;</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}