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.config.ConfigConstants.*;
022import static org.opends.server.schema.BooleanSyntax.*;
023import static org.opends.server.util.ServerConstants.*;
024import static org.opends.server.util.StaticUtils.*;
025
026import java.io.File;
027import java.io.IOException;
028import java.util.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.LinkedHashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036
037import org.forgerock.i18n.LocalizableMessage;
038import org.forgerock.i18n.slf4j.LocalizedLogger;
039import org.forgerock.opendj.config.server.ConfigChangeResult;
040import org.forgerock.opendj.config.server.ConfigException;
041import org.forgerock.opendj.ldap.AVA;
042import org.forgerock.opendj.ldap.ByteString;
043import org.forgerock.opendj.ldap.ConditionResult;
044import org.forgerock.opendj.ldap.ResultCode;
045import org.forgerock.opendj.ldap.SearchScope;
046import org.forgerock.opendj.ldap.schema.AttributeType;
047import org.opends.server.admin.server.ConfigurationChangeListener;
048import org.opends.server.admin.std.server.BackupBackendCfg;
049import org.opends.server.api.Backend;
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.schema.GeneralizedTimeSyntax;
058import org.opends.server.types.Attribute;
059import org.opends.server.types.AttributeBuilder;
060import org.opends.server.types.Attributes;
061import org.opends.server.types.BackupConfig;
062import org.opends.server.types.BackupDirectory;
063import org.opends.server.types.BackupInfo;
064import org.forgerock.opendj.ldap.DN;
065import org.opends.server.types.DirectoryException;
066import org.opends.server.types.Entry;
067import org.opends.server.types.IndexType;
068import org.opends.server.types.InitializationException;
069import org.opends.server.types.LDIFExportConfig;
070import org.opends.server.types.LDIFImportConfig;
071import org.opends.server.types.LDIFImportResult;
072import org.opends.server.types.ObjectClass;
073import org.forgerock.opendj.ldap.RDN;
074import org.opends.server.types.RestoreConfig;
075import org.opends.server.types.SearchFilter;
076
077/**
078 * This class defines a backend used to present information about Directory
079 * Server backups.  It will not actually store anything, but upon request will
080 * retrieve information about the backups that it knows about.  The backups will
081 * be arranged in a hierarchy based on the directory that contains them, and
082 * it may be possible to dynamically discover new backups if a previously
083 * unknown backup directory is included in the base DN.
084 */
085public class BackupBackend
086       extends Backend<BackupBackendCfg>
087       implements ConfigurationChangeListener<BackupBackendCfg>
088{
089  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
090
091
092
093  /** The current configuration state. */
094  private BackupBackendCfg currentConfig;
095
096  /** The DN for the base backup entry. */
097  private DN backupBaseDN;
098
099  /** The set of base DNs for this backend. */
100  private DN[] baseDNs;
101
102  /** The backup base entry. */
103  private Entry backupBaseEntry;
104
105  /** A cache of BackupDirectories. */
106  private HashMap<File,CachedBackupDirectory> backupDirectories;
107
108  /**
109   * To avoid parsing and reparsing the contents of backup.info files, we
110   * cache the BackupDirectory for each directory using this class.
111   */
112  private class CachedBackupDirectory
113  {
114    /** The path to the 'bak' directory. */
115    private final String directoryPath;
116
117    /** The 'backup.info' file. */
118    private final File backupInfo;
119
120    /** The last modify time of the backupInfo file. */
121    private long lastModified;
122
123    /** The BackupDirectory parsed at lastModified time. */
124    private BackupDirectory backupDirectory;
125
126    /**
127     * A BackupDirectory that is cached based on the backup descriptor file.
128     *
129     * @param directory Path to the backup directory itself.
130     */
131    public CachedBackupDirectory(File directory)
132    {
133      directoryPath = directory.getPath();
134      backupInfo = new File(directoryPath + File.separator + BACKUP_DIRECTORY_DESCRIPTOR_FILE);
135      lastModified = -1;
136      backupDirectory = null;
137    }
138
139    /**
140     * Return a BackupDirectory. This will be recomputed every time the underlying descriptor (backup.info) file
141     * changes.
142     *
143     * @return An up-to-date BackupDirectory
144     * @throws IOException If a problem occurs while trying to read the contents of the descriptor file.
145     * @throws ConfigException If the contents of the descriptor file cannot be parsed to create a backup directory
146     *                         structure.
147     */
148    public synchronized BackupDirectory getBackupDirectory()
149            throws IOException, ConfigException
150    {
151      long currentModified = backupInfo.lastModified();
152      if (backupDirectory == null || currentModified != lastModified)
153      {
154        backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(directoryPath);
155        lastModified = currentModified;
156      }
157      return backupDirectory;
158    }
159  }
160
161
162  /**
163   * Creates a new backend with the provided information.  All backend
164   * implementations must implement a default constructor that use
165   * <CODE>super()</CODE> to invoke this constructor.
166   */
167  public BackupBackend()
168  {
169    super();
170
171    // Perform all initialization in initializeBackend.
172  }
173
174
175
176  /** {@inheritDoc} */
177  @Override
178  public void configureBackend(BackupBackendCfg config, ServerContext serverContext) throws ConfigException
179  {
180    // Make sure that a configuration entry was provided.  If not, then we will
181    // not be able to complete initialization.
182    if (config == null)
183    {
184      throw new ConfigException(ERR_BACKEND_CONFIG_ENTRY_NULL.get(getBackendID()));
185    }
186    currentConfig = config;
187  }
188
189
190
191  /** {@inheritDoc} */
192  @Override
193  public void openBackend()
194         throws ConfigException, InitializationException
195  {
196    // Create the set of base DNs that we will handle.  In this case, it's just
197    // the DN of the base backup entry.
198    try
199    {
200      backupBaseDN = DN.valueOf(DN_BACKUP_ROOT);
201    }
202    catch (Exception e)
203    {
204      logger.traceException(e);
205
206      LocalizableMessage message =
207          ERR_BACKEND_CANNOT_DECODE_BACKEND_ROOT_DN.get(getExceptionMessage(e), getBackendID());
208      throw new InitializationException(message, e);
209    }
210
211    // FIXME -- Deal with this more correctly.
212    this.baseDNs = new DN[] { backupBaseDN };
213
214
215    // Determine the set of backup directories that we will use by default.
216    Set<String> values = currentConfig.getBackupDirectory();
217    backupDirectories = new LinkedHashMap<>(values.size());
218    for (String s : values)
219    {
220      File dir = getFileForPath(s);
221      backupDirectories.put(dir, new CachedBackupDirectory(dir));
222    }
223
224
225    // Construct the backup base entry.
226    LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<>(2);
227    objectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP);
228
229    ObjectClass untypedOC =
230         DirectoryServer.getObjectClass(OC_UNTYPED_OBJECT_LC, true);
231    objectClasses.put(untypedOC, OC_UNTYPED_OBJECT);
232
233    LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0);
234    LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(1);
235
236    for (AVA ava : backupBaseDN.rdn())
237    {
238      AttributeType attrType = ava.getAttributeType();
239      userAttrs.put(attrType, Attributes.createAsList(attrType, ava.getAttributeValue()));
240    }
241
242    backupBaseEntry = new Entry(backupBaseDN, objectClasses, userAttrs, opAttrs);
243
244    currentConfig.addBackupChangeListener(this);
245
246    // Register the backup base as a private suffix.
247    try
248    {
249      DirectoryServer.registerBaseDN(backupBaseDN, this, true);
250    }
251    catch (Exception e)
252    {
253      logger.traceException(e);
254
255      LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
256          backupBaseDN, getExceptionMessage(e));
257      throw new InitializationException(message, e);
258    }
259  }
260
261
262
263  /** {@inheritDoc} */
264  @Override
265  public void closeBackend()
266  {
267    currentConfig.removeBackupChangeListener(this);
268
269    try
270    {
271      DirectoryServer.deregisterBaseDN(backupBaseDN);
272    }
273    catch (Exception e)
274    {
275      logger.traceException(e);
276    }
277  }
278
279
280
281  /** {@inheritDoc} */
282  @Override
283  public DN[] getBaseDNs()
284  {
285    return baseDNs;
286  }
287
288
289
290  /** {@inheritDoc} */
291  @Override
292  public long getEntryCount()
293  {
294    int numEntries = 1;
295
296    AttributeType backupPathType =
297         DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
298
299    for (File dir : backupDirectories.keySet())
300    {
301      try
302      {
303        // Check to see if the descriptor file exists.  If not, then skip this
304        // backup directory.
305        File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
306        if (! descriptorFile.exists())
307        {
308          continue;
309        }
310
311        DN backupDirDN = makeChildDN(backupBaseDN, backupPathType,
312                                     dir.getAbsolutePath());
313        getBackupDirectoryEntry(backupDirDN);
314        numEntries++;
315      }
316      catch (Exception e) {}
317    }
318
319    return numEntries;
320  }
321
322
323
324  /** {@inheritDoc} */
325  @Override
326  public boolean isIndexed(AttributeType attributeType, IndexType indexType)
327  {
328    // All searches in this backend will always be considered indexed.
329    return true;
330  }
331
332
333
334  /** {@inheritDoc} */
335  @Override
336  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
337  {
338    long ret = getNumberOfSubordinates(entryDN, false);
339    if(ret < 0)
340    {
341      return ConditionResult.UNDEFINED;
342    }
343    return ConditionResult.valueOf(ret != 0);
344  }
345
346  /** {@inheritDoc} */
347  @Override
348  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
349    checkNotNull(baseDN, "baseDN must not be null");
350    return getNumberOfSubordinates(baseDN, true) + 1;
351  }
352
353  /** {@inheritDoc} */
354  @Override
355  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
356    checkNotNull(parentDN, "parentDN must not be null");
357    return getNumberOfSubordinates(parentDN, false);
358  }
359
360  private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException
361  {
362    // If the requested entry was the backend base entry, then return
363    // the number of backup directories.
364    if (backupBaseDN.equals(entryDN))
365    {
366      long count = 0;
367      for (File dir : backupDirectories.keySet())
368      {
369        // Check to see if the descriptor file exists.  If not, then skip this
370        // backup directory.
371        File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
372        if (! descriptorFile.exists())
373        {
374          continue;
375        }
376
377        // If subtree is included, count the number of entries for each
378        // backup directory.
379        if (includeSubtree)
380        {
381          count++;
382          try
383          {
384            BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory();
385            count += backupDirectory.getBackups().keySet().size();
386          }
387          catch (Exception e)
388          {
389            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(
390                entryDN, e.getMessage()));
391          }
392        }
393
394        count ++;
395      }
396      return count;
397    }
398
399    // See if the requested entry was one level below the backend base entry.
400    // If so, then it must point to a backup directory.  Otherwise, it must be
401    // two levels below the backup base entry and must point to a specific
402    // backup.
403    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
404    if (parentDN == null)
405    {
406      return -1;
407    }
408    else if (backupBaseDN.equals(parentDN))
409    {
410      long count = 0;
411      Entry backupDirEntry = getBackupDirectoryEntry(entryDN);
412
413      AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
414      List<Attribute> attrList = backupDirEntry.getAttribute(t);
415      for (ByteString v : attrList.get(0))
416      {
417        try
418        {
419          File dir = new File(v.toString());
420          BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory();
421          count += backupDirectory.getBackups().keySet().size();
422        }
423        catch (Exception e)
424        {
425          return -1;
426        }
427      }
428      return count;
429    }
430    else if (backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN)))
431    {
432      return 0;
433    }
434    else
435    {
436      return -1;
437    }
438  }
439
440  /** {@inheritDoc} */
441  @Override
442  public Entry getEntry(DN entryDN)
443         throws DirectoryException
444  {
445    // If the requested entry was null, then throw an exception.
446    if (entryDN == null)
447    {
448      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
449          ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID()));
450    }
451
452
453    // If the requested entry was the backend base entry, then retrieve it.
454    if (entryDN.equals(backupBaseDN))
455    {
456      return backupBaseEntry.duplicate(true);
457    }
458
459
460    // See if the requested entry was one level below the backend base entry.
461    // If so, then it must point to a backup directory.  Otherwise, it must be
462    // two levels below the backup base entry and must point to a specific
463    // backup.
464    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
465    if (parentDN == null)
466    {
467      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
468          ERR_BACKUP_INVALID_BASE.get(entryDN));
469    }
470    else if (parentDN.equals(backupBaseDN))
471    {
472      return getBackupDirectoryEntry(entryDN);
473    }
474    else if (backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN)))
475    {
476      return getBackupEntry(entryDN);
477    }
478    else
479    {
480      LocalizableMessage message = ERR_BACKUP_INVALID_BASE.get(entryDN);
481      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
482              message, backupBaseDN, null);
483    }
484  }
485
486
487
488  /**
489   * Generates an entry for a backup directory based on the provided DN.  The
490   * DN must contain an RDN component that specifies the path to the backup
491   * directory, and that directory must exist and be a valid backup directory.
492   *
493   * @param  entryDN  The DN of the backup directory entry to retrieve.
494   *
495   * @return  The requested backup directory entry.
496   *
497   * @throws  DirectoryException  If the specified directory does not exist or
498   *                              is not a valid backup directory, or if the DN
499   *                              does not specify any backup directory.
500   */
501  private Entry getBackupDirectoryEntry(DN entryDN)
502         throws DirectoryException
503  {
504    // Make sure that the DN specifies a backup directory.
505    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
506    ByteString v = entryDN.rdn().getAttributeValue(t);
507    if (v == null)
508    {
509      LocalizableMessage message =
510          ERR_BACKUP_DN_DOES_NOT_SPECIFY_DIRECTORY.get(entryDN);
511      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
512                                   backupBaseDN, null);
513    }
514
515
516    // Get a handle to the backup directory and the information that it
517    // contains.
518    BackupDirectory backupDirectory;
519    try
520    {
521      File dir = new File(v.toString());
522      backupDirectory = backupDirectories.get(dir).getBackupDirectory();
523    }
524    catch (ConfigException ce)
525    {
526      logger.traceException(ce);
527
528      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
529          ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(entryDN, ce.getMessage()));
530    }
531    catch (Exception e)
532    {
533      logger.traceException(e);
534
535      LocalizableMessage message =
536          ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
537      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
538                                   message);
539    }
540
541
542    // Construct the backup directory entry to return.
543    LinkedHashMap<ObjectClass,String> ocMap = new LinkedHashMap<>(2);
544    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
545
546    ObjectClass backupDirOC =
547         DirectoryServer.getObjectClass(OC_BACKUP_DIRECTORY, true);
548    ocMap.put(backupDirOC, OC_BACKUP_DIRECTORY);
549
550    LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0);
551    LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(3);
552    userAttrs.put(t, asList(t, v));
553
554    t = DirectoryServer.getAttributeType(ATTR_BACKUP_BACKEND_DN);
555    userAttrs.put(t, asList(t, ByteString.valueOfUtf8(backupDirectory.getConfigEntryDN().toString())));
556
557    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
558    e.processVirtualAttributes();
559    return e;
560  }
561
562
563
564  /**
565   * Generates an entry for a backup based on the provided DN.  The DN must
566   * have an RDN component that specifies the backup ID, and the parent DN must
567   * have an RDN component that specifies the backup directory.
568   *
569   * @param  entryDN  The DN of the backup entry to retrieve.
570   *
571   * @return  The requested backup entry.
572   *
573   * @throws  DirectoryException  If the specified backup does not exist or is
574   *                              invalid.
575   */
576  private Entry getBackupEntry(DN entryDN)
577          throws DirectoryException
578  {
579    // First, get the backup ID from the entry DN.
580    AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID);
581    ByteString idValue = entryDN.rdn().getAttributeValue(idType);
582    if (idValue == null) {
583      throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(entryDN));
584    }
585    String backupID = idValue.toString();
586
587    // Next, get the backup directory from the parent DN.
588    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
589    if (parentDN == null) {
590      throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_PARENT_DN.get(entryDN));
591    }
592
593    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
594    ByteString v = parentDN.rdn().getAttributeValue(t);
595    if (v == null) {
596      throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(entryDN));
597    }
598
599    BackupDirectory backupDirectory;
600    try {
601      backupDirectory = backupDirectories.get(new File(v.toString())).getBackupDirectory();
602    } catch (ConfigException ce) {
603      logger.traceException(ce);
604
605      throw newConstraintViolation(ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(entryDN, ce.getMessageObject()));
606    } catch (Exception e) {
607      logger.traceException(e);
608
609      LocalizableMessage message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY
610          .get(getExceptionMessage(e));
611      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
612          message);
613    }
614
615    BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
616    if (backupInfo == null) {
617      LocalizableMessage message = ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory
618          .getPath());
619      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
620          parentDN, null);
621    }
622
623    // Construct the backup entry to return.
624    LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(3);
625    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
626
627    ObjectClass oc = DirectoryServer.getObjectClass(OC_BACKUP_INFO, true);
628    ocMap.put(oc, OC_BACKUP_INFO);
629
630    oc = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
631    ocMap.put(oc, OC_EXTENSIBLE_OBJECT);
632
633    LinkedHashMap<AttributeType, List<Attribute>> opAttrs = new LinkedHashMap<>(0);
634    LinkedHashMap<AttributeType, List<Attribute>> userAttrs = new LinkedHashMap<>();
635    userAttrs.put(idType, asList(idType, idValue));
636
637    backupInfo.getBackupDirectory();
638    userAttrs.put(t, asList(t, v));
639
640    Date backupDate = backupInfo.getBackupDate();
641    if (backupDate != null) {
642      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DATE);
643      userAttrs.put(t,
644          asList(t, ByteString.valueOfUtf8(GeneralizedTimeSyntax.format(backupDate))));
645    }
646
647    putBoolean(userAttrs, ATTR_BACKUP_COMPRESSED, backupInfo.isCompressed());
648    putBoolean(userAttrs, ATTR_BACKUP_ENCRYPTED, backupInfo.isEncrypted());
649    putBoolean(userAttrs, ATTR_BACKUP_INCREMENTAL, backupInfo.isIncremental());
650
651    HashSet<String> dependencies = backupInfo.getDependencies();
652    if (dependencies != null && !dependencies.isEmpty()) {
653      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DEPENDENCY);
654      AttributeBuilder builder = new AttributeBuilder(t);
655      builder.addAllStrings(dependencies);
656      userAttrs.put(t, builder.toAttributeList());
657    }
658
659    byte[] signedHash = backupInfo.getSignedHash();
660    if (signedHash != null) {
661      putByteString(userAttrs, ATTR_BACKUP_SIGNED_HASH, signedHash);
662    }
663
664    byte[] unsignedHash = backupInfo.getUnsignedHash();
665    if (unsignedHash != null) {
666      putByteString(userAttrs, ATTR_BACKUP_UNSIGNED_HASH, unsignedHash);
667    }
668
669    HashMap<String, String> properties = backupInfo.getBackupProperties();
670    if (properties != null && !properties.isEmpty()) {
671      for (Map.Entry<String, String> e : properties.entrySet()) {
672        t = DirectoryServer.getAttributeType(toLowerCase(e.getKey()));
673        userAttrs.put(t, asList(t, ByteString.valueOfUtf8(e.getValue())));
674      }
675    }
676
677    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
678    e.processVirtualAttributes();
679    return e;
680  }
681
682  private void putByteString(LinkedHashMap<AttributeType, List<Attribute>> userAttrs, String attrName, byte[] value)
683  {
684    AttributeType t = DirectoryServer.getAttributeType(attrName);
685    userAttrs.put(t, asList(t, ByteString.wrap(value)));
686  }
687
688  private void putBoolean(LinkedHashMap<AttributeType, List<Attribute>> attrsMap, String attrName, boolean value)
689  {
690    AttributeType t = DirectoryServer.getAttributeType(attrName);
691    attrsMap.put(t, asList(t, createBooleanValue(value)));
692  }
693
694  private List<Attribute> asList(AttributeType attrType, ByteString value)
695  {
696    return Attributes.createAsList(attrType, value);
697  }
698
699  private DirectoryException newConstraintViolation(LocalizableMessage message)
700  {
701    return new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
702  }
703
704  /** {@inheritDoc} */
705  @Override
706  public void addEntry(Entry entry, AddOperation addOperation)
707         throws DirectoryException
708  {
709    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
710        ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID()));
711  }
712
713
714
715  /** {@inheritDoc} */
716  @Override
717  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
718         throws DirectoryException
719  {
720    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
721        ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID()));
722  }
723
724
725
726  /** {@inheritDoc} */
727  @Override
728  public void replaceEntry(Entry oldEntry, Entry newEntry,
729      ModifyOperation modifyOperation) throws DirectoryException
730  {
731    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
732        ERR_BACKEND_MODIFY_NOT_SUPPORTED.get(oldEntry.getName(), getBackendID()));
733  }
734
735
736
737  /** {@inheritDoc} */
738  @Override
739  public void renameEntry(DN currentDN, Entry entry,
740                                   ModifyDNOperation modifyDNOperation)
741         throws DirectoryException
742  {
743    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
744        ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID()));
745  }
746
747
748
749  /** {@inheritDoc} */
750  @Override
751  public void search(SearchOperation searchOperation)
752         throws DirectoryException
753  {
754    // Get the base entry for the search, if possible.  If it doesn't exist,
755    // then this will throw an exception.
756    DN    baseDN    = searchOperation.getBaseDN();
757    Entry baseEntry = getEntry(baseDN);
758
759
760    // Look at the base DN and see if it's the backup base DN, a backup
761    // directory entry DN, or a backup entry DN.
762    DN parentDN;
763    SearchScope  scope  = searchOperation.getScope();
764    SearchFilter filter = searchOperation.getFilter();
765    if (backupBaseDN.equals(baseDN))
766    {
767      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
768          && filter.matchesEntry(baseEntry))
769      {
770        searchOperation.returnEntry(baseEntry, null);
771      }
772
773      if (scope != SearchScope.BASE_OBJECT && !backupDirectories.isEmpty())
774      {
775        AttributeType backupPathType =
776             DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
777        for (File dir : backupDirectories.keySet())
778        {
779          // Check to see if the descriptor file exists.  If not, then skip this
780          // backup directory.
781          File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
782          if (! descriptorFile.exists())
783          {
784            continue;
785          }
786
787
788          DN backupDirDN = makeChildDN(backupBaseDN, backupPathType,
789                                       dir.getAbsolutePath());
790
791          Entry backupDirEntry;
792          try
793          {
794            backupDirEntry = getBackupDirectoryEntry(backupDirDN);
795          }
796          catch (Exception e)
797          {
798            logger.traceException(e);
799
800            continue;
801          }
802
803          if (filter.matchesEntry(backupDirEntry))
804          {
805            searchOperation.returnEntry(backupDirEntry, null);
806          }
807
808          if (scope != SearchScope.SINGLE_LEVEL)
809          {
810            List<Attribute> attrList = backupDirEntry.getAttribute(backupPathType);
811            returnEntries(searchOperation, backupDirDN, filter, attrList);
812          }
813        }
814      }
815    }
816    else if (backupBaseDN.equals(parentDN = DirectoryServer.getParentDNInSuffix(baseDN)))
817    {
818      Entry backupDirEntry = getBackupDirectoryEntry(baseDN);
819
820      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
821          && filter.matchesEntry(backupDirEntry))
822      {
823        searchOperation.returnEntry(backupDirEntry, null);
824      }
825
826
827      if (scope != SearchScope.BASE_OBJECT)
828      {
829        AttributeType t =
830             DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH);
831        List<Attribute> attrList = backupDirEntry.getAttribute(t);
832        returnEntries(searchOperation, baseDN, filter, attrList);
833      }
834    }
835    else
836    {
837      if (parentDN == null
838          || !backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN)))
839      {
840        LocalizableMessage message = ERR_BACKUP_NO_SUCH_ENTRY.get(backupBaseDN);
841        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
842      }
843
844      if (scope == SearchScope.BASE_OBJECT ||
845          scope == SearchScope.WHOLE_SUBTREE)
846      {
847        Entry backupEntry = getBackupEntry(baseDN);
848        if (backupEntry == null)
849        {
850          LocalizableMessage message = ERR_BACKUP_NO_SUCH_ENTRY.get(backupBaseDN);
851          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
852        }
853
854        if (filter.matchesEntry(backupEntry))
855        {
856          searchOperation.returnEntry(backupEntry, null);
857        }
858      }
859    }
860  }
861
862  private void returnEntries(SearchOperation searchOperation, DN baseDN, SearchFilter filter, List<Attribute> attrList)
863  {
864    for (ByteString v : attrList.get(0))
865    {
866      try
867      {
868        File dir = new File(v.toString());
869        BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory();
870        AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID);
871
872        for (String backupID : backupDirectory.getBackups().keySet())
873        {
874          DN backupEntryDN = makeChildDN(baseDN, idType, backupID);
875          Entry backupEntry = getBackupEntry(backupEntryDN);
876          if (filter.matchesEntry(backupEntry))
877          {
878            searchOperation.returnEntry(backupEntry, null);
879          }
880        }
881      }
882      catch (Exception e)
883      {
884        logger.traceException(e);
885
886        continue;
887      }
888    }
889  }
890
891  @Override
892  public Set<String> getSupportedControls()
893  {
894    return Collections.emptySet();
895  }
896
897  @Override
898  public Set<String> getSupportedFeatures()
899  {
900    return Collections.emptySet();
901  }
902
903  @Override
904  public boolean supports(BackendOperation backendOperation)
905  {
906    return false;
907  }
908
909  @Override
910  public void exportLDIF(LDIFExportConfig exportConfig)
911         throws DirectoryException
912  {
913    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
914        ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
915  }
916
917  /** {@inheritDoc} */
918  @Override
919  public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
920      throws DirectoryException
921  {
922    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
923        ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
924  }
925
926  /** {@inheritDoc} */
927  @Override
928  public void createBackup(BackupConfig backupConfig)
929  throws DirectoryException
930  {
931    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
932        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
933  }
934
935  /** {@inheritDoc} */
936  @Override
937  public void removeBackup(BackupDirectory backupDirectory,
938                           String backupID)
939         throws DirectoryException
940  {
941    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
942        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
943  }
944
945  /** {@inheritDoc} */
946  @Override
947  public void restoreBackup(RestoreConfig restoreConfig)
948         throws DirectoryException
949  {
950    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
951        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
952  }
953
954  /** {@inheritDoc} */
955  @Override
956  public boolean isConfigurationChangeAcceptable(
957       BackupBackendCfg cfg, List<LocalizableMessage> unacceptableReasons)
958  {
959    // We'll accept anything here.  The only configurable attribute is the
960    // default set of backup directories, but that doesn't require any
961    // validation at this point.
962    return true;
963  }
964
965  /** {@inheritDoc} */
966  @Override
967  public ConfigChangeResult applyConfigurationChange(BackupBackendCfg cfg)
968  {
969    final ConfigChangeResult ccr = new ConfigChangeResult();
970
971    Set<String> values = cfg.getBackupDirectory();
972    backupDirectories = new LinkedHashMap<>(values.size());
973    for (String s : values)
974    {
975      File dir = getFileForPath(s);
976      backupDirectories.put(dir, new CachedBackupDirectory(dir));
977    }
978
979    currentConfig = cfg;
980    return ccr;
981  }
982
983  /**
984   * Create a new child DN from a given parent DN.  The child RDN is formed
985   * from a given attribute type and string value.
986   * @param parentDN The DN of the parent.
987   * @param rdnAttrType The attribute type of the RDN.
988   * @param rdnStringValue The string value of the RDN.
989   * @return A new child DN.
990   */
991  public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType,
992                               String rdnStringValue)
993  {
994    ByteString attrValue = ByteString.valueOfUtf8(rdnStringValue);
995    return parentDN.child(new RDN(rdnAttrType, attrValue));
996  }
997}