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