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