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