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