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