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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.backends;
028    
029    
030    
031    import java.io.File;
032    import java.util.*;
033    
034    import org.opends.messages.Message;
035    import org.opends.server.admin.Configuration;
036    import org.opends.server.admin.server.ConfigurationChangeListener;
037    import org.opends.server.admin.std.server.BackupBackendCfg;
038    import org.opends.server.api.Backend;
039    import org.opends.server.config.ConfigException;
040    import org.opends.server.core.AddOperation;
041    import org.opends.server.core.DeleteOperation;
042    import org.opends.server.core.DirectoryServer;
043    import org.opends.server.core.ModifyOperation;
044    import org.opends.server.core.ModifyDNOperation;
045    import org.opends.server.core.SearchOperation;
046    import org.opends.server.loggers.debug.DebugTracer;
047    import org.opends.server.protocols.asn1.ASN1OctetString;
048    import org.opends.server.types.Attribute;
049    import org.opends.server.types.AttributeType;
050    import org.opends.server.types.AttributeValue;
051    import org.opends.server.types.BackupConfig;
052    import org.opends.server.types.BackupDirectory;
053    import org.opends.server.types.BackupInfo;
054    import org.opends.server.types.ConditionResult;
055    import org.opends.server.types.ConfigChangeResult;
056    import org.opends.server.types.DebugLogLevel;
057    import org.opends.server.types.DirectoryException;
058    import org.opends.server.types.DN;
059    import org.opends.server.types.Entry;
060    import org.opends.server.types.IndexType;
061    import org.opends.server.types.InitializationException;
062    import org.opends.server.types.LDIFExportConfig;
063    import org.opends.server.types.LDIFImportConfig;
064    import org.opends.server.types.LDIFImportResult;
065    import org.opends.server.types.ObjectClass;
066    import org.opends.server.types.RDN;
067    import org.opends.server.types.RestoreConfig;
068    import org.opends.server.types.ResultCode;
069    import org.opends.server.types.SearchFilter;
070    import org.opends.server.types.SearchScope;
071    import org.opends.server.schema.BooleanSyntax;
072    import org.opends.server.schema.GeneralizedTimeSyntax;
073    import org.opends.server.util.Validator;
074    
075    import static org.opends.messages.BackendMessages.*;
076    import static org.opends.server.config.ConfigConstants.*;
077    import static org.opends.server.loggers.debug.DebugLogger.*;
078    import static org.opends.server.util.ServerConstants.*;
079    import static org.opends.server.util.StaticUtils.*;
080    
081    
082    /**
083     * This class defines a backend used to present information about Directory
084     * Server backups.  It will not actually store anything, but upon request will
085     * retrieve information about the backups that it knows about.  The backups will
086     * be arranged in a hierarchy based on the directory that contains them, and
087     * it may be possible to dynamically discover new backups if a previously
088     * unknown backup directory is included in the base DN.
089     */
090    public class BackupBackend
091           extends Backend
092           implements ConfigurationChangeListener<BackupBackendCfg>
093    {
094      /**
095       * The tracer object for the debug logger.
096       */
097      private static final DebugTracer TRACER = getTracer();
098    
099    
100    
101      // The current configuration state.
102      private BackupBackendCfg currentConfig;
103    
104      // The DN for the base backup entry.
105      private DN backupBaseDN;
106    
107      // The set of base DNs for this backend.
108      private DN[] baseDNs;
109    
110      // The backup base entry.
111      private Entry backupBaseEntry;
112    
113      // The set of supported controls for this backend.
114      private HashSet<String> supportedControls;
115    
116      // The set of supported features for this backend.
117      private HashSet<String> supportedFeatures;
118    
119      // The set of predefined backup directories that we will use.
120      private LinkedHashSet<File> backupDirectories;
121    
122    
123    
124      /**
125       * Creates a new backend with the provided information.  All backend
126       * implementations must implement a default constructor that use
127       * <CODE>super()</CODE> to invoke this constructor.
128       */
129      public BackupBackend()
130      {
131        super();
132    
133        // Perform all initialization in initializeBackend.
134      }
135    
136    
137    
138      /**
139       * {@inheritDoc}
140       */
141      @Override()
142      public void configureBackend(Configuration config) throws ConfigException
143      {
144        // Make sure that a configuration entry was provided.  If not, then we will
145        // not be able to complete initialization.
146        if (config == null)
147        {
148          Message message = ERR_BACKUP_CONFIG_ENTRY_NULL.get();
149          throw new ConfigException(message);
150        }
151    
152    
153        Validator.ensureTrue(config instanceof BackupBackendCfg);
154    
155        currentConfig = (BackupBackendCfg)config;
156      }
157    
158    
159    
160      /**
161       * {@inheritDoc}
162       */
163      @Override()
164      public void initializeBackend()
165             throws ConfigException, InitializationException
166      {
167        // Create the set of base DNs that we will handle.  In this case, it's just
168        // the DN of the base backup entry.
169        try
170        {
171          backupBaseDN = DN.decode(DN_BACKUP_ROOT);
172        }
173        catch (Exception e)
174        {
175          if (debugEnabled())
176          {
177            TRACER.debugCaught(DebugLogLevel.ERROR, e);
178          }
179    
180          Message message =
181              ERR_BACKUP_CANNOT_DECODE_BACKUP_ROOT_DN.get(getExceptionMessage(e));
182          throw new InitializationException(message, e);
183        }
184    
185        // FIXME -- Deal with this more correctly.
186        this.baseDNs = new DN[] { backupBaseDN };
187    
188    
189        // Determine the set of backup directories that we will use by default.
190        Set<String> values = currentConfig.getBackupDirectory();
191        backupDirectories = new LinkedHashSet<File>(values.size());
192        for (String s : values)
193        {
194          backupDirectories.add(getFileForPath(s));
195        }
196    
197    
198        // Construct the backup base entry.
199        LinkedHashMap<ObjectClass,String> objectClasses =
200             new LinkedHashMap<ObjectClass,String>(2);
201        objectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP);
202    
203        ObjectClass untypedOC =
204             DirectoryServer.getObjectClass(OC_UNTYPED_OBJECT_LC, true);
205        objectClasses.put(untypedOC, OC_UNTYPED_OBJECT);
206    
207        LinkedHashMap<AttributeType,List<Attribute>> opAttrs =
208             new LinkedHashMap<AttributeType,List<Attribute>>(0);
209        LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
210             new LinkedHashMap<AttributeType,List<Attribute>>(1);
211    
212        RDN rdn = backupBaseDN.getRDN();
213        int numAVAs = rdn.getNumValues();
214        for (int i=0; i < numAVAs; i++)
215        {
216          LinkedHashSet<AttributeValue> valueSet =
217               new LinkedHashSet<AttributeValue>(1);
218          valueSet.add(rdn.getAttributeValue(i));
219    
220          AttributeType attrType = rdn.getAttributeType(i);
221          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
222          attrList.add(new Attribute(attrType, attrType.getNameOrOID(),
223                                     valueSet));
224    
225          userAttrs.put(attrType, attrList);
226        }
227    
228        backupBaseEntry = new Entry(backupBaseDN, objectClasses, userAttrs,
229                                    opAttrs);
230    
231    
232        // Define an empty sets for the supported controls and features.
233        supportedControls = new HashSet<String>(0);
234        supportedFeatures = new HashSet<String>(0);
235    
236    
237        // Register this as a change listener.
238        currentConfig.addBackupChangeListener(this);
239    
240    
241        // Register the backup base as a private suffix.
242        try
243        {
244          DirectoryServer.registerBaseDN(backupBaseDN, this, true);
245        }
246        catch (Exception e)
247        {
248          if (debugEnabled())
249          {
250            TRACER.debugCaught(DebugLogLevel.ERROR, e);
251          }
252    
253          Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
254              backupBaseDN.toString(), getExceptionMessage(e));
255          throw new InitializationException(message, e);
256        }
257      }
258    
259    
260    
261      /**
262       * {@inheritDoc}
263       */
264      @Override()
265      public void finalizeBackend()
266      {
267        currentConfig.removeBackupChangeListener(this);
268    
269        try
270        {
271          DirectoryServer.deregisterBaseDN(backupBaseDN);
272        }
273        catch (Exception e)
274        {
275          if (debugEnabled())
276          {
277            TRACER.debugCaught(DebugLogLevel.ERROR, e);
278          }
279        }
280      }
281    
282    
283    
284      /**
285       * {@inheritDoc}
286       */
287      @Override()
288      public DN[] getBaseDNs()
289      {
290        return baseDNs;
291      }
292    
293    
294    
295      /**
296       * {@inheritDoc}
297       */
298      @Override()
299      public long getEntryCount()
300      {
301        int numEntries = 1;
302    
303        AttributeType backupPathType =
304             DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
305    
306        for (File f : backupDirectories)
307        {
308          try
309          {
310            // Check to see if the descriptor file exists.  If not, then skip this
311            // backup directory.
312            File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
313            if (! descriptorFile.exists())
314            {
315              continue;
316            }
317    
318            DN backupDirDN = makeChildDN(backupBaseDN, backupPathType,
319                                         f.getAbsolutePath());
320            getBackupDirectoryEntry(backupDirDN);
321            numEntries++;
322          } catch (Exception e) {}
323        }
324    
325        return numEntries;
326      }
327    
328    
329    
330      /**
331       * {@inheritDoc}
332       */
333      @Override()
334      public boolean isLocal()
335      {
336        // For the purposes of this method, this is a local backend.
337        return true;
338      }
339    
340    
341    
342      /**
343       * {@inheritDoc}
344       */
345      @Override()
346      public boolean isIndexed(AttributeType attributeType, IndexType indexType)
347      {
348        // All searches in this backend will always be considered indexed.
349        return true;
350      }
351    
352    
353    
354      /**
355       * {@inheritDoc}
356       */
357      @Override()
358      public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
359      {
360        long ret = numSubordinates(entryDN, false);
361        if(ret < 0)
362        {
363          return ConditionResult.UNDEFINED;
364        }
365        else if(ret == 0)
366        {
367          return ConditionResult.FALSE;
368        }
369        else
370        {
371          return ConditionResult.TRUE;
372        }
373      }
374    
375    
376    
377      /**
378       * {@inheritDoc}
379       */
380      @Override()
381      public long numSubordinates(DN entryDN, boolean subtree)
382          throws DirectoryException
383      {
384        // If the requested entry was null, then return undefined.
385        if (entryDN == null)
386        {
387          return -1;
388        }
389    
390        // If the requested entry was the backend base entry, then return
391        // the number of backup directories.
392        if (backupBaseDN.equals(entryDN))
393        {
394          long count = 0;
395          for (File f : backupDirectories)
396          {
397            // Check to see if the descriptor file exists.  If not, then skip this
398            // backup directory.
399            File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
400            if (! descriptorFile.exists())
401            {
402              continue;
403            }
404    
405            // If subtree is included, count the number of entries for each
406            // backup directory.
407            if (subtree)
408            {
409              try
410              {
411                BackupDirectory backupDirectory =
412                    BackupDirectory.readBackupDirectoryDescriptor(f.getPath());
413                count += backupDirectory.getBackups().keySet().size();
414              }
415              catch (Exception e)
416              {
417                return -1;
418              }
419            }
420    
421            count ++;
422          }
423          return count;
424        }
425    
426        // See if the requested entry was one level below the backend base entry.
427        // If so, then it must point to a backup directory.  Otherwise, it must be
428        // two levels below the backup base entry and must point to a specific
429        // backup.
430        DN parentDN = entryDN.getParentDNInSuffix();
431        if (parentDN == null)
432        {
433          return -1;
434        }
435        else if (backupBaseDN.equals(parentDN))
436        {
437          long count = 0;
438          Entry backupDirEntry = getBackupDirectoryEntry(entryDN);
439    
440          AttributeType t =
441              DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
442          List<Attribute> attrList = backupDirEntry.getAttribute(t);
443          if ((attrList != null) && (! attrList.isEmpty()))
444          {
445            for (AttributeValue v : attrList.get(0).getValues())
446            {
447              try
448              {
449                BackupDirectory backupDirectory =
450                    BackupDirectory.readBackupDirectoryDescriptor(
451                        v.getStringValue());
452                count += backupDirectory.getBackups().keySet().size();
453              }
454              catch (Exception e)
455              {
456                return -1;
457              }
458            }
459          }
460          return count;
461        }
462        else if (backupBaseDN.equals(parentDN.getParentDNInSuffix()))
463        {
464          return 0;
465        }
466        else
467        {
468          return -1;
469        }
470      }
471    
472    
473    
474      /**
475       * {@inheritDoc}
476       */
477      @Override()
478      public Entry getEntry(DN entryDN)
479             throws DirectoryException
480      {
481        // If the requested entry was null, then throw an exception.
482        if (entryDN == null)
483        {
484          Message message = ERR_BACKUP_GET_ENTRY_NULL.get();
485          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
486                                       message);
487        }
488    
489    
490        // If the requested entry was the backend base entry, then retrieve it.
491        if (entryDN.equals(backupBaseDN))
492        {
493          return backupBaseEntry.duplicate(true);
494        }
495    
496    
497        // See if the requested entry was one level below the backend base entry.
498        // If so, then it must point to a backup directory.  Otherwise, it must be
499        // two levels below the backup base entry and must point to a specific
500        // backup.
501        DN parentDN = entryDN.getParentDNInSuffix();
502        if (parentDN == null)
503        {
504          Message message = ERR_BACKUP_INVALID_BASE.get(String.valueOf(entryDN));
505          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
506        }
507        else if (parentDN.equals(backupBaseDN))
508        {
509          return getBackupDirectoryEntry(entryDN);
510        }
511        else if (backupBaseDN.equals(parentDN.getParentDNInSuffix()))
512        {
513          return getBackupEntry(entryDN);
514        }
515        else
516        {
517          Message message = ERR_BACKUP_INVALID_BASE.get(String.valueOf(entryDN));
518          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
519                  message, backupBaseDN, null);
520        }
521      }
522    
523    
524    
525      /**
526       * Generates an entry for a backup directory based on the provided DN.  The
527       * DN must contain an RDN component that specifies the path to the backup
528       * directory, and that directory must exist and be a valid backup directory.
529       *
530       * @param  entryDN  The DN of the backup directory entry to retrieve.
531       *
532       * @return  The requested backup directory entry.
533       *
534       * @throws  DirectoryException  If the specified directory does not exist or
535       *                              is not a valid backup directory, or if the DN
536       *                              does not specify any backup directory.
537       */
538      private Entry getBackupDirectoryEntry(DN entryDN)
539             throws DirectoryException
540      {
541        // Make sure that the DN specifies a backup directory.
542        AttributeType t =
543             DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
544        AttributeValue v = entryDN.getRDN().getAttributeValue(t);
545        if (v == null)
546        {
547          Message message =
548              ERR_BACKUP_DN_DOES_NOT_SPECIFY_DIRECTORY.get(String.valueOf(entryDN));
549          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
550                                       backupBaseDN, null);
551        }
552    
553    
554        // Get a handle to the backup directory and the information that it
555        // contains.
556        BackupDirectory backupDirectory;
557        try
558        {
559          backupDirectory =
560               BackupDirectory.readBackupDirectoryDescriptor(v.getStringValue());
561        }
562        catch (ConfigException ce)
563        {
564          if (debugEnabled())
565          {
566            TRACER.debugCaught(DebugLogLevel.ERROR, ce);
567          }
568    
569          Message message = ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(
570              String.valueOf(entryDN), ce.getMessage());
571          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
572        }
573        catch (Exception e)
574        {
575          if (debugEnabled())
576          {
577            TRACER.debugCaught(DebugLogLevel.ERROR, e);
578          }
579    
580          Message message =
581              ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
582          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
583                                       message);
584        }
585    
586    
587        // Construct the backup directory entry to return.
588        LinkedHashMap<ObjectClass,String> ocMap =
589            new LinkedHashMap<ObjectClass,String>(2);
590        ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
591    
592        ObjectClass backupDirOC =
593             DirectoryServer.getObjectClass(OC_BACKUP_DIRECTORY, true);
594        ocMap.put(backupDirOC, OC_BACKUP_DIRECTORY);
595    
596        LinkedHashMap<AttributeType,List<Attribute>> opAttrs =
597             new LinkedHashMap<AttributeType,List<Attribute>>(0);
598        LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
599             new LinkedHashMap<AttributeType,List<Attribute>>(3);
600    
601        LinkedHashSet<AttributeValue> valueSet =
602             new LinkedHashSet<AttributeValue>(1);
603        valueSet.add(v);
604    
605        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
606        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
607        userAttrs.put(t, attrList);
608    
609    
610        t = DirectoryServer.getAttributeType(ATTR_BACKUP_BACKEND_DN, true);
611        valueSet = new LinkedHashSet<AttributeValue>(1);
612        valueSet.add(new AttributeValue(t,
613                              backupDirectory.getConfigEntryDN().toString()));
614        attrList = new ArrayList<Attribute>(1);
615        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
616        userAttrs.put(t, attrList);
617    
618    
619        Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
620        e.processVirtualAttributes();
621        return e;
622      }
623    
624    
625    
626      /**
627       * Generates an entry for a backup based on the provided DN.  The DN must
628       * have an RDN component that specifies the backup ID, and the parent DN must
629       * have an RDN component that specifies the backup directory.
630       *
631       * @param  entryDN  The DN of the backup entry to retrieve.
632       *
633       * @return  The requested backup entry.
634       *
635       * @throws  DirectoryException  If the specified backup does not exist or is
636       *                              invalid.
637       */
638      private Entry getBackupEntry(DN entryDN)
639              throws DirectoryException
640      {
641        // First, get the backup ID from the entry DN.
642        AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID,
643                                                                true);
644        AttributeValue idValue = entryDN.getRDN().getAttributeValue(idType);
645        if (idValue == null)
646        {
647          Message message =
648              ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(String.valueOf(entryDN));
649          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
650        }
651        String backupID = idValue.getStringValue();
652    
653    
654        // Next, get the backup directory from the parent DN.
655        DN parentDN = entryDN.getParentDNInSuffix();
656        if (parentDN == null)
657        {
658          Message message =
659              ERR_BACKUP_NO_BACKUP_PARENT_DN.get(String.valueOf(entryDN));
660          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
661        }
662    
663        AttributeType t =
664             DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
665        AttributeValue v = parentDN.getRDN().getAttributeValue(t);
666        if (v == null)
667        {
668          Message message =
669              ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(String.valueOf(entryDN));
670          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
671        }
672    
673    
674        BackupDirectory backupDirectory;
675        try
676        {
677          backupDirectory =
678               BackupDirectory.readBackupDirectoryDescriptor(v.getStringValue());
679        }
680        catch (ConfigException ce)
681        {
682          if (debugEnabled())
683          {
684            TRACER.debugCaught(DebugLogLevel.ERROR, ce);
685          }
686    
687          Message message =
688              ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(
689                      String.valueOf(entryDN), ce.getMessageObject());
690          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
691        }
692        catch (Exception e)
693        {
694          if (debugEnabled())
695          {
696            TRACER.debugCaught(DebugLogLevel.ERROR, e);
697          }
698    
699          Message message =
700              ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
701          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
702                                       message);
703        }
704    
705        BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
706        if (backupInfo == null)
707        {
708          Message message =
709              ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory.getPath());
710          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
711                  message, parentDN, null);
712        }
713    
714    
715        // Construct the backup entry to return.
716        LinkedHashMap<ObjectClass,String> ocMap =
717            new LinkedHashMap<ObjectClass,String>(3);
718        ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
719    
720        ObjectClass oc = DirectoryServer.getObjectClass(OC_BACKUP_INFO, true);
721        ocMap.put(oc, OC_BACKUP_INFO);
722    
723        oc = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
724        ocMap.put(oc, OC_EXTENSIBLE_OBJECT);
725    
726        LinkedHashMap<AttributeType,List<Attribute>> opAttrs =
727             new LinkedHashMap<AttributeType,List<Attribute>>(0);
728        LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
729             new LinkedHashMap<AttributeType,List<Attribute>>();
730    
731        LinkedHashSet<AttributeValue> valueSet =
732             new LinkedHashSet<AttributeValue>(1);
733        valueSet.add(idValue);
734    
735        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
736        attrList.add(new Attribute(idType, idType.getNameOrOID(), valueSet));
737        userAttrs.put(idType, attrList);
738    
739    
740        backupInfo.getBackupDirectory();
741        valueSet = new LinkedHashSet<AttributeValue>(1);
742        valueSet.add(v);
743        attrList = new ArrayList<Attribute>(1);
744        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
745        userAttrs.put(t, attrList);
746    
747    
748        Date backupDate = backupInfo.getBackupDate();
749        if (backupDate != null)
750        {
751          t = DirectoryServer.getAttributeType(ATTR_BACKUP_DATE, true);
752          valueSet = new LinkedHashSet<AttributeValue>(1);
753          valueSet.add(new AttributeValue(t,
754                                GeneralizedTimeSyntax.format(backupDate)));
755          attrList = new ArrayList<Attribute>(1);
756          attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
757          userAttrs.put(t, attrList);
758        }
759    
760    
761        t = DirectoryServer.getAttributeType(ATTR_BACKUP_COMPRESSED, true);
762        valueSet = new LinkedHashSet<AttributeValue>(1);
763        valueSet.add(BooleanSyntax.createBooleanValue(backupInfo.isCompressed()));
764        attrList = new ArrayList<Attribute>(1);
765        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
766        userAttrs.put(t, attrList);
767    
768    
769        t = DirectoryServer.getAttributeType(ATTR_BACKUP_ENCRYPTED, true);
770        valueSet = new LinkedHashSet<AttributeValue>(1);
771        valueSet.add(BooleanSyntax.createBooleanValue(backupInfo.isEncrypted()));
772        attrList = new ArrayList<Attribute>(1);
773        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
774        userAttrs.put(t, attrList);
775    
776    
777        t = DirectoryServer.getAttributeType(ATTR_BACKUP_INCREMENTAL, true);
778        valueSet = new LinkedHashSet<AttributeValue>(1);
779        valueSet.add(BooleanSyntax.createBooleanValue(backupInfo.isIncremental()));
780        attrList = new ArrayList<Attribute>(1);
781        attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
782        userAttrs.put(t, attrList);
783    
784    
785        HashSet<String> dependencies = backupInfo.getDependencies();
786        if ((dependencies != null) && (! dependencies.isEmpty()))
787        {
788          t = DirectoryServer.getAttributeType(ATTR_BACKUP_DEPENDENCY, true);
789          valueSet = new LinkedHashSet<AttributeValue>(dependencies.size());
790          for (String s : dependencies)
791          {
792            valueSet.add(new AttributeValue(t, s));
793          }
794          attrList = new ArrayList<Attribute>(1);
795          attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
796          userAttrs.put(t, attrList);
797        }
798    
799    
800        byte[] signedHash = backupInfo.getSignedHash();
801        if (signedHash != null)
802        {
803          t = DirectoryServer.getAttributeType(ATTR_BACKUP_SIGNED_HASH, true);
804          valueSet = new LinkedHashSet<AttributeValue>(1);
805          valueSet.add(new AttributeValue(t, new ASN1OctetString(signedHash)));
806          attrList = new ArrayList<Attribute>(1);
807          attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
808          userAttrs.put(t, attrList);
809        }
810    
811    
812        byte[] unsignedHash = backupInfo.getUnsignedHash();
813        if (unsignedHash != null)
814        {
815          t = DirectoryServer.getAttributeType(ATTR_BACKUP_UNSIGNED_HASH, true);
816          valueSet = new LinkedHashSet<AttributeValue>(1);
817          valueSet.add(new AttributeValue(t, new ASN1OctetString(unsignedHash)));
818          attrList = new ArrayList<Attribute>(1);
819          attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
820          userAttrs.put(t, attrList);
821        }
822    
823    
824        HashMap<String,String> properties = backupInfo.getBackupProperties();
825        if ((properties != null) && (! properties.isEmpty()))
826        {
827          for (Map.Entry<String,String> e : properties.entrySet())
828          {
829            t = DirectoryServer.getAttributeType(toLowerCase(e.getKey()), true);
830            valueSet = new LinkedHashSet<AttributeValue>(1);
831            valueSet.add(new AttributeValue(t, e.getValue()));
832            attrList = new ArrayList<Attribute>(1);
833            attrList.add(new Attribute(t, t.getNameOrOID(), valueSet));
834            userAttrs.put(t, attrList);
835          }
836        }
837    
838    
839        Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
840        e.processVirtualAttributes();
841        return e;
842      }
843    
844    
845    
846      /**
847       * {@inheritDoc}
848       */
849      @Override()
850      public void addEntry(Entry entry, AddOperation addOperation)
851             throws DirectoryException
852      {
853        Message message = ERR_BACKUP_ADD_NOT_SUPPORTED.get();
854        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
855      }
856    
857    
858    
859      /**
860       * {@inheritDoc}
861       */
862      @Override()
863      public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
864             throws DirectoryException
865      {
866        Message message = ERR_BACKUP_DELETE_NOT_SUPPORTED.get();
867        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
868      }
869    
870    
871    
872      /**
873       * {@inheritDoc}
874       */
875      @Override()
876      public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
877             throws DirectoryException
878      {
879        Message message = ERR_BACKUP_MODIFY_NOT_SUPPORTED.get();
880        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
881      }
882    
883    
884    
885      /**
886       * {@inheritDoc}
887       */
888      @Override()
889      public void renameEntry(DN currentDN, Entry entry,
890                                       ModifyDNOperation modifyDNOperation)
891             throws DirectoryException
892      {
893        Message message = ERR_BACKUP_MODIFY_DN_NOT_SUPPORTED.get();
894        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
895      }
896    
897    
898    
899      /**
900       * {@inheritDoc}
901       */
902      @Override()
903      public void search(SearchOperation searchOperation)
904             throws DirectoryException
905      {
906        // Get the base entry for the search, if possible.  If it doesn't exist,
907        // then this will throw an exception.
908        DN    baseDN    = searchOperation.getBaseDN();
909        Entry baseEntry = getEntry(baseDN);
910    
911    
912        // Look at the base DN and see if it's the backup base DN, a backup
913        // directory entry DN, or a backup entry DN.
914        DN parentDN;
915        SearchScope  scope  = searchOperation.getScope();
916        SearchFilter filter = searchOperation.getFilter();
917        if (backupBaseDN.equals(baseDN))
918        {
919          if ((scope == SearchScope.BASE_OBJECT) ||
920              (scope == SearchScope.WHOLE_SUBTREE))
921          {
922            if (filter.matchesEntry(baseEntry))
923            {
924              searchOperation.returnEntry(baseEntry, null);
925            }
926          }
927    
928          if ((scope != SearchScope.BASE_OBJECT) && (! backupDirectories.isEmpty()))
929          {
930            AttributeType backupPathType =
931                 DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
932            for (File f : backupDirectories)
933            {
934              // Check to see if the descriptor file exists.  If not, then skip this
935              // backup directory.
936              File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
937              if (! descriptorFile.exists())
938              {
939                continue;
940              }
941    
942    
943              DN backupDirDN = makeChildDN(backupBaseDN, backupPathType,
944                                           f.getAbsolutePath());
945    
946              Entry backupDirEntry;
947              try
948              {
949                backupDirEntry = getBackupDirectoryEntry(backupDirDN);
950              }
951              catch (Exception e)
952              {
953                if (debugEnabled())
954                {
955                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
956                }
957    
958                continue;
959              }
960    
961              if (filter.matchesEntry(backupDirEntry))
962              {
963                searchOperation.returnEntry(backupDirEntry, null);
964              }
965    
966              if (scope != SearchScope.SINGLE_LEVEL)
967              {
968                List<Attribute> attrList =
969                     backupDirEntry.getAttribute(backupPathType);
970                if ((attrList != null) && (! attrList.isEmpty()))
971                {
972                  for (AttributeValue v : attrList.get(0).getValues())
973                  {
974                    try
975                    {
976                      BackupDirectory backupDirectory =
977                           BackupDirectory.readBackupDirectoryDescriptor(
978                                v.getStringValue());
979                      AttributeType idType =
980                           DirectoryServer.getAttributeType(ATTR_BACKUP_ID,
981                                                            true);
982                      for (String backupID : backupDirectory.getBackups().keySet())
983                      {
984                        DN backupEntryDN = makeChildDN(backupDirDN, idType,
985                                                       backupID);
986                        Entry backupEntry = getBackupEntry(backupEntryDN);
987                        if (filter.matchesEntry(backupEntry))
988                        {
989                          searchOperation.returnEntry(backupEntry, null);
990                        }
991                      }
992                    }
993                    catch (Exception e)
994                    {
995                      if (debugEnabled())
996                      {
997                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
998                      }
999    
1000                      continue;
1001                    }
1002                  }
1003                }
1004              }
1005            }
1006          }
1007        }
1008        else if (backupBaseDN.equals(parentDN = baseDN.getParentDNInSuffix()))
1009        {
1010          Entry backupDirEntry = getBackupDirectoryEntry(baseDN);
1011    
1012          if ((scope == SearchScope.BASE_OBJECT) ||
1013              (scope == SearchScope.WHOLE_SUBTREE))
1014          {
1015            if (filter.matchesEntry(backupDirEntry))
1016            {
1017              searchOperation.returnEntry(backupDirEntry, null);
1018            }
1019          }
1020    
1021    
1022          if (scope != SearchScope.BASE_OBJECT)
1023          {
1024            AttributeType t =
1025                 DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
1026            List<Attribute> attrList = backupDirEntry.getAttribute(t);
1027            if ((attrList != null) && (! attrList.isEmpty()))
1028            {
1029              for (AttributeValue v : attrList.get(0).getValues())
1030              {
1031                try
1032                {
1033                  BackupDirectory backupDirectory =
1034                       BackupDirectory.readBackupDirectoryDescriptor(
1035                            v.getStringValue());
1036                  AttributeType idType =
1037                       DirectoryServer.getAttributeType(ATTR_BACKUP_ID,
1038                                                        true);
1039                  for (String backupID : backupDirectory.getBackups().keySet())
1040                  {
1041                    DN backupEntryDN = makeChildDN(baseDN, idType,
1042                                                   backupID);
1043                    Entry backupEntry = getBackupEntry(backupEntryDN);
1044                    if (filter.matchesEntry(backupEntry))
1045                    {
1046                      searchOperation.returnEntry(backupEntry, null);
1047                    }
1048                  }
1049                }
1050                catch (Exception e)
1051                {
1052                  if (debugEnabled())
1053                  {
1054                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
1055                  }
1056    
1057                  continue;
1058                }
1059              }
1060            }
1061          }
1062        }
1063        else
1064        {
1065          if ((parentDN == null)
1066              || (! backupBaseDN.equals(parentDN.getParentDNInSuffix())))
1067          {
1068            Message message = ERR_BACKUP_NO_SUCH_ENTRY.get(
1069                    String.valueOf(backupBaseDN)
1070            );
1071            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
1072          }
1073    
1074          if ((scope == SearchScope.BASE_OBJECT) ||
1075              (scope == SearchScope.WHOLE_SUBTREE))
1076          {
1077            Entry backupEntry = getBackupEntry(baseDN);
1078            if (backupEntry == null)
1079            {
1080              Message message = ERR_BACKUP_NO_SUCH_ENTRY.get(
1081                      String.valueOf(backupBaseDN));
1082              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
1083            }
1084    
1085            if (filter.matchesEntry(backupEntry))
1086            {
1087              searchOperation.returnEntry(backupEntry, null);
1088            }
1089          }
1090        }
1091      }
1092    
1093    
1094    
1095      /**
1096       * {@inheritDoc}
1097       */
1098      @Override()
1099      public HashSet<String> getSupportedControls()
1100      {
1101        return supportedControls;
1102      }
1103    
1104    
1105    
1106      /**
1107       * {@inheritDoc}
1108       */
1109      @Override()
1110      public HashSet<String> getSupportedFeatures()
1111      {
1112        return supportedFeatures;
1113      }
1114    
1115    
1116    
1117      /**
1118       * {@inheritDoc}
1119       */
1120      @Override()
1121      public boolean supportsLDIFExport()
1122      {
1123        // We do not support LDIF exports.
1124        return false;
1125      }
1126    
1127    
1128    
1129      /**
1130       * {@inheritDoc}
1131       */
1132      @Override()
1133      public void exportLDIF(LDIFExportConfig exportConfig)
1134             throws DirectoryException
1135      {
1136        Message message = ERR_BACKUP_EXPORT_NOT_SUPPORTED.get();
1137        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1138      }
1139    
1140    
1141    
1142      /**
1143       * {@inheritDoc}
1144       */
1145      @Override()
1146      public boolean supportsLDIFImport()
1147      {
1148        // This backend does not support LDIF imports.
1149        return false;
1150      }
1151    
1152    
1153    
1154      /**
1155       * {@inheritDoc}
1156       */
1157      @Override()
1158      public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
1159             throws DirectoryException
1160      {
1161        // This backend does not support LDIF imports.
1162        Message message = ERR_BACKUP_IMPORT_NOT_SUPPORTED.get();
1163        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1164      }
1165    
1166    
1167    
1168      /**
1169       * {@inheritDoc}
1170       */
1171      @Override()
1172      public boolean supportsBackup()
1173      {
1174        // This backend does not provide a backup/restore mechanism.
1175        return false;
1176      }
1177    
1178    
1179    
1180      /**
1181       * {@inheritDoc}
1182       */
1183      @Override()
1184      public boolean supportsBackup(BackupConfig backupConfig,
1185                                    StringBuilder unsupportedReason)
1186      {
1187        // This backend does not provide a backup/restore mechanism.
1188        return false;
1189      }
1190    
1191    
1192    
1193      /**
1194       * {@inheritDoc}
1195       */
1196      @Override()
1197      public void createBackup(BackupConfig backupConfig)
1198      throws DirectoryException
1199      {
1200        // This backend does not provide a backup/restore mechanism.
1201        Message message = ERR_BACKUP_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1202        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1203      }
1204    
1205    
1206    
1207      /**
1208       * {@inheritDoc}
1209       */
1210      @Override()
1211      public void removeBackup(BackupDirectory backupDirectory,
1212                               String backupID)
1213             throws DirectoryException
1214      {
1215        // This backend does not provide a backup/restore mechanism.
1216        Message message = ERR_BACKUP_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1217        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1218      }
1219    
1220    
1221    
1222      /**
1223       * {@inheritDoc}
1224       */
1225      @Override()
1226      public boolean supportsRestore()
1227      {
1228        // This backend does not provide a backup/restore mechanism.
1229        return false;
1230      }
1231    
1232    
1233    
1234      /**
1235       * {@inheritDoc}
1236       */
1237      @Override()
1238      public void restoreBackup(RestoreConfig restoreConfig)
1239             throws DirectoryException
1240      {
1241        // This backend does not provide a backup/restore mechanism.
1242        Message message = ERR_BACKUP_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1243        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1244      }
1245    
1246    
1247    
1248      /**
1249       * {@inheritDoc}
1250       */
1251      public boolean isConfigurationChangeAcceptable(
1252           BackupBackendCfg cfg, List<Message> unacceptableReasons)
1253      {
1254        // We'll accept anything here.  The only configurable attribute is the
1255        // default set of backup directories, but that doesn't require any
1256        // validation at this point.
1257        return true;
1258      }
1259    
1260    
1261    
1262      /**
1263       * {@inheritDoc}
1264       */
1265      public ConfigChangeResult applyConfigurationChange(BackupBackendCfg cfg)
1266      {
1267        ResultCode         resultCode          = ResultCode.SUCCESS;
1268        boolean            adminActionRequired = false;
1269        ArrayList<Message> messages            = new ArrayList<Message>();
1270    
1271    
1272        Set<String> values = cfg.getBackupDirectory();
1273        backupDirectories = new LinkedHashSet<File>(values.size());
1274        for (String s : values)
1275        {
1276          backupDirectories.add(getFileForPath(s));
1277        }
1278    
1279        currentConfig = cfg;
1280        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1281      }
1282    
1283    
1284    
1285      /**
1286       * Create a new child DN from a given parent DN.  The child RDN is formed
1287       * from a given attribute type and string value.
1288       * @param parentDN The DN of the parent.
1289       * @param rdnAttrType The attribute type of the RDN.
1290       * @param rdnStringValue The string value of the RDN.
1291       * @return A new child DN.
1292       */
1293      public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType,
1294                                   String rdnStringValue)
1295      {
1296        AttributeValue attrValue =
1297             new AttributeValue(rdnAttrType, rdnStringValue);
1298        return parentDN.concat(RDN.create(rdnAttrType, attrValue));
1299      }
1300    
1301    
1302    
1303      /**
1304       * {@inheritDoc}
1305       */
1306      public void preloadEntryCache() throws UnsupportedOperationException {
1307        throw new UnsupportedOperationException("Operation not supported.");
1308      }
1309    }
1310