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.types;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.text.SimpleDateFormat;
033    import java.util.Date;
034    import java.util.HashSet;
035    import java.util.HashMap;
036    import java.util.LinkedList;
037    import java.util.TimeZone;
038    
039    import org.opends.server.config.ConfigException;
040    import org.opends.server.util.Base64;
041    
042    import static org.opends.server.loggers.debug.DebugLogger.*;
043    import org.opends.server.loggers.debug.DebugTracer;
044    import static org.opends.messages.CoreMessages.*;
045    import static org.opends.server.util.ServerConstants.*;
046    import static org.opends.server.util.StaticUtils.*;
047    
048    
049    
050    /**
051     * This class defines a data structure for holding information about a
052     * backup that is available in a backup directory.
053     */
054    @org.opends.server.types.PublicAPI(
055         stability=org.opends.server.types.StabilityLevel.VOLATILE,
056         mayInstantiate=false,
057         mayExtend=false,
058         mayInvoke=true)
059    public final class BackupInfo
060    {
061      /**
062       * The tracer object for the debug logger.
063       */
064      private static final DebugTracer TRACER = getTracer();
065    
066    
067    
068    
069      /**
070       * The name of the property that holds the date that the backup was
071       * created.
072       */
073      public static final String PROPERTY_BACKUP_DATE = "backup_date";
074    
075    
076    
077      /**
078       * The name of the property that holds the backup ID in encoded
079       * representations.
080       */
081      public static final String PROPERTY_BACKUP_ID = "backup_id";
082    
083    
084    
085      /**
086       * The name of the property that holds the incremental flag in
087       * encoded representations.
088       */
089      public static final String PROPERTY_IS_INCREMENTAL = "incremental";
090    
091    
092    
093      /**
094       * The name of the property that holds the compressed flag in
095       * encoded representations.
096       */
097      public static final String PROPERTY_IS_COMPRESSED = "compressed";
098    
099    
100    
101      /**
102       * The name of the property that holds the encrypted flag in encoded
103       * representations.
104       */
105      public static final String PROPERTY_IS_ENCRYPTED = "encrypted";
106    
107    
108    
109      /**
110       * The name of the property that holds the unsigned hash in encoded
111       * representations.
112       */
113      public static final String PROPERTY_UNSIGNED_HASH = "hash";
114    
115    
116    
117      /**
118       * The name of the property that holds the signed hash in encoded
119       * representations.
120       */
121      public static final String PROPERTY_SIGNED_HASH = "signed_hash";
122    
123    
124    
125      /**
126       * The name of the property that holds the set of dependencies in
127       * encoded representations (one dependency per instance).
128       */
129      public static final String PROPERTY_DEPENDENCY = "dependency";
130    
131    
132    
133      /**
134       * The prefix to use with custom backup properties.  The name of the
135       * property will be appended to this prefix.
136       */
137      public static final String PROPERTY_CUSTOM_PREFIX = "property.";
138    
139    
140    
141      // The backup directory with which this backup info structure is
142      // associated.
143      private BackupDirectory backupDirectory;
144    
145      // Indicates whether this backup is compressed.
146      private boolean isCompressed;
147    
148      // Indicates whether this backup is encrypted.
149      private boolean isEncrypted;
150    
151      // Indicates whether this is an incremental backup.
152      private boolean isIncremental;
153    
154      // The signed hash for this backup, if appropriate.
155      private byte[] signedHash;
156    
157      // The unsigned hash for this backup, if appropriate.
158      private byte[] unsignedHash;
159    
160      // The time that this backup was created.
161      private Date backupDate;
162    
163      // The set of backup ID(s) on which this backup is dependent.
164      private HashSet<String> dependencies;
165    
166      // The set of additional properties associated with this backup.
167      // This is intended for use by the backend for storing any kind of
168      // state information that it might need to associated with the
169      // backup.  The mapping will be between a name and a value, where
170      // the name must not contain an equal sign and neither the name nor
171      // the value may contain line breaks;
172      private HashMap<String,String> backupProperties;
173    
174      // The unique ID for this backup.
175      private String backupID;
176    
177    
178    
179      /**
180       * Creates a new backup info structure with the provided
181       * information.
182       *
183       * @param  backupDirectory   A reference to the backup directory in
184       *                           which this backup is stored.
185       * @param  backupID          The unique ID for this backup.
186       * @param  backupDate        The time that this backup was created.
187       * @param  isIncremental     Indicates whether this is an
188       *                           incremental or a full backup.
189       * @param  isCompressed      Indicates whether the backup is
190       *                           compressed.
191       * @param  isEncrypted       Indicates whether the backup is
192       *                           encrypted.
193       * @param  unsignedHash      The unsigned hash for this backup, if
194       *                           appropriate.
195       * @param  signedHash        The signed hash for this backup, if
196       *                           appropriate.
197       * @param  dependencies      The backup IDs of the previous backups
198       *                           on which this backup is dependent.
199       * @param  backupProperties  The set of additional backend-specific
200       *                           properties that should be stored with
201       *                           this backup information.  It should be
202       *                           a mapping between property names and
203       *                           values, where the names do not contain
204       *                           any equal signs and neither the names
205       *                           nor the values contain line breaks.
206       */
207      public BackupInfo(BackupDirectory backupDirectory, String backupID,
208                        Date backupDate, boolean isIncremental,
209                        boolean isCompressed, boolean isEncrypted,
210                        byte[] unsignedHash, byte[] signedHash,
211                        HashSet<String> dependencies,
212                        HashMap<String,String> backupProperties)
213      {
214        this.backupDirectory = backupDirectory;
215        this.backupID        = backupID;
216        this.backupDate      = backupDate;
217        this.isIncremental   = isIncremental;
218        this.isCompressed    = isCompressed;
219        this.isEncrypted     = isEncrypted;
220        this.unsignedHash    = unsignedHash;
221        this.signedHash      = signedHash;
222    
223        if (dependencies == null)
224        {
225          this.dependencies = new HashSet<String>();
226        }
227        else
228        {
229          this.dependencies = dependencies;
230        }
231    
232        if (backupProperties == null)
233        {
234          this.backupProperties = new HashMap<String,String>();
235        }
236        else
237        {
238          this.backupProperties = backupProperties;
239        }
240      }
241    
242    
243    
244      /**
245       * Retrieves the reference to the backup directory in which this
246       * backup is stored.
247       *
248       * @return  A reference to the backup directory in which this backup
249       *          is stored.
250       */
251      public BackupDirectory getBackupDirectory()
252      {
253        return backupDirectory;
254      }
255    
256    
257    
258      /**
259       * Retrieves the unique ID for this backup.
260       *
261       * @return  The unique ID for this backup.
262       */
263      public String getBackupID()
264      {
265        return backupID;
266      }
267    
268    
269    
270      /**
271       * Retrieves the date that this backup was created.
272       *
273       * @return  The date that this backup was created.
274       */
275      public Date getBackupDate()
276      {
277        return backupDate;
278      }
279    
280    
281    
282      /**
283       * Indicates whether this is an incremental or a full backup.
284       *
285       * @return  <CODE>true</CODE> if this is an incremental backup, or
286       *          <CODE>false</CODE> if it is a full backup.
287       */
288      public boolean isIncremental()
289      {
290        return isIncremental;
291      }
292    
293    
294    
295      /**
296       * Indicates whether this backup is compressed.
297       *
298       * @return  <CODE>true</CODE> if this backup is compressed, or
299       *          <CODE>false</CODE> if it is not.
300       */
301      public boolean isCompressed()
302      {
303        return isCompressed;
304      }
305    
306    
307    
308      /**
309       * Indicates whether this backup is encrypted.
310       *
311       * @return  <CODE>true</CODE> if this backup is encrypted, or
312       *          <CODE>false</CODE> if it is not.
313       */
314      public boolean isEncrypted()
315      {
316        return isEncrypted;
317      }
318    
319    
320    
321      /**
322       * Retrieves the data for the unsigned hash for this backup, if
323       * appropriate.
324       *
325       * @return  The data for the unsigned hash for this backup, or
326       *          <CODE>null</CODE> if there is none.
327       */
328      public byte[] getUnsignedHash()
329      {
330        return unsignedHash;
331      }
332    
333    
334    
335      /**
336       * Retrieves the data for the signed hash for this backup, if
337       * appropriate.
338       *
339       * @return  The data for the signed hash for this backup, or
340       *          <CODE>null</CODE> if there is none.
341       */
342      public byte[] getSignedHash()
343      {
344        return signedHash;
345      }
346    
347    
348    
349      /**
350       * Retrieves the set of the backup IDs for the backups on which this
351       * backup is dependent.  This is primarily intended for use with
352       * incremental backups (which should be dependent on at least a full
353       * backup and possibly one or more other incremental backups).  The
354       * contents of this hash should not be directly updated by the
355       * caller.
356       *
357       * @return  The set of the backup IDs for the backups on which this
358       *          backup is dependent.
359       */
360      public HashSet<String> getDependencies()
361      {
362        return dependencies;
363      }
364    
365    
366    
367      /**
368       * Indicates whether this backup has a dependency on the backup with
369       * the provided ID.
370       *
371       * @param  backupID  The backup ID for which to make the
372       *                   determination.
373       *
374       * @return  <CODE>true</CODE> if this backup has a dependency on the
375       *          backup with the provided ID, or <CODE>false</CODE> if
376       *          not.
377       */
378      public boolean dependsOn(String backupID)
379      {
380        return dependencies.contains(backupID);
381      }
382    
383    
384    
385      /**
386       * Retrieves a set of additional properties that should be
387       * associated with this backup.  This may be used by the backend to
388       * store arbitrary information that may be needed later to restore
389       * the backup or perform an incremental backup based on this backup.
390       * The mapping will be between property names and values, where the
391       * names are not allowed to contain equal signs, and neither the
392       * names nor the values may have line breaks.  The contents of the
393       * mapping should not be altered by the caller.
394       *
395       * @return  A set of additional properties that should be associated
396       *          with this backup.
397       */
398      public HashMap<String,String> getBackupProperties()
399      {
400        return backupProperties;
401      }
402    
403    
404    
405      /**
406       * Retrieves the value of the backup property with the specified
407       * name.
408       *
409       * @param  name  The name of the backup property to retrieve.
410       *
411       * @return  The value of the backup property with the specified
412       *          name, or <CODE>null</CODE> if there is no such property.
413       */
414      public String getBackupProperty(String name)
415      {
416        return backupProperties.get(name);
417      }
418    
419    
420    
421      /**
422       * Encodes this backup info structure to a multi-line string
423       * representation.  This representation may be parsed by the
424       * <CODE>decode</CODE> method to reconstruct the structure.
425       *
426       * @return  A multi-line string representation of this backup info
427       *          structure.
428       */
429      public LinkedList<String> encode()
430      {
431        LinkedList<String> list       = new LinkedList<String>();
432        SimpleDateFormat   dateFormat =
433             new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
434    
435        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
436    
437        list.add(PROPERTY_BACKUP_ID + "=" + backupID);
438        list.add(PROPERTY_BACKUP_DATE + "=" +
439                 dateFormat.format(backupDate));
440        list.add(PROPERTY_IS_INCREMENTAL + "=" +
441                 String.valueOf(isIncremental));
442        list.add(PROPERTY_IS_COMPRESSED + "=" +
443                 String.valueOf(isCompressed));
444        list.add(PROPERTY_IS_ENCRYPTED + "=" +
445                 String.valueOf(isEncrypted));
446    
447        if (unsignedHash != null)
448        {
449          list.add(PROPERTY_UNSIGNED_HASH + "=" +
450                   Base64.encode(unsignedHash));
451        }
452    
453        if (signedHash != null)
454        {
455          list.add(PROPERTY_SIGNED_HASH + "=" +
456                   Base64.encode(signedHash));
457        }
458    
459        if (! dependencies.isEmpty())
460        {
461          for (String dependency : dependencies)
462          {
463            list.add(PROPERTY_DEPENDENCY + "=" + dependency);
464          }
465        }
466    
467        if (! backupProperties.isEmpty())
468        {
469          for (String name : backupProperties.keySet())
470          {
471            String value = backupProperties.get(name);
472            if (value == null)
473            {
474              value = "";
475            }
476    
477            list.add(PROPERTY_CUSTOM_PREFIX + name + "=" + value);
478          }
479        }
480    
481        return list;
482      }
483    
484    
485    
486      /**
487       * Decodes the provided list of strings as the representation of a
488       * backup info structure.
489       *
490       * @param  backupDirectory  The reference to the backup directory
491       *                          with which the backup info is
492       *                          associated.
493       * @param  encodedInfo      The list of strings that comprise the
494       *                          string representation of the backup info
495       *                          structure.
496       *
497       * @return  The decoded backup info structure.
498       *
499       * @throws  ConfigException  If a problem occurs while attempting to
500       *                           decode the backup info data.
501       */
502      public static BackupInfo decode(BackupDirectory backupDirectory,
503                                      LinkedList<String> encodedInfo)
504             throws ConfigException
505      {
506        String                 backupID         = null;
507        Date                   backupDate       = null;
508        boolean                isIncremental    = false;
509        boolean                isCompressed     = false;
510        boolean                isEncrypted      = false;
511        byte[]                 unsignedHash     = null;
512        byte[]                 signedHash       = null;
513        HashSet<String>        dependencies     = new HashSet<String>();
514        HashMap<String,String> backupProperties =
515             new HashMap<String,String>();
516    
517        String backupPath = backupDirectory.getPath();
518        try
519        {
520          for (String line : encodedInfo)
521          {
522            int equalPos = line.indexOf('=');
523            if (equalPos < 0)
524            {
525              Message message =
526                  ERR_BACKUPINFO_NO_DELIMITER.get(line, backupPath);
527              throw new ConfigException(message);
528            }
529            else if (equalPos == 0)
530            {
531              Message message =
532                  ERR_BACKUPINFO_NO_NAME.get(line, backupPath);
533              throw new ConfigException(message);
534            }
535    
536            String name  = line.substring(0, equalPos);
537            String value = line.substring(equalPos+1);
538    
539            if (name.equals(PROPERTY_BACKUP_ID))
540            {
541              if (backupID == null)
542              {
543                backupID = value;
544              }
545              else
546              {
547                Message message = ERR_BACKUPINFO_MULTIPLE_BACKUP_IDS.get(
548                    backupPath, backupID, value);
549                throw new ConfigException(message);
550              }
551            }
552            else if (name.equals(PROPERTY_BACKUP_DATE))
553            {
554              SimpleDateFormat dateFormat =
555                   new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
556              dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
557              backupDate = dateFormat.parse(value);
558            }
559            else if (name.equals(PROPERTY_IS_INCREMENTAL))
560            {
561              isIncremental = Boolean.valueOf(value);
562            }
563            else if (name.equals(PROPERTY_IS_COMPRESSED))
564            {
565              isCompressed = Boolean.valueOf(value);
566            }
567            else if (name.equals(PROPERTY_IS_ENCRYPTED))
568            {
569              isEncrypted = Boolean.valueOf(value);
570            }
571            else if (name.equals(PROPERTY_UNSIGNED_HASH))
572            {
573              unsignedHash = Base64.decode(value);
574            }
575            else if (name.equals(PROPERTY_SIGNED_HASH))
576            {
577              signedHash = Base64.decode(value);
578            }
579            else if (name.equals(PROPERTY_DEPENDENCY))
580            {
581              dependencies.add(value);
582            }
583            else if (name.startsWith(PROPERTY_CUSTOM_PREFIX))
584            {
585              String propertyName =
586                   name.substring(PROPERTY_CUSTOM_PREFIX.length());
587              backupProperties.put(propertyName, value);
588            }
589            else
590            {
591              Message message = ERR_BACKUPINFO_UNKNOWN_PROPERTY.get(
592                  backupPath, name, value);
593              throw new ConfigException(message);
594            }
595          }
596        }
597        catch (ConfigException ce)
598        {
599          throw ce;
600        }
601        catch (Exception e)
602        {
603          if (debugEnabled())
604          {
605            TRACER.debugCaught(DebugLogLevel.ERROR, e);
606          }
607    
608          Message message = ERR_BACKUPINFO_CANNOT_DECODE.get(
609              backupPath, getExceptionMessage(e));
610          throw new ConfigException(message, e);
611        }
612    
613    
614        // There must have been at least a backup ID and backup date
615        // specified.
616        if (backupID == null)
617        {
618          Message message = ERR_BACKUPINFO_NO_BACKUP_ID.get(backupPath);
619          throw new ConfigException(message);
620        }
621    
622        if (backupDate == null)
623        {
624          Message message =
625              ERR_BACKUPINFO_NO_BACKUP_DATE.get(backupID, backupPath);
626          throw new ConfigException(message);
627        }
628    
629    
630        return new BackupInfo(backupDirectory, backupID, backupDate,
631                              isIncremental, isCompressed, isEncrypted,
632                              unsignedHash, signedHash, dependencies,
633                              backupProperties);
634      }
635    
636    
637    
638      /**
639       * Retrieves a multi-line string representation of this backup info
640       * structure.
641       *
642       * @return  A multi-line string representation of this backup info
643       *          structure.
644       */
645      public String toString()
646      {
647        StringBuilder buffer = new StringBuilder();
648        toString(buffer);
649        return buffer.toString();
650      }
651    
652    
653    
654      /**
655       * Appends a multi-line string representation of this backup info
656       * structure to the provided buffer.
657       *
658       * @param  buffer  The buffer to which the information should be
659       *                 written.
660       */
661      public void toString(StringBuilder buffer)
662      {
663        LinkedList<String> lines = encode();
664        for (String line : lines)
665        {
666          buffer.append(line);
667          buffer.append(EOL);
668        }
669      }
670    }
671