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 2007-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2011-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.io.File;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.LinkedHashMap;
030import java.util.LinkedList;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.concurrent.locks.ReentrantReadWriteLock;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.config.server.ConfigChangeResult;
039import org.forgerock.opendj.config.server.ConfigException;
040import org.forgerock.opendj.ldap.ConditionResult;
041import org.forgerock.opendj.ldap.DN;
042import org.forgerock.opendj.ldap.ResultCode;
043import org.forgerock.opendj.ldap.SearchScope;
044import org.forgerock.opendj.ldap.schema.AttributeType;
045import org.opends.server.admin.server.ConfigurationChangeListener;
046import org.opends.server.admin.std.server.LDIFBackendCfg;
047import org.opends.server.api.AlertGenerator;
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.BackupConfig;
058import org.opends.server.types.BackupDirectory;
059import org.opends.server.types.Control;
060import org.opends.server.types.DirectoryException;
061import org.opends.server.types.Entry;
062import org.opends.server.types.ExistingFileBehavior;
063import org.opends.server.types.IndexType;
064import org.opends.server.types.InitializationException;
065import org.opends.server.types.LDIFExportConfig;
066import org.opends.server.types.LDIFImportConfig;
067import org.opends.server.types.LDIFImportResult;
068import org.opends.server.types.RestoreConfig;
069import org.opends.server.types.SearchFilter;
070import org.opends.server.util.LDIFException;
071import org.opends.server.util.LDIFReader;
072import org.opends.server.util.LDIFWriter;
073import org.opends.server.util.StaticUtils;
074
075/**
076 * This class provides a backend implementation that stores the underlying data
077 * in an LDIF file.  When the backend is initialized, the contents of the
078 * backend are read into memory and all read operations are performed purely
079 * from memory.  Write operations cause the underlying LDIF file to be
080 * re-written on disk.
081 */
082public class LDIFBackend
083       extends Backend<LDIFBackendCfg>
084       implements ConfigurationChangeListener<LDIFBackendCfg>, AlertGenerator
085{
086  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
087
088
089
090  /** The base DNs for this backend. */
091  private DN[] baseDNs;
092
093  /** The mapping between parent DNs and their immediate children. */
094  private final Map<DN, Set<DN>> childDNs = new HashMap<>();
095
096  /** The base DNs for this backend, in a hash set. */
097  private Set<DN> baseDNSet;
098
099  /** The set of supported controls for this backend. */
100  private final Set<String> supportedControls =
101      Collections.singleton(OID_SUBTREE_DELETE_CONTROL);
102
103  /** The current configuration for this backend. */
104  private LDIFBackendCfg currentConfig;
105
106  /** The mapping between entry DNs and the corresponding entries. */
107  private final Map<DN, Entry> entryMap = new LinkedHashMap<>();
108
109  /** A read-write lock used to protect access to this backend. */
110  private final ReentrantReadWriteLock backendLock = new ReentrantReadWriteLock();
111
112  /** The path to the LDIF file containing the data for this backend. */
113  private String ldifFilePath;
114
115  /**
116   * Creates a new backend with the provided information.  All backend
117   * implementations must implement a default constructor that use
118   * <CODE>super()</CODE> to invoke this constructor.
119   */
120  public LDIFBackend()
121  {
122  }
123
124  /** {@inheritDoc} */
125  @Override
126  public void openBackend()
127         throws ConfigException, InitializationException
128  {
129    // We won't support anything other than exactly one base DN in this
130    // implementation.  If we were to add such support in the future, we would
131    // likely want to separate the data for each base DN into a separate entry
132    // map.
133    if (baseDNs == null || baseDNs.length != 1)
134    {
135      throw new ConfigException(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(currentConfig.dn()));
136    }
137
138    for (DN dn : baseDNs)
139    {
140      try
141      {
142        DirectoryServer.registerBaseDN(dn, this,
143                                       currentConfig.isIsPrivateBackend());
144      }
145      catch (Exception e)
146      {
147        logger.traceException(e);
148
149        LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
150            dn, getExceptionMessage(e));
151        throw new InitializationException(message, e);
152      }
153    }
154
155    DirectoryServer.registerAlertGenerator(this);
156
157    readLDIF();
158  }
159
160
161
162  /**
163   * Reads the contents of the LDIF backing file into memory.
164   *
165   * @throws  InitializationException  If a problem occurs while reading the
166   *                                   LDIF file.
167   */
168  private void readLDIF()
169          throws InitializationException
170  {
171    File ldifFile = getFileForPath(ldifFilePath);
172    if (! ldifFile.exists())
173    {
174      // This is fine.  We will just start with an empty backend.
175      if (logger.isTraceEnabled())
176      {
177        logger.trace("LDIF backend starting empty because LDIF file " +
178                         ldifFilePath + " does not exist");
179      }
180
181      entryMap.clear();
182      childDNs.clear();
183      return;
184    }
185
186
187    try
188    {
189      importLDIF(new LDIFImportConfig(ldifFile.getAbsolutePath()), false);
190    }
191    catch (DirectoryException de)
192    {
193      throw new InitializationException(de.getMessageObject(), de);
194    }
195  }
196
197
198
199  /**
200   * Writes the current set of entries to the target LDIF file.  The new LDIF
201   * will first be created as a temporary file and then renamed into place.  The
202   * caller must either hold the write lock for this backend, or must ensure
203   * that it's in some other state that guarantees exclusive access to the data.
204   *
205   * @throws  DirectoryException  If a problem occurs that prevents the updated
206   *                              LDIF from being written.
207   */
208  private void writeLDIF()
209          throws DirectoryException
210  {
211    File ldifFile = getFileForPath(ldifFilePath);
212    File tempFile = new File(ldifFile.getAbsolutePath() + ".new");
213    File oldFile  = new File(ldifFile.getAbsolutePath() + ".old");
214
215
216    // Write the new data to a temporary file.
217    LDIFWriter writer;
218    try
219    {
220      LDIFExportConfig exportConfig =
221           new LDIFExportConfig(tempFile.getAbsolutePath(),
222                                ExistingFileBehavior.OVERWRITE);
223      writer = new LDIFWriter(exportConfig);
224    }
225    catch (Exception e)
226    {
227      logger.traceException(e);
228
229      LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_CREATING_FILE.get(
230                       tempFile.getAbsolutePath(),
231                       currentConfig.dn(),
232                       stackTraceToSingleLineString(e));
233      DirectoryServer.sendAlertNotification(this,
234                           ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
235      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
236                                   m, e);
237    }
238
239
240    for (Entry entry : entryMap.values())
241    {
242      try
243      {
244        writer.writeEntry(entry);
245      }
246      catch (Exception e)
247      {
248        logger.traceException(e);
249
250        StaticUtils.close(writer);
251
252        LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_WRITING_FILE.get(
253                         tempFile.getAbsolutePath(),
254                         currentConfig.dn(),
255                         stackTraceToSingleLineString(e));
256        DirectoryServer.sendAlertNotification(this,
257                             ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
258        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
259                                     m, e);
260      }
261    }
262
263    // On Linux the final write() on a file can actually fail but not throw an Exception.
264    // The close() will throw an Exception in this case so we MUST check for Exceptions
265    // here.
266    try
267    {
268        writer.close();
269    }
270    catch (Exception e)
271    {
272      logger.traceException(e);
273      LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_CLOSING_FILE.get(
274                       tempFile.getAbsolutePath(),
275                       currentConfig.dn(),
276                       stackTraceToSingleLineString(e));
277      DirectoryServer.sendAlertNotification(this,
278                           ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
279      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
280                                   m, e);
281    }
282
283    // Extra sanity check
284    if (!entryMap.isEmpty() && tempFile.exists() && tempFile.length() == 0)
285    {
286      LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_EMPTY_FILE.get(
287                       tempFile.getAbsolutePath(),
288                       currentConfig.dn());
289      DirectoryServer.sendAlertNotification(this,
290                           ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
291      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m);
292    }
293
294    if (tempFile.exists())
295    {
296      // Rename the existing "live" file out of the way and move the new file
297      // into place.
298      try
299      {
300        oldFile.delete();
301      }
302      catch (Exception e)
303      {
304        logger.traceException(e);
305      }
306    }
307
308    try
309    {
310      if (ldifFile.exists())
311      {
312        ldifFile.renameTo(oldFile);
313      }
314    }
315    catch (Exception e)
316    {
317      logger.traceException(e);
318    }
319
320    try
321    {
322      tempFile.renameTo(ldifFile);
323    }
324    catch (Exception e)
325    {
326      logger.traceException(e);
327
328      LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_RENAMING_FILE.get(
329                       tempFile.getAbsolutePath(),
330                       ldifFile.getAbsolutePath(),
331                       currentConfig.dn(),
332                       stackTraceToSingleLineString(e));
333      DirectoryServer.sendAlertNotification(this,
334                           ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
335      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
336                                   m, e);
337    }
338  }
339
340  /** {@inheritDoc} */
341  @Override
342  public void closeBackend()
343  {
344    backendLock.writeLock().lock();
345
346    try
347    {
348      currentConfig.removeLDIFChangeListener(this);
349      DirectoryServer.deregisterAlertGenerator(this);
350
351      for (DN dn : baseDNs)
352      {
353        try
354        {
355          DirectoryServer.deregisterBaseDN(dn);
356        }
357        catch (Exception e)
358        {
359          logger.traceException(e);
360        }
361      }
362    }
363    finally
364    {
365      backendLock.writeLock().unlock();
366    }
367  }
368
369  /** {@inheritDoc} */
370  @Override
371  public DN[] getBaseDNs()
372  {
373    return baseDNs;
374  }
375
376  /** {@inheritDoc} */
377  @Override
378  public long getEntryCount()
379  {
380    backendLock.readLock().lock();
381
382    try
383    {
384      if (entryMap != null)
385      {
386        return entryMap.size();
387      }
388
389      return -1;
390    }
391    finally
392    {
393      backendLock.readLock().unlock();
394    }
395  }
396
397  /** {@inheritDoc} */
398  @Override
399  public boolean isIndexed(AttributeType attributeType, IndexType indexType)
400  {
401    // All searches in this backend will always be considered indexed.
402    return true;
403  }
404
405  /** {@inheritDoc} */
406  @Override
407  public ConditionResult hasSubordinates(DN entryDN)
408         throws DirectoryException
409  {
410    backendLock.readLock().lock();
411
412    try
413    {
414      Set<DN> childDNSet = childDNs.get(entryDN);
415      if (childDNSet == null || childDNSet.isEmpty())
416      {
417        // It could be that the entry doesn't exist, in which case we should
418        // throw an exception.
419        if (entryMap.containsKey(entryDN))
420        {
421          return ConditionResult.FALSE;
422        }
423        else
424        {
425          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
426              ERR_LDIF_BACKEND_HAS_SUBORDINATES_NO_SUCH_ENTRY.get(entryDN));
427        }
428      }
429      else
430      {
431        return ConditionResult.TRUE;
432      }
433    }
434    finally
435    {
436      backendLock.readLock().unlock();
437    }
438  }
439
440  /** {@inheritDoc} */
441  @Override
442  public long getNumberOfChildren(DN parentDN) throws DirectoryException
443  {
444    checkNotNull(parentDN, "parentDN must not be null");
445    return getNumberOfSubordinates(parentDN, false);
446  }
447
448  /** {@inheritDoc} */
449  @Override
450  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
451  {
452    checkNotNull(baseDN, "baseDN must not be null");
453    if (!Arrays.asList(baseDNs).contains(baseDN))
454    {
455      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY
456          .get(baseDN));
457    }
458    final int baseDNIfExists = childDNs.containsKey(baseDN) ? 1 : 0;
459    return getNumberOfSubordinates(baseDN, true) + baseDNIfExists;
460  }
461
462  private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException
463  {
464    backendLock.readLock().lock();
465
466    try
467    {
468      Set<DN> childDNSet = childDNs.get(entryDN);
469      if (childDNSet == null || childDNSet.isEmpty())
470      {
471        // It could be that the entry doesn't exist, in which case we should
472        // throw an exception.
473        if (entryMap.containsKey(entryDN))
474        {
475          return 0L;
476        }
477        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY
478            .get(entryDN));
479      }
480
481      if (!includeSubtree)
482      {
483        return childDNSet.size();
484      }
485
486      long count = 0;
487      for (DN childDN : childDNSet)
488      {
489        count += getNumberOfSubordinates(childDN, true);
490        count++;
491      }
492      return count;
493    }
494    finally
495    {
496      backendLock.readLock().unlock();
497    }
498  }
499
500  /** {@inheritDoc} */
501  @Override
502  public Entry getEntry(DN entryDN)
503  {
504    backendLock.readLock().lock();
505
506    try
507    {
508      return entryMap.get(entryDN);
509    }
510    finally
511    {
512      backendLock.readLock().unlock();
513    }
514  }
515
516  /** {@inheritDoc} */
517  @Override
518  public boolean entryExists(DN entryDN)
519  {
520    backendLock.readLock().lock();
521
522    try
523    {
524      return entryMap.containsKey(entryDN);
525    }
526    finally
527    {
528      backendLock.readLock().unlock();
529    }
530  }
531
532  /** {@inheritDoc} */
533  @Override
534  public void addEntry(Entry entry, AddOperation addOperation)
535         throws DirectoryException
536  {
537    backendLock.writeLock().lock();
538
539    try
540    {
541      // Make sure that the target entry does not already exist, but that its
542      // parent does exist (or that the entry being added is the base DN).
543      DN entryDN = entry.getName();
544      if (entryMap.containsKey(entryDN))
545      {
546        LocalizableMessage m = ERR_LDIF_BACKEND_ADD_ALREADY_EXISTS.get(entryDN);
547        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m);
548      }
549
550      if (baseDNSet.contains(entryDN))
551      {
552        entryMap.put(entryDN, entry.duplicate(false));
553        writeLDIF();
554        return;
555      }
556      else
557      {
558        DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
559        if (parentDN != null && entryMap.containsKey(parentDN))
560        {
561          entryMap.put(entryDN, entry.duplicate(false));
562
563          Set<DN> childDNSet = childDNs.get(parentDN);
564          if (childDNSet == null)
565          {
566            childDNSet = new HashSet<>();
567            childDNs.put(parentDN, childDNSet);
568          }
569          childDNSet.add(entryDN);
570          writeLDIF();
571          return;
572        }
573        else
574        {
575          DN matchedDN = null;
576          if (parentDN != null)
577          {
578            while (true)
579            {
580              parentDN = DirectoryServer.getParentDNInSuffix(parentDN);
581              if (parentDN == null)
582              {
583                break;
584              }
585
586              if (entryMap.containsKey(parentDN))
587              {
588                matchedDN = parentDN;
589                break;
590              }
591            }
592          }
593
594          LocalizableMessage m = ERR_LDIF_BACKEND_ADD_MISSING_PARENT.get(entryDN);
595          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
596        }
597      }
598    }
599    finally
600    {
601      backendLock.writeLock().unlock();
602    }
603  }
604
605  /** {@inheritDoc} */
606  @Override
607  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
608         throws DirectoryException
609  {
610    backendLock.writeLock().lock();
611
612    try
613    {
614      // Get the DN of the target entry's parent, if it exists.  We'll need to
615      // also remove the reference to the target entry from the parent's set of
616      // children.
617      DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
618
619      // Make sure that the target entry exists.  If not, then fail.
620      if (! entryMap.containsKey(entryDN))
621      {
622        DN matchedDN = null;
623        while (parentDN != null)
624        {
625          if (entryMap.containsKey(parentDN))
626          {
627            matchedDN = parentDN;
628            break;
629          }
630
631          parentDN = DirectoryServer.getParentDNInSuffix(parentDN);
632        }
633
634        LocalizableMessage m = ERR_LDIF_BACKEND_DELETE_NO_SUCH_ENTRY.get(entryDN);
635        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
636      }
637
638
639      // See if the target entry has any children.  If so, then we'll only
640      // delete it if the request contains the subtree delete control (in
641      // which case we'll delete the entire subtree).
642      Set<DN> childDNSet = childDNs.get(entryDN);
643      if (childDNSet == null || childDNSet.isEmpty())
644      {
645        entryMap.remove(entryDN);
646        childDNs.remove(entryDN);
647
648        if (parentDN != null)
649        {
650          Set<DN> parentChildren = childDNs.get(parentDN);
651          if (parentChildren != null)
652          {
653            parentChildren.remove(entryDN);
654            if (parentChildren.isEmpty())
655            {
656              childDNs.remove(parentDN);
657            }
658          }
659        }
660      }
661      else
662      {
663        boolean subtreeDelete = deleteOperation != null
664            && deleteOperation
665                .getRequestControl(SubtreeDeleteControl.DECODER) != null;
666
667        if (! subtreeDelete)
668        {
669          LocalizableMessage m = ERR_LDIF_BACKEND_DELETE_NONLEAF.get(entryDN);
670          throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, m);
671        }
672
673        entryMap.remove(entryDN);
674        childDNs.remove(entryDN);
675
676        if (parentDN != null)
677        {
678          Set<DN> parentChildren = childDNs.get(parentDN);
679          if (parentChildren != null)
680          {
681            parentChildren.remove(entryDN);
682            if (parentChildren.isEmpty())
683            {
684              childDNs.remove(parentDN);
685            }
686          }
687        }
688
689        for (DN childDN : childDNSet)
690        {
691          subtreeDelete(childDN);
692        }
693      }
694
695      writeLDIF();
696    }
697    finally
698    {
699      backendLock.writeLock().unlock();
700    }
701  }
702
703
704
705  /**
706   * Removes the specified entry and any subordinates that it may have from
707   * the backend.  This method assumes that the caller holds the backend write
708   * lock.
709   *
710   * @param  entryDN  The DN of the entry to remove, along with all of its
711   *                  subordinate entries.
712   */
713  private void subtreeDelete(DN entryDN)
714  {
715    entryMap.remove(entryDN);
716    Set<DN> childDNSet = childDNs.remove(entryDN);
717    if (childDNSet != null)
718    {
719      for (DN childDN : childDNSet)
720      {
721        subtreeDelete(childDN);
722      }
723    }
724  }
725
726  /** {@inheritDoc} */
727  @Override
728  public void replaceEntry(Entry oldEntry, Entry newEntry,
729      ModifyOperation modifyOperation) throws DirectoryException
730  {
731    backendLock.writeLock().lock();
732
733    try
734    {
735      // Make sure that the target entry exists.  If not, then fail.
736      DN entryDN = newEntry.getName();
737      if (! entryMap.containsKey(entryDN))
738      {
739        DN matchedDN = null;
740        DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
741        while (parentDN != null)
742        {
743          if (entryMap.containsKey(parentDN))
744          {
745            matchedDN = parentDN;
746            break;
747          }
748
749          parentDN = DirectoryServer.getParentDNInSuffix(parentDN);
750        }
751
752        LocalizableMessage m = ERR_LDIF_BACKEND_MODIFY_NO_SUCH_ENTRY.get(entryDN);
753        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
754      }
755
756      entryMap.put(entryDN, newEntry.duplicate(false));
757      writeLDIF();
758      return;
759    }
760    finally
761    {
762      backendLock.writeLock().unlock();
763    }
764  }
765
766  /** {@inheritDoc} */
767  @Override
768  public void renameEntry(DN currentDN, Entry entry,
769                          ModifyDNOperation modifyDNOperation)
770         throws DirectoryException
771  {
772    backendLock.writeLock().lock();
773
774    try
775    {
776      // Make sure that the original entry exists and that the new entry doesn't
777      // exist but its parent does.
778      DN newDN = entry.getName();
779      if (! entryMap.containsKey(currentDN))
780      {
781        DN matchedDN = null;
782        DN parentDN = DirectoryServer.getParentDNInSuffix(currentDN);
783        while (parentDN != null)
784        {
785          if (entryMap.containsKey(parentDN))
786          {
787            matchedDN = parentDN;
788            break;
789          }
790
791          parentDN = DirectoryServer.getParentDNInSuffix(parentDN);
792        }
793
794        LocalizableMessage m = ERR_LDIF_BACKEND_MODDN_NO_SUCH_SOURCE_ENTRY.get(currentDN);
795        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
796      }
797
798      if (entryMap.containsKey(newDN))
799      {
800        LocalizableMessage m = ERR_LDIF_BACKEND_MODDN_TARGET_ENTRY_ALREADY_EXISTS.get(newDN);
801        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m);
802      }
803
804      DN newParentDN = DirectoryServer.getParentDNInSuffix(newDN);
805      if (! entryMap.containsKey(newParentDN))
806      {
807        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
808            ERR_LDIF_BACKEND_MODDN_NEW_PARENT_DOESNT_EXIST.get(newParentDN));
809      }
810
811      // Remove the entry from the list of children for the old parent and
812      // add the new entry DN to the set of children for the new parent.
813      DN oldParentDN = DirectoryServer.getParentDNInSuffix(currentDN);
814      Set<DN> parentChildDNs = childDNs.get(oldParentDN);
815      if (parentChildDNs != null)
816      {
817        parentChildDNs.remove(currentDN);
818        if (parentChildDNs.isEmpty()
819            && modifyDNOperation.getNewSuperior() != null)
820        {
821          childDNs.remove(oldParentDN);
822        }
823      }
824
825      parentChildDNs = childDNs.get(newParentDN);
826      if (parentChildDNs == null)
827      {
828        parentChildDNs = new HashSet<>();
829        childDNs.put(newParentDN, parentChildDNs);
830      }
831      parentChildDNs.add(newDN);
832
833
834      // If the entry has children, then we'll need to work on the whole
835      // subtree.  Otherwise, just work on the target entry.
836      Set<DN> childDNSet = childDNs.remove(currentDN);
837      entryMap.remove(currentDN);
838      entryMap.put(newDN, entry.duplicate(false));
839      if (childDNSet != null && !childDNSet.isEmpty())
840      {
841        for (DN childDN : childDNSet)
842        {
843          subtreeRename(childDN, newDN);
844        }
845      }
846      writeLDIF();
847    }
848    finally
849    {
850      backendLock.writeLock().unlock();
851    }
852  }
853
854
855
856  /**
857   * Moves the specified entry and all of its children so that they are
858   * appropriately placed below the given new parent DN.  This method assumes
859   * that the caller holds the backend write lock.
860   *
861   * @param  entryDN      The DN of the entry to move/rename.
862   * @param  newParentDN  The DN of the new parent under which the entry should
863   *                      be placed.
864   */
865  private void subtreeRename(DN entryDN, DN newParentDN)
866  {
867    Set<DN> childDNSet = childDNs.remove(entryDN);
868    DN newEntryDN = newParentDN.child(entryDN.rdn());
869
870    Entry oldEntry = entryMap.remove(entryDN);
871    if (oldEntry == null)
872    {
873      // This should never happen.
874      if (logger.isTraceEnabled())
875      {
876        logger.trace("Subtree rename encountered entry DN " +
877                            entryDN + " for nonexistent entry.");
878      }
879      return;
880    }
881
882    Entry newEntry = oldEntry.duplicate(false);
883    newEntry.setDN(newEntryDN);
884    entryMap.put(newEntryDN, newEntry);
885
886    Set<DN> parentChildren = childDNs.get(newParentDN);
887    if (parentChildren == null)
888    {
889      parentChildren = new HashSet<>();
890      childDNs.put(newParentDN, parentChildren);
891    }
892    parentChildren.add(newEntryDN);
893
894    if (childDNSet != null)
895    {
896      for (DN childDN : childDNSet)
897      {
898        subtreeRename(childDN, newEntryDN);
899      }
900    }
901  }
902
903  /** {@inheritDoc} */
904  @Override
905  public void search(SearchOperation searchOperation)
906         throws DirectoryException
907  {
908    backendLock.readLock().lock();
909
910    try
911    {
912      // Get the base DN, scope, and filter for the search.
913      DN           baseDN = searchOperation.getBaseDN();
914      SearchScope  scope  = searchOperation.getScope();
915      SearchFilter filter = searchOperation.getFilter();
916
917
918      // Make sure the base entry exists if it's supposed to be in this backend.
919      Entry baseEntry = entryMap.get(baseDN);
920      if (baseEntry == null && handlesEntry(baseDN))
921      {
922        DN matchedDN = DirectoryServer.getParentDNInSuffix(baseDN);
923        while (matchedDN != null)
924        {
925          if (entryMap.containsKey(matchedDN))
926          {
927            break;
928          }
929
930          matchedDN = DirectoryServer.getParentDNInSuffix(matchedDN);
931        }
932
933        LocalizableMessage m = ERR_LDIF_BACKEND_SEARCH_NO_SUCH_BASE.get(baseDN);
934        throw new DirectoryException(
935                ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
936      }
937
938      if (baseEntry != null)
939      {
940        baseEntry = baseEntry.duplicate(true);
941      }
942
943      // If it's a base-level search, then just get that entry and return it if
944      // it matches the filter.
945      if (scope == SearchScope.BASE_OBJECT)
946      {
947        if (filter.matchesEntry(baseEntry))
948        {
949          searchOperation.returnEntry(baseEntry, new LinkedList<Control>());
950        }
951      }
952      else
953      {
954        // Walk through all entries and send the ones that match.
955        for (Entry e : entryMap.values())
956        {
957          e = e.duplicate(true);
958          if (e.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(e))
959          {
960            searchOperation.returnEntry(e, new LinkedList<Control>());
961          }
962        }
963      }
964    }
965    finally
966    {
967      backendLock.readLock().unlock();
968    }
969  }
970
971  /** {@inheritDoc} */
972  @Override
973  public Set<String> getSupportedControls()
974  {
975    return supportedControls;
976  }
977
978  /** {@inheritDoc} */
979  @Override
980  public Set<String> getSupportedFeatures()
981  {
982    return Collections.emptySet();
983  }
984
985  /** {@inheritDoc} */
986  @Override
987  public boolean supports(BackendOperation backendOperation)
988  {
989    switch (backendOperation)
990    {
991    case LDIF_EXPORT:
992    case LDIF_IMPORT:
993      return true;
994
995    default:
996      return false;
997    }
998  }
999
1000  /** {@inheritDoc} */
1001  @Override
1002  public void exportLDIF(LDIFExportConfig exportConfig)
1003         throws DirectoryException
1004  {
1005    backendLock.readLock().lock();
1006
1007    try
1008    {
1009      // Create the LDIF writer.
1010      LDIFWriter ldifWriter;
1011      try
1012      {
1013        ldifWriter = new LDIFWriter(exportConfig);
1014      }
1015      catch (Exception e)
1016      {
1017        logger.traceException(e);
1018
1019        LocalizableMessage m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_WRITER.get(
1020                         stackTraceToSingleLineString(e));
1021        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1022                                     m, e);
1023      }
1024
1025
1026      // Walk through all the entries and write them to LDIF.
1027      DN entryDN = null;
1028      try
1029      {
1030        for (Entry entry : entryMap.values())
1031        {
1032          entryDN = entry.getName();
1033          ldifWriter.writeEntry(entry);
1034        }
1035      }
1036      catch (Exception e)
1037      {
1038        LocalizableMessage m = ERR_LDIF_BACKEND_CANNOT_WRITE_ENTRY_TO_LDIF.get(
1039            entryDN, stackTraceToSingleLineString(e));
1040        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1041                                     m, e);
1042      }
1043      finally
1044      {
1045        StaticUtils.close(ldifWriter);
1046      }
1047    }
1048    finally
1049    {
1050      backendLock.readLock().unlock();
1051    }
1052  }
1053
1054  /** {@inheritDoc} */
1055  @Override
1056  public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
1057      throws DirectoryException
1058  {
1059    return importLDIF(importConfig, true);
1060  }
1061
1062  /**
1063   * Processes an LDIF import operation, optionally writing the resulting LDIF
1064   * to disk.
1065   *
1066   * @param  importConfig  The LDIF import configuration.
1067   * @param  writeLDIF     Indicates whether the LDIF backing file for this
1068   *                       backend should be updated when the import is
1069   *                       complete.  This should only be {@code false} when
1070   *                       reading the LDIF as the backend is coming online.
1071   */
1072  private LDIFImportResult importLDIF(LDIFImportConfig importConfig,
1073                                     boolean writeLDIF)
1074         throws DirectoryException
1075  {
1076    backendLock.writeLock().lock();
1077
1078    try
1079    {
1080      LDIFReader reader;
1081      try
1082      {
1083        reader = new LDIFReader(importConfig);
1084      }
1085      catch (Exception e)
1086      {
1087        LocalizableMessage m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_READER.get(
1088                         stackTraceToSingleLineString(e));
1089        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1090                                     m, e);
1091      }
1092
1093      entryMap.clear();
1094      childDNs.clear();
1095
1096
1097      try
1098      {
1099        while (true)
1100        {
1101          Entry e = null;
1102          try
1103          {
1104            e = reader.readEntry();
1105            if (e == null)
1106            {
1107              break;
1108            }
1109          }
1110          catch (LDIFException le)
1111          {
1112            if (! le.canContinueReading())
1113            {
1114              LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(
1115                               stackTraceToSingleLineString(le));
1116              throw new DirectoryException(
1117                             DirectoryServer.getServerErrorResultCode(), m, le);
1118            }
1119            else
1120            {
1121              continue;
1122            }
1123          }
1124
1125          // Make sure that we don't already have an entry with the same DN.  If
1126          // a duplicate is encountered, then log a message and continue.
1127          DN entryDN = e.getName();
1128          if (entryMap.containsKey(entryDN))
1129          {
1130            LocalizableMessage m =
1131                ERR_LDIF_BACKEND_DUPLICATE_ENTRY.get(ldifFilePath, currentConfig.dn(), entryDN);
1132            logger.error(m);
1133            reader.rejectLastEntry(m);
1134            continue;
1135          }
1136
1137
1138          // If the entry DN is a base DN, then add it with no more processing.
1139          if (baseDNSet.contains(entryDN))
1140          {
1141            entryMap.put(entryDN, e);
1142            continue;
1143          }
1144
1145
1146          // Make sure that the parent exists.  If not, then reject the entry.
1147          boolean isBelowBaseDN = false;
1148          for (DN baseDN : baseDNs)
1149          {
1150            if (baseDN.isSuperiorOrEqualTo(entryDN))
1151            {
1152              isBelowBaseDN = true;
1153              break;
1154            }
1155          }
1156
1157          if (! isBelowBaseDN)
1158          {
1159            LocalizableMessage m = ERR_LDIF_BACKEND_ENTRY_OUT_OF_SCOPE.get(
1160                ldifFilePath, currentConfig.dn(), entryDN);
1161            logger.error(m);
1162            reader.rejectLastEntry(m);
1163            continue;
1164          }
1165
1166          DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
1167          if (parentDN == null || !entryMap.containsKey(parentDN))
1168          {
1169            LocalizableMessage m = ERR_LDIF_BACKEND_MISSING_PARENT.get(
1170                ldifFilePath, currentConfig.dn(), entryDN);
1171            logger.error(m);
1172            reader.rejectLastEntry(m);
1173            continue;
1174          }
1175
1176
1177          // The entry does not exist but its parent does, so add it and update
1178          // the set of children for the parent.
1179          entryMap.put(entryDN, e);
1180
1181          Set<DN> childDNSet = childDNs.get(parentDN);
1182          if (childDNSet == null)
1183          {
1184            childDNSet = new HashSet<>();
1185            childDNs.put(parentDN, childDNSet);
1186          }
1187
1188          childDNSet.add(entryDN);
1189        }
1190
1191
1192        if (writeLDIF)
1193        {
1194          writeLDIF();
1195        }
1196
1197        return new LDIFImportResult(reader.getEntriesRead(),
1198                                    reader.getEntriesRejected(),
1199                                    reader.getEntriesIgnored());
1200      }
1201      catch (DirectoryException de)
1202      {
1203        throw de;
1204      }
1205      catch (Exception e)
1206      {
1207        LocalizableMessage m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(
1208                         stackTraceToSingleLineString(e));
1209        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1210                                     m, e);
1211      }
1212      finally
1213      {
1214        StaticUtils.close(reader);
1215      }
1216    }
1217    finally
1218    {
1219      backendLock.writeLock().unlock();
1220    }
1221  }
1222
1223  /** {@inheritDoc} */
1224  @Override
1225  public void createBackup(BackupConfig backupConfig)
1226         throws DirectoryException
1227  {
1228    LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1229    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1230  }
1231
1232  /** {@inheritDoc} */
1233  @Override
1234  public void removeBackup(BackupDirectory backupDirectory, String backupID)
1235         throws DirectoryException
1236  {
1237    LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1238    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1239  }
1240
1241  /** {@inheritDoc} */
1242  @Override
1243  public void restoreBackup(RestoreConfig restoreConfig)
1244         throws DirectoryException
1245  {
1246    LocalizableMessage message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1247    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1248  }
1249
1250  /** {@inheritDoc} */
1251  @Override
1252  public void configureBackend(LDIFBackendCfg config, ServerContext serverContext) throws ConfigException
1253  {
1254    if (config != null)
1255    {
1256      currentConfig = config;
1257      currentConfig.addLDIFChangeListener(this);
1258
1259      baseDNs = new DN[currentConfig.getBaseDN().size()];
1260      currentConfig.getBaseDN().toArray(baseDNs);
1261      if (baseDNs.length != 1)
1262      {
1263        throw new ConfigException(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(currentConfig.dn()));
1264      }
1265
1266      baseDNSet = new HashSet<>();
1267      Collections.addAll(baseDNSet, baseDNs);
1268
1269      ldifFilePath = currentConfig.getLDIFFile();
1270    }
1271  }
1272
1273  /** {@inheritDoc} */
1274  @Override
1275  public boolean isConfigurationChangeAcceptable(LDIFBackendCfg configuration,
1276                      List<LocalizableMessage> unacceptableReasons)
1277  {
1278    boolean configAcceptable = true;
1279
1280    // Make sure that there is only a single base DN.
1281    if (configuration.getBaseDN().size() != 1)
1282    {
1283      unacceptableReasons.add(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(configuration.dn()));
1284      configAcceptable = false;
1285    }
1286
1287    return configAcceptable;
1288  }
1289
1290  /** {@inheritDoc} */
1291  @Override
1292  public ConfigChangeResult applyConfigurationChange(
1293                                 LDIFBackendCfg configuration)
1294  {
1295    // We don't actually need to do anything in response to this.  However, if
1296    // the base DNs or LDIF file are different from what we're currently using
1297    // then indicate that admin action is required.
1298    final ConfigChangeResult ccr = new ConfigChangeResult();
1299
1300    if (ldifFilePath != null)
1301    {
1302      File currentLDIF = getFileForPath(ldifFilePath);
1303      File newLDIF     = getFileForPath(configuration.getLDIFFile());
1304      if (! currentLDIF.equals(newLDIF))
1305      {
1306        ccr.addMessage(INFO_LDIF_BACKEND_LDIF_FILE_CHANGED.get());
1307        ccr.setAdminActionRequired(true);
1308      }
1309    }
1310
1311    if (baseDNSet != null && !baseDNSet.equals(configuration.getBaseDN()))
1312    {
1313      ccr.addMessage(INFO_LDIF_BACKEND_BASE_DN_CHANGED.get());
1314      ccr.setAdminActionRequired(true);
1315    }
1316
1317    currentConfig = configuration;
1318    return ccr;
1319  }
1320
1321  /** {@inheritDoc} */
1322  @Override
1323  public DN getComponentEntryDN()
1324  {
1325    return currentConfig.dn();
1326  }
1327
1328  /** {@inheritDoc} */
1329  @Override
1330  public String getClassName()
1331  {
1332    return LDIFBackend.class.getName();
1333  }
1334
1335  /** {@inheritDoc} */
1336  @Override
1337  public Map<String,String> getAlerts()
1338  {
1339    Map<String,String> alerts = new LinkedHashMap<>();
1340    alerts.put(ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE,
1341               ALERT_DESCRIPTION_LDIF_BACKEND_CANNOT_WRITE_UPDATE);
1342    return alerts;
1343  }
1344}