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}