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