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