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