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