/**
   * Generates an entry for a backup directory based on the provided DN. The DN must contain an RDN
   * component that specifies the path to the backup directory, and that directory must exist and be
   * a valid backup directory.
   *
   * @param entryDN The DN of the backup directory entry to retrieve.
   * @return The requested backup directory entry.
   * @throws DirectoryException If the specified directory does not exist or is not a valid backup
   *     directory, or if the DN does not specify any backup directory.
   */
  private Entry getBackupDirectoryEntry(DN entryDN) throws DirectoryException {
    // Make sure that the DN specifies a backup directory.
    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
    AttributeValue v = entryDN.getRDN().getAttributeValue(t);
    if (v == null) {
      Message message = ERR_BACKUP_DN_DOES_NOT_SPECIFY_DIRECTORY.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, backupBaseDN, null);
    }

    // Get a handle to the backup directory and the information that it
    // contains.
    BackupDirectory backupDirectory;
    try {
      backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
    } catch (ConfigException ce) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }

      Message message =
          ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(String.valueOf(entryDN), ce.getMessage());
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    // Construct the backup directory entry to return.
    LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<ObjectClass, String>(2);
    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);

    ObjectClass backupDirOC = DirectoryServer.getObjectClass(OC_BACKUP_DIRECTORY, true);
    ocMap.put(backupDirOC, OC_BACKUP_DIRECTORY);

    LinkedHashMap<AttributeType, List<Attribute>> opAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(0);
    LinkedHashMap<AttributeType, List<Attribute>> userAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(3);

    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, v));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_BACKEND_DN, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(
        Attributes.create(
            t, AttributeValues.create(t, backupDirectory.getConfigEntryDN().toString())));
    userAttrs.put(t, attrList);

    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }
  /** {@inheritDoc} */
  @Override
  public Entry getEntry(DN entryDN) throws DirectoryException {
    // If the requested entry was null, then throw an exception.
    if (entryDN == null) {
      throw new DirectoryException(
          DirectoryServer.getServerErrorResultCode(),
          ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID()));
    }

    // If the requested entry was the backend base entry, then retrieve it.
    if (entryDN.equals(backupBaseDN)) {
      return backupBaseEntry.duplicate(true);
    }

    // See if the requested entry was one level below the backend base entry.
    // If so, then it must point to a backup directory.  Otherwise, it must be
    // two levels below the backup base entry and must point to a specific
    // backup.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null) {
      Message message = ERR_BACKUP_INVALID_BASE.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
    } else if (parentDN.equals(backupBaseDN)) {
      return getBackupDirectoryEntry(entryDN);
    } else if (backupBaseDN.equals(parentDN.getParentDNInSuffix())) {
      return getBackupEntry(entryDN);
    } else {
      Message message = ERR_BACKUP_INVALID_BASE.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, backupBaseDN, null);
    }
  }
 /**
  * Create a new child DN from a given parent DN. The child RDN is formed from a given attribute
  * type and string value.
  *
  * @param parentDN The DN of the parent.
  * @param rdnAttrType The attribute type of the RDN.
  * @param rdnStringValue The string value of the RDN.
  * @return A new child DN.
  */
 public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType, String rdnStringValue) {
   AttributeValue attrValue = AttributeValues.create(rdnAttrType, rdnStringValue);
   return parentDN.concat(RDN.create(rdnAttrType, attrValue));
 }
  /** {@inheritDoc} */
  @Override
  public void search(SearchOperation searchOperation) throws DirectoryException {
    // Get the base entry for the search, if possible.  If it doesn't exist,
    // then this will throw an exception.
    DN baseDN = searchOperation.getBaseDN();
    Entry baseEntry = getEntry(baseDN);

    // Look at the base DN and see if it's the backup base DN, a backup
    // directory entry DN, or a backup entry DN.
    DN parentDN;
    SearchScope scope = searchOperation.getScope();
    SearchFilter filter = searchOperation.getFilter();
    if (backupBaseDN.equals(baseDN)) {
      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
          && filter.matchesEntry(baseEntry)) {
        searchOperation.returnEntry(baseEntry, null);
      }

      if (scope != SearchScope.BASE_OBJECT && !backupDirectories.isEmpty()) {
        AttributeType backupPathType =
            DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
        for (File f : backupDirectories) {
          // Check to see if the descriptor file exists.  If not, then skip this
          // backup directory.
          File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
          if (!descriptorFile.exists()) {
            continue;
          }

          DN backupDirDN = makeChildDN(backupBaseDN, backupPathType, f.getAbsolutePath());

          Entry backupDirEntry;
          try {
            backupDirEntry = getBackupDirectoryEntry(backupDirDN);
          } catch (Exception e) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            continue;
          }

          if (filter.matchesEntry(backupDirEntry)) {
            searchOperation.returnEntry(backupDirEntry, null);
          }

          if (scope != SearchScope.SINGLE_LEVEL) {
            List<Attribute> attrList = backupDirEntry.getAttribute(backupPathType);
            if (attrList != null && !attrList.isEmpty()) {
              for (AttributeValue v : attrList.get(0)) {
                try {
                  BackupDirectory backupDirectory =
                      BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
                  AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID, true);
                  for (String backupID : backupDirectory.getBackups().keySet()) {
                    DN backupEntryDN = makeChildDN(backupDirDN, idType, backupID);
                    Entry backupEntry = getBackupEntry(backupEntryDN);
                    if (filter.matchesEntry(backupEntry)) {
                      searchOperation.returnEntry(backupEntry, null);
                    }
                  }
                } catch (Exception e) {
                  if (debugEnabled()) {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }

                  continue;
                }
              }
            }
          }
        }
      }
    } else if (backupBaseDN.equals(parentDN = baseDN.getParentDNInSuffix())) {
      Entry backupDirEntry = getBackupDirectoryEntry(baseDN);

      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
          && filter.matchesEntry(backupDirEntry)) {
        searchOperation.returnEntry(backupDirEntry, null);
      }

      if (scope != SearchScope.BASE_OBJECT) {
        AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
        List<Attribute> attrList = backupDirEntry.getAttribute(t);
        if (attrList != null && !attrList.isEmpty()) {
          for (AttributeValue v : attrList.get(0)) {
            try {
              BackupDirectory backupDirectory =
                  BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
              AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID, true);
              for (String backupID : backupDirectory.getBackups().keySet()) {
                DN backupEntryDN = makeChildDN(baseDN, idType, backupID);
                Entry backupEntry = getBackupEntry(backupEntryDN);
                if (filter.matchesEntry(backupEntry)) {
                  searchOperation.returnEntry(backupEntry, null);
                }
              }
            } catch (Exception e) {
              if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }

              continue;
            }
          }
        }
      }
    } else {
      if (parentDN == null || !backupBaseDN.equals(parentDN.getParentDNInSuffix())) {
        Message message = ERR_BACKUP_NO_SUCH_ENTRY.get(String.valueOf(backupBaseDN));
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
      }

      if (scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE) {
        Entry backupEntry = getBackupEntry(baseDN);
        if (backupEntry == null) {
          Message message = ERR_BACKUP_NO_SUCH_ENTRY.get(String.valueOf(backupBaseDN));
          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
        }

        if (filter.matchesEntry(backupEntry)) {
          searchOperation.returnEntry(backupEntry, null);
        }
      }
    }
  }
  /**
   * Generates an entry for a backup based on the provided DN. The DN must have an RDN component
   * that specifies the backup ID, and the parent DN must have an RDN component that specifies the
   * backup directory.
   *
   * @param entryDN The DN of the backup entry to retrieve.
   * @return The requested backup entry.
   * @throws DirectoryException If the specified backup does not exist or is invalid.
   */
  private Entry getBackupEntry(DN entryDN) throws DirectoryException {
    // First, get the backup ID from the entry DN.
    AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID, true);
    AttributeValue idValue = entryDN.getRDN().getAttributeValue(idType);
    if (idValue == null) {
      Message message = ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }
    String backupID = idValue.getValue().toString();

    // Next, get the backup directory from the parent DN.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null) {
      Message message = ERR_BACKUP_NO_BACKUP_PARENT_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
    AttributeValue v = parentDN.getRDN().getAttributeValue(t);
    if (v == null) {
      Message message = ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    BackupDirectory backupDirectory;
    try {
      backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
    } catch (ConfigException ce) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }

      Message message =
          ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(String.valueOf(entryDN), ce.getMessageObject());
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
    if (backupInfo == null) {
      Message message = ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory.getPath());
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, parentDN, null);
    }

    // Construct the backup entry to return.
    LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<ObjectClass, String>(3);
    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);

    ObjectClass oc = DirectoryServer.getObjectClass(OC_BACKUP_INFO, true);
    ocMap.put(oc, OC_BACKUP_INFO);

    oc = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
    ocMap.put(oc, OC_EXTENSIBLE_OBJECT);

    LinkedHashMap<AttributeType, List<Attribute>> opAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(0);
    LinkedHashMap<AttributeType, List<Attribute>> userAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>();

    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(idType, idValue));
    userAttrs.put(idType, attrList);

    backupInfo.getBackupDirectory();
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, v));
    userAttrs.put(t, attrList);

    Date backupDate = backupInfo.getBackupDate();
    if (backupDate != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DATE, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(
          Attributes.create(
              t, AttributeValues.create(t, GeneralizedTimeSyntax.format(backupDate))));
      userAttrs.put(t, attrList);
    }

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_COMPRESSED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isCompressed())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_ENCRYPTED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isEncrypted())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_INCREMENTAL, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(
        Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isIncremental())));
    userAttrs.put(t, attrList);

    HashSet<String> dependencies = backupInfo.getDependencies();
    if (dependencies != null && !dependencies.isEmpty()) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DEPENDENCY, true);
      AttributeBuilder builder = new AttributeBuilder(t);
      for (String s : dependencies) {
        builder.add(AttributeValues.create(t, s));
      }
      attrList = new ArrayList<Attribute>(1);
      attrList.add(builder.toAttribute());
      userAttrs.put(t, attrList);
    }

    byte[] signedHash = backupInfo.getSignedHash();
    if (signedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_SIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(signedHash))));
      userAttrs.put(t, attrList);
    }

    byte[] unsignedHash = backupInfo.getUnsignedHash();
    if (unsignedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_UNSIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(unsignedHash))));
      userAttrs.put(t, attrList);
    }

    HashMap<String, String> properties = backupInfo.getBackupProperties();
    if (properties != null && !properties.isEmpty()) {
      for (Map.Entry<String, String> e : properties.entrySet()) {
        t = DirectoryServer.getAttributeType(toLowerCase(e.getKey()), true);
        attrList = new ArrayList<Attribute>(1);
        attrList.add(Attributes.create(t, AttributeValues.create(t, e.getValue())));
        userAttrs.put(t, attrList);
      }
    }

    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }
  /** {@inheritDoc} */
  @Override
  public long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException {
    // If the requested entry was null, then return undefined.
    if (entryDN == null) {
      return -1;
    }

    // If the requested entry was the backend base entry, then return
    // the number of backup directories.
    if (backupBaseDN.equals(entryDN)) {
      long count = 0;
      for (File f : backupDirectories) {
        // Check to see if the descriptor file exists.  If not, then skip this
        // backup directory.
        File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
        if (!descriptorFile.exists()) {
          continue;
        }

        // If subtree is included, count the number of entries for each
        // backup directory.
        if (subtree) {
          try {
            BackupDirectory backupDirectory =
                BackupDirectory.readBackupDirectoryDescriptor(f.getPath());
            count += backupDirectory.getBackups().keySet().size();
          } catch (Exception e) {
            return -1;
          }
        }

        count++;
      }
      return count;
    }

    // See if the requested entry was one level below the backend base entry.
    // If so, then it must point to a backup directory.  Otherwise, it must be
    // two levels below the backup base entry and must point to a specific
    // backup.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null) {
      return -1;
    } else if (backupBaseDN.equals(parentDN)) {
      long count = 0;
      Entry backupDirEntry = getBackupDirectoryEntry(entryDN);

      AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
      List<Attribute> attrList = backupDirEntry.getAttribute(t);
      if (attrList != null && !attrList.isEmpty()) {
        for (AttributeValue v : attrList.get(0)) {
          try {
            BackupDirectory backupDirectory =
                BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
            count += backupDirectory.getBackups().keySet().size();
          } catch (Exception e) {
            return -1;
          }
        }
      }
      return count;
    } else if (backupBaseDN.equals(parentDN.getParentDNInSuffix())) {
      return 0;
    } else {
      return -1;
    }
  }
  /** {@inheritDoc} */
  @Override
  public void initializeBackend() throws ConfigException, InitializationException {
    // Create the set of base DNs that we will handle.  In this case, it's just
    // the DN of the base backup entry.
    try {
      backupBaseDN = DN.decode(DN_BACKUP_ROOT);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message =
          ERR_BACKEND_CANNOT_DECODE_BACKEND_ROOT_DN.get(getExceptionMessage(e), getBackendID());
      throw new InitializationException(message, e);
    }

    // FIXME -- Deal with this more correctly.
    this.baseDNs = new DN[] {backupBaseDN};

    // Determine the set of backup directories that we will use by default.
    Set<String> values = currentConfig.getBackupDirectory();
    backupDirectories = new LinkedHashSet<File>(values.size());
    for (String s : values) {
      backupDirectories.add(getFileForPath(s));
    }

    // Construct the backup base entry.
    LinkedHashMap<ObjectClass, String> objectClasses = new LinkedHashMap<ObjectClass, String>(2);
    objectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP);

    ObjectClass untypedOC = DirectoryServer.getObjectClass(OC_UNTYPED_OBJECT_LC, true);
    objectClasses.put(untypedOC, OC_UNTYPED_OBJECT);

    LinkedHashMap<AttributeType, List<Attribute>> opAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(0);
    LinkedHashMap<AttributeType, List<Attribute>> userAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(1);

    RDN rdn = backupBaseDN.getRDN();
    int numAVAs = rdn.getNumValues();
    for (int i = 0; i < numAVAs; i++) {
      AttributeType attrType = rdn.getAttributeType(i);
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(attrType, rdn.getAttributeValue(i)));

      userAttrs.put(attrType, attrList);
    }

    backupBaseEntry = new Entry(backupBaseDN, objectClasses, userAttrs, opAttrs);

    currentConfig.addBackupChangeListener(this);

    // Register the backup base as a private suffix.
    try {
      DirectoryServer.registerBaseDN(backupBaseDN, this, true);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message =
          ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(backupBaseDN.toString(), getExceptionMessage(e));
      throw new InitializationException(message, e);
    }
  }