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