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