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.backends; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.server.config.ConfigConstants.*; 022import static org.opends.server.core.DirectoryServer.*; 023import static org.opends.server.schema.BooleanSyntax.*; 024import static org.opends.server.util.ServerConstants.*; 025import static org.opends.server.util.StaticUtils.*; 026 027import java.io.File; 028import java.io.IOException; 029import java.util.Collections; 030import java.util.Date; 031import java.util.HashMap; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.config.server.ConfigChangeResult; 040import org.forgerock.opendj.config.server.ConfigException; 041import org.forgerock.opendj.config.server.ConfigurationChangeListener; 042import org.forgerock.opendj.ldap.AVA; 043import org.forgerock.opendj.ldap.ByteString; 044import org.forgerock.opendj.ldap.ConditionResult; 045import org.forgerock.opendj.ldap.DN; 046import org.forgerock.opendj.ldap.RDN; 047import org.forgerock.opendj.ldap.ResultCode; 048import org.forgerock.opendj.ldap.SearchScope; 049import org.forgerock.opendj.ldap.schema.AttributeType; 050import org.forgerock.opendj.ldap.schema.CoreSchema; 051import org.forgerock.opendj.ldap.schema.ObjectClass; 052import org.forgerock.opendj.server.config.server.BackupBackendCfg; 053import org.opends.server.api.Backend; 054import org.opends.server.core.AddOperation; 055import org.opends.server.core.DeleteOperation; 056import org.opends.server.core.DirectoryServer; 057import org.opends.server.core.ModifyDNOperation; 058import org.opends.server.core.ModifyOperation; 059import org.opends.server.core.SearchOperation; 060import org.opends.server.core.ServerContext; 061import org.opends.server.schema.GeneralizedTimeSyntax; 062import org.opends.server.types.Attribute; 063import org.opends.server.types.AttributeBuilder; 064import org.opends.server.types.Attributes; 065import org.opends.server.types.BackupConfig; 066import org.opends.server.types.BackupDirectory; 067import org.opends.server.types.BackupInfo; 068import org.opends.server.types.DirectoryException; 069import org.opends.server.types.Entry; 070import org.opends.server.types.IndexType; 071import org.opends.server.types.InitializationException; 072import org.opends.server.types.LDIFExportConfig; 073import org.opends.server.types.LDIFImportConfig; 074import org.opends.server.types.LDIFImportResult; 075import org.opends.server.types.RestoreConfig; 076import org.opends.server.types.SearchFilter; 077 078/** 079 * This class defines a backend used to present information about Directory 080 * Server backups. It will not actually store anything, but upon request will 081 * retrieve information about the backups that it knows about. The backups will 082 * be arranged in a hierarchy based on the directory that contains them, and 083 * it may be possible to dynamically discover new backups if a previously 084 * unknown backup directory is included in the base DN. 085 */ 086public class BackupBackend 087 extends Backend<BackupBackendCfg> 088 implements ConfigurationChangeListener<BackupBackendCfg> 089{ 090 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 091 092 /** The current configuration state. */ 093 private BackupBackendCfg currentConfig; 094 095 /** The DN for the base backup entry. */ 096 private DN backupBaseDN; 097 098 /** The set of base DNs for this backend. */ 099 private Set<DN> baseDNs; 100 101 /** The backup base entry. */ 102 private Entry backupBaseEntry; 103 104 /** A cache of BackupDirectories. */ 105 private HashMap<File,CachedBackupDirectory> backupDirectories; 106 107 /** 108 * To avoid parsing and reparsing the contents of backup.info files, we 109 * cache the BackupDirectory for each directory using this class. 110 */ 111 private class CachedBackupDirectory 112 { 113 /** The path to the 'bak' directory. */ 114 private final String directoryPath; 115 116 /** The 'backup.info' file. */ 117 private final File backupInfo; 118 119 /** The last modify time of the backupInfo file. */ 120 private long lastModified; 121 122 /** The BackupDirectory parsed at lastModified time. */ 123 private BackupDirectory backupDirectory; 124 125 /** 126 * A BackupDirectory that is cached based on the backup descriptor file. 127 * 128 * @param directory Path to the backup directory itself. 129 */ 130 public CachedBackupDirectory(File directory) 131 { 132 directoryPath = directory.getPath(); 133 backupInfo = new File(directoryPath + File.separator + BACKUP_DIRECTORY_DESCRIPTOR_FILE); 134 lastModified = -1; 135 backupDirectory = null; 136 } 137 138 /** 139 * Return a BackupDirectory. This will be recomputed every time the underlying descriptor (backup.info) file 140 * changes. 141 * 142 * @return An up-to-date BackupDirectory 143 * @throws IOException If a problem occurs while trying to read the contents of the descriptor file. 144 * @throws ConfigException If the contents of the descriptor file cannot be parsed to create a backup directory 145 * structure. 146 */ 147 public synchronized BackupDirectory getBackupDirectory() 148 throws IOException, ConfigException 149 { 150 long currentModified = backupInfo.lastModified(); 151 if (backupDirectory == null || currentModified != lastModified) 152 { 153 backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(directoryPath); 154 lastModified = currentModified; 155 } 156 return backupDirectory; 157 } 158 } 159 160 /** 161 * Creates a new backend with the provided information. All backend 162 * implementations must implement a default constructor that use 163 * <CODE>super()</CODE> to invoke this constructor. 164 */ 165 public BackupBackend() 166 { 167 super(); 168 169 // Perform all initialization in initializeBackend. 170 } 171 172 @Override 173 public void configureBackend(BackupBackendCfg config, ServerContext serverContext) throws ConfigException 174 { 175 // Make sure that a configuration entry was provided. If not, then we will 176 // not be able to complete initialization. 177 if (config == null) 178 { 179 throw new ConfigException(ERR_BACKEND_CONFIG_ENTRY_NULL.get(getBackendID())); 180 } 181 currentConfig = config; 182 } 183 184 @Override 185 public void openBackend() 186 throws ConfigException, InitializationException 187 { 188 // Create the set of base DNs that we will handle. In this case, it's just 189 // the DN of the base backup entry. 190 try 191 { 192 backupBaseDN = DN.valueOf(DN_BACKUP_ROOT); 193 } 194 catch (Exception e) 195 { 196 logger.traceException(e); 197 198 LocalizableMessage message = 199 ERR_BACKEND_CANNOT_DECODE_BACKEND_ROOT_DN.get(getExceptionMessage(e), getBackendID()); 200 throw new InitializationException(message, e); 201 } 202 203 this.baseDNs = Collections.singleton(backupBaseDN); 204 205 // Determine the set of backup directories that we will use by default. 206 Set<String> values = currentConfig.getBackupDirectory(); 207 backupDirectories = new LinkedHashMap<>(values.size()); 208 for (String s : values) 209 { 210 File dir = getFileForPath(s); 211 backupDirectories.put(dir, new CachedBackupDirectory(dir)); 212 } 213 214 // Construct the backup base entry. 215 LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<>(2); 216 objectClasses.put(CoreSchema.getTopObjectClass(), OC_TOP); 217 objectClasses.put(getSchema().getObjectClass(OC_UNTYPED_OBJECT_LC), OC_UNTYPED_OBJECT); 218 219 LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0); 220 LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(1); 221 222 for (AVA ava : backupBaseDN.rdn()) 223 { 224 AttributeType attrType = ava.getAttributeType(); 225 userAttrs.put(attrType, Attributes.createAsList(attrType, ava.getAttributeValue())); 226 } 227 228 backupBaseEntry = new Entry(backupBaseDN, objectClasses, userAttrs, opAttrs); 229 230 currentConfig.addBackupChangeListener(this); 231 232 // Register the backup base as a private suffix. 233 try 234 { 235 DirectoryServer.registerBaseDN(backupBaseDN, this, true); 236 } 237 catch (Exception e) 238 { 239 logger.traceException(e); 240 241 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 242 backupBaseDN, getExceptionMessage(e)); 243 throw new InitializationException(message, e); 244 } 245 } 246 247 @Override 248 public void closeBackend() 249 { 250 currentConfig.removeBackupChangeListener(this); 251 252 try 253 { 254 DirectoryServer.deregisterBaseDN(backupBaseDN); 255 } 256 catch (Exception e) 257 { 258 logger.traceException(e); 259 } 260 } 261 262 @Override 263 public Set<DN> getBaseDNs() 264 { 265 return baseDNs; 266 } 267 268 @Override 269 public long getEntryCount() 270 { 271 int numEntries = 1; 272 273 AttributeType backupPathType = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 274 275 for (File dir : backupDirectories.keySet()) 276 { 277 try 278 { 279 // Check to see if the descriptor file exists. If not, then skip this 280 // backup directory. 281 File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE); 282 if (! descriptorFile.exists()) 283 { 284 continue; 285 } 286 287 DN backupDirDN = makeChildDN(backupBaseDN, backupPathType, 288 dir.getAbsolutePath()); 289 getBackupDirectoryEntry(backupDirDN); 290 numEntries++; 291 } 292 catch (Exception e) {} 293 } 294 295 return numEntries; 296 } 297 298 @Override 299 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 300 { 301 // All searches in this backend will always be considered indexed. 302 return true; 303 } 304 305 @Override 306 public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException 307 { 308 long ret = getNumberOfSubordinates(entryDN, false); 309 if(ret < 0) 310 { 311 return ConditionResult.UNDEFINED; 312 } 313 return ConditionResult.valueOf(ret != 0); 314 } 315 316 @Override 317 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException { 318 checkNotNull(baseDN, "baseDN must not be null"); 319 return getNumberOfSubordinates(baseDN, true) + 1; 320 } 321 322 @Override 323 public long getNumberOfChildren(DN parentDN) throws DirectoryException { 324 checkNotNull(parentDN, "parentDN must not be null"); 325 return getNumberOfSubordinates(parentDN, false); 326 } 327 328 private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException 329 { 330 // If the requested entry was the backend base entry, then return 331 // the number of backup directories. 332 if (backupBaseDN.equals(entryDN)) 333 { 334 long count = 0; 335 for (File dir : backupDirectories.keySet()) 336 { 337 // Check to see if the descriptor file exists. If not, then skip this 338 // backup directory. 339 File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE); 340 if (! descriptorFile.exists()) 341 { 342 continue; 343 } 344 345 // If subtree is included, count the number of entries for each 346 // backup directory. 347 if (includeSubtree) 348 { 349 count++; 350 try 351 { 352 BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory(); 353 count += backupDirectory.getBackups().keySet().size(); 354 } 355 catch (Exception e) 356 { 357 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get( 358 entryDN, e.getMessage())); 359 } 360 } 361 362 count ++; 363 } 364 return count; 365 } 366 367 // See if the requested entry was one level below the backend base entry. 368 // If so, then it must point to a backup directory. Otherwise, it must be 369 // two levels below the backup base entry and must point to a specific 370 // backup. 371 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 372 if (parentDN == null) 373 { 374 return -1; 375 } 376 else if (backupBaseDN.equals(parentDN)) 377 { 378 long count = 0; 379 Entry backupDirEntry = getBackupDirectoryEntry(entryDN); 380 381 AttributeType t = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 382 List<Attribute> attrList = backupDirEntry.getAttribute(t); 383 for (ByteString v : attrList.get(0)) 384 { 385 try 386 { 387 File dir = new File(v.toString()); 388 BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory(); 389 count += backupDirectory.getBackups().keySet().size(); 390 } 391 catch (Exception e) 392 { 393 return -1; 394 } 395 } 396 return count; 397 } 398 else if (backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN))) 399 { 400 return 0; 401 } 402 else 403 { 404 return -1; 405 } 406 } 407 408 @Override 409 public Entry getEntry(DN entryDN) 410 throws DirectoryException 411 { 412 // If the requested entry was null, then throw an exception. 413 if (entryDN == null) 414 { 415 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 416 ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID())); 417 } 418 419 // If the requested entry was the backend base entry, then retrieve it. 420 if (entryDN.equals(backupBaseDN)) 421 { 422 return backupBaseEntry.duplicate(true); 423 } 424 425 // See if the requested entry was one level below the backend base entry. 426 // If so, then it must point to a backup directory. Otherwise, it must be 427 // two levels below the backup base entry and must point to a specific 428 // backup. 429 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 430 if (parentDN == null) 431 { 432 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 433 ERR_BACKUP_INVALID_BASE.get(entryDN)); 434 } 435 else if (parentDN.equals(backupBaseDN)) 436 { 437 return getBackupDirectoryEntry(entryDN); 438 } 439 else if (backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN))) 440 { 441 return getBackupEntry(entryDN); 442 } 443 else 444 { 445 LocalizableMessage message = ERR_BACKUP_INVALID_BASE.get(entryDN); 446 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 447 message, backupBaseDN, null); 448 } 449 } 450 451 /** 452 * Generates an entry for a backup directory based on the provided DN. The 453 * DN must contain an RDN component that specifies the path to the backup 454 * directory, and that directory must exist and be a valid backup directory. 455 * 456 * @param entryDN The DN of the backup directory entry to retrieve. 457 * 458 * @return The requested backup directory entry. 459 * 460 * @throws DirectoryException If the specified directory does not exist or 461 * is not a valid backup directory, or if the DN 462 * does not specify any backup directory. 463 */ 464 private Entry getBackupDirectoryEntry(DN entryDN) 465 throws DirectoryException 466 { 467 // Make sure that the DN specifies a backup directory. 468 AttributeType t = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 469 ByteString v = entryDN.rdn().getAttributeValue(t); 470 if (v == null) 471 { 472 LocalizableMessage message = 473 ERR_BACKUP_DN_DOES_NOT_SPECIFY_DIRECTORY.get(entryDN); 474 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, 475 backupBaseDN, null); 476 } 477 478 // Get a handle to the backup directory and the information that it 479 // contains. 480 BackupDirectory backupDirectory; 481 try 482 { 483 File dir = new File(v.toString()); 484 backupDirectory = backupDirectories.get(dir).getBackupDirectory(); 485 } 486 catch (ConfigException ce) 487 { 488 logger.traceException(ce); 489 490 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 491 ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(entryDN, ce.getMessage())); 492 } 493 catch (Exception e) 494 { 495 logger.traceException(e); 496 497 LocalizableMessage message = 498 ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e)); 499 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 500 message); 501 } 502 503 // Construct the backup directory entry to return. 504 LinkedHashMap<ObjectClass,String> ocMap = new LinkedHashMap<>(2); 505 ocMap.put(CoreSchema.getTopObjectClass(), OC_TOP); 506 ocMap.put(getSchema().getObjectClass(OC_BACKUP_DIRECTORY), OC_BACKUP_DIRECTORY); 507 508 LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0); 509 LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(3); 510 userAttrs.put(t, asList(t, v)); 511 512 t = getSchema().getAttributeType(ATTR_BACKUP_BACKEND_DN); 513 userAttrs.put(t, asList(t, ByteString.valueOfUtf8(backupDirectory.getConfigEntryDN().toString()))); 514 515 Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs); 516 e.processVirtualAttributes(); 517 return e; 518 } 519 520 /** 521 * Generates an entry for a backup based on the provided DN. The DN must 522 * have an RDN component that specifies the backup ID, and the parent DN must 523 * have an RDN component that specifies the backup directory. 524 * 525 * @param entryDN The DN of the backup entry to retrieve. 526 * 527 * @return The requested backup entry. 528 * 529 * @throws DirectoryException If the specified backup does not exist or is 530 * invalid. 531 */ 532 private Entry getBackupEntry(DN entryDN) 533 throws DirectoryException 534 { 535 // First, get the backup ID from the entry DN. 536 AttributeType idType = getSchema().getAttributeType(ATTR_BACKUP_ID); 537 ByteString idValue = entryDN.rdn().getAttributeValue(idType); 538 if (idValue == null) { 539 throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(entryDN)); 540 } 541 String backupID = idValue.toString(); 542 543 // Next, get the backup directory from the parent DN. 544 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 545 if (parentDN == null) { 546 throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_PARENT_DN.get(entryDN)); 547 } 548 549 AttributeType t = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 550 ByteString v = parentDN.rdn().getAttributeValue(t); 551 if (v == null) { 552 throw newConstraintViolation(ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(entryDN)); 553 } 554 555 BackupDirectory backupDirectory; 556 try { 557 backupDirectory = backupDirectories.get(new File(v.toString())).getBackupDirectory(); 558 } catch (ConfigException ce) { 559 logger.traceException(ce); 560 561 throw newConstraintViolation(ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(entryDN, ce.getMessageObject())); 562 } catch (Exception e) { 563 logger.traceException(e); 564 565 LocalizableMessage message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY 566 .get(getExceptionMessage(e)); 567 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 568 message); 569 } 570 571 BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID); 572 if (backupInfo == null) { 573 LocalizableMessage message = ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory 574 .getPath()); 575 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 576 parentDN, null); 577 } 578 579 // Construct the backup entry to return. 580 LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(3); 581 ocMap.put(CoreSchema.getTopObjectClass(), OC_TOP); 582 ocMap.put(getSchema().getObjectClass(OC_BACKUP_INFO), OC_BACKUP_INFO); 583 ocMap.put(CoreSchema.getExtensibleObjectObjectClass(), OC_EXTENSIBLE_OBJECT); 584 585 LinkedHashMap<AttributeType, List<Attribute>> opAttrs = new LinkedHashMap<>(0); 586 LinkedHashMap<AttributeType, List<Attribute>> userAttrs = new LinkedHashMap<>(); 587 userAttrs.put(idType, asList(idType, idValue)); 588 589 backupInfo.getBackupDirectory(); 590 userAttrs.put(t, asList(t, v)); 591 592 Date backupDate = backupInfo.getBackupDate(); 593 if (backupDate != null) { 594 t = getSchema().getAttributeType(ATTR_BACKUP_DATE); 595 userAttrs.put(t, 596 asList(t, ByteString.valueOfUtf8(GeneralizedTimeSyntax.format(backupDate)))); 597 } 598 599 putBoolean(userAttrs, ATTR_BACKUP_COMPRESSED, backupInfo.isCompressed()); 600 putBoolean(userAttrs, ATTR_BACKUP_ENCRYPTED, backupInfo.isEncrypted()); 601 putBoolean(userAttrs, ATTR_BACKUP_INCREMENTAL, backupInfo.isIncremental()); 602 603 Set<String> dependencies = backupInfo.getDependencies(); 604 if (dependencies != null && !dependencies.isEmpty()) { 605 t = getSchema().getAttributeType(ATTR_BACKUP_DEPENDENCY); 606 AttributeBuilder builder = new AttributeBuilder(t); 607 builder.addAllStrings(dependencies); 608 userAttrs.put(t, builder.toAttributeList()); 609 } 610 611 byte[] signedHash = backupInfo.getSignedHash(); 612 if (signedHash != null) { 613 putByteString(userAttrs, ATTR_BACKUP_SIGNED_HASH, signedHash); 614 } 615 616 byte[] unsignedHash = backupInfo.getUnsignedHash(); 617 if (unsignedHash != null) { 618 putByteString(userAttrs, ATTR_BACKUP_UNSIGNED_HASH, unsignedHash); 619 } 620 621 Map<String, String> properties = backupInfo.getBackupProperties(); 622 if (properties != null && !properties.isEmpty()) { 623 for (Map.Entry<String, String> e : properties.entrySet()) { 624 t = getSchema().getAttributeType(toLowerCase(e.getKey())); 625 userAttrs.put(t, asList(t, ByteString.valueOfUtf8(e.getValue()))); 626 } 627 } 628 629 Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs); 630 e.processVirtualAttributes(); 631 return e; 632 } 633 634 private void putByteString(LinkedHashMap<AttributeType, List<Attribute>> userAttrs, String attrName, byte[] value) 635 { 636 AttributeType t = getSchema().getAttributeType(attrName); 637 userAttrs.put(t, asList(t, ByteString.wrap(value))); 638 } 639 640 private void putBoolean(LinkedHashMap<AttributeType, List<Attribute>> attrsMap, String attrName, boolean value) 641 { 642 AttributeType t = getSchema().getAttributeType(attrName); 643 attrsMap.put(t, asList(t, createBooleanValue(value))); 644 } 645 646 private List<Attribute> asList(AttributeType attrType, ByteString value) 647 { 648 return Attributes.createAsList(attrType, value); 649 } 650 651 private DirectoryException newConstraintViolation(LocalizableMessage message) 652 { 653 return new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 654 } 655 656 @Override 657 public void addEntry(Entry entry, AddOperation addOperation) 658 throws DirectoryException 659 { 660 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 661 ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID())); 662 } 663 664 @Override 665 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 666 throws DirectoryException 667 { 668 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 669 ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID())); 670 } 671 672 @Override 673 public void replaceEntry(Entry oldEntry, Entry newEntry, 674 ModifyOperation modifyOperation) throws DirectoryException 675 { 676 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 677 ERR_BACKEND_MODIFY_NOT_SUPPORTED.get(oldEntry.getName(), getBackendID())); 678 } 679 680 @Override 681 public void renameEntry(DN currentDN, Entry entry, 682 ModifyDNOperation modifyDNOperation) 683 throws DirectoryException 684 { 685 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 686 ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); 687 } 688 689 @Override 690 public void search(SearchOperation searchOperation) 691 throws DirectoryException 692 { 693 // Get the base entry for the search, if possible. If it doesn't exist, 694 // then this will throw an exception. 695 DN baseDN = searchOperation.getBaseDN(); 696 Entry baseEntry = getEntry(baseDN); 697 698 // Look at the base DN and see if it's the backup base DN, a backup 699 // directory entry DN, or a backup entry DN. 700 DN parentDN; 701 SearchScope scope = searchOperation.getScope(); 702 SearchFilter filter = searchOperation.getFilter(); 703 if (backupBaseDN.equals(baseDN)) 704 { 705 if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE) 706 && filter.matchesEntry(baseEntry)) 707 { 708 searchOperation.returnEntry(baseEntry, null); 709 } 710 711 if (scope != SearchScope.BASE_OBJECT && !backupDirectories.isEmpty()) 712 { 713 AttributeType backupPathType = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 714 for (File dir : backupDirectories.keySet()) 715 { 716 // Check to see if the descriptor file exists. If not, then skip this 717 // backup directory. 718 File descriptorFile = new File(dir, BACKUP_DIRECTORY_DESCRIPTOR_FILE); 719 if (! descriptorFile.exists()) 720 { 721 continue; 722 } 723 724 DN backupDirDN = makeChildDN(backupBaseDN, backupPathType, 725 dir.getAbsolutePath()); 726 727 Entry backupDirEntry; 728 try 729 { 730 backupDirEntry = getBackupDirectoryEntry(backupDirDN); 731 } 732 catch (Exception e) 733 { 734 logger.traceException(e); 735 736 continue; 737 } 738 739 if (filter.matchesEntry(backupDirEntry)) 740 { 741 searchOperation.returnEntry(backupDirEntry, null); 742 } 743 744 if (scope != SearchScope.SINGLE_LEVEL) 745 { 746 List<Attribute> attrList = backupDirEntry.getAttribute(backupPathType); 747 returnEntries(searchOperation, backupDirDN, filter, attrList); 748 } 749 } 750 } 751 } 752 else if (backupBaseDN.equals(parentDN = DirectoryServer.getParentDNInSuffix(baseDN))) 753 { 754 Entry backupDirEntry = getBackupDirectoryEntry(baseDN); 755 756 if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE) 757 && filter.matchesEntry(backupDirEntry)) 758 { 759 searchOperation.returnEntry(backupDirEntry, null); 760 } 761 762 if (scope != SearchScope.BASE_OBJECT) 763 { 764 AttributeType t = getSchema().getAttributeType(ATTR_BACKUP_DIRECTORY_PATH); 765 List<Attribute> attrList = backupDirEntry.getAttribute(t); 766 returnEntries(searchOperation, baseDN, filter, attrList); 767 } 768 } 769 else 770 { 771 if (parentDN == null 772 || !backupBaseDN.equals(DirectoryServer.getParentDNInSuffix(parentDN))) 773 { 774 LocalizableMessage message = ERR_BACKUP_NO_SUCH_ENTRY.get(backupBaseDN); 775 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 776 } 777 778 if (scope == SearchScope.BASE_OBJECT || 779 scope == SearchScope.WHOLE_SUBTREE) 780 { 781 Entry backupEntry = getBackupEntry(baseDN); 782 if (backupEntry == null) 783 { 784 LocalizableMessage message = ERR_BACKUP_NO_SUCH_ENTRY.get(backupBaseDN); 785 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 786 } 787 788 if (filter.matchesEntry(backupEntry)) 789 { 790 searchOperation.returnEntry(backupEntry, null); 791 } 792 } 793 } 794 } 795 796 private void returnEntries(SearchOperation searchOperation, DN baseDN, SearchFilter filter, List<Attribute> attrList) 797 { 798 for (ByteString v : attrList.get(0)) 799 { 800 try 801 { 802 File dir = new File(v.toString()); 803 BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory(); 804 AttributeType idType = getSchema().getAttributeType(ATTR_BACKUP_ID); 805 806 for (String backupID : backupDirectory.getBackups().keySet()) 807 { 808 DN backupEntryDN = makeChildDN(baseDN, idType, backupID); 809 Entry backupEntry = getBackupEntry(backupEntryDN); 810 if (filter.matchesEntry(backupEntry)) 811 { 812 searchOperation.returnEntry(backupEntry, null); 813 } 814 } 815 } 816 catch (Exception e) 817 { 818 logger.traceException(e); 819 820 continue; 821 } 822 } 823 } 824 825 @Override 826 public Set<String> getSupportedControls() 827 { 828 return Collections.emptySet(); 829 } 830 831 @Override 832 public Set<String> getSupportedFeatures() 833 { 834 return Collections.emptySet(); 835 } 836 837 @Override 838 public boolean supports(BackendOperation backendOperation) 839 { 840 return false; 841 } 842 843 @Override 844 public void exportLDIF(LDIFExportConfig exportConfig) 845 throws DirectoryException 846 { 847 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 848 ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); 849 } 850 851 @Override 852 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 853 throws DirectoryException 854 { 855 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 856 ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); 857 } 858 859 @Override 860 public void createBackup(BackupConfig backupConfig) 861 throws DirectoryException 862 { 863 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 864 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 865 } 866 867 @Override 868 public void removeBackup(BackupDirectory backupDirectory, 869 String backupID) 870 throws DirectoryException 871 { 872 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 873 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 874 } 875 876 @Override 877 public void restoreBackup(RestoreConfig restoreConfig) 878 throws DirectoryException 879 { 880 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 881 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 882 } 883 884 @Override 885 public boolean isConfigurationChangeAcceptable( 886 BackupBackendCfg cfg, List<LocalizableMessage> unacceptableReasons) 887 { 888 // We'll accept anything here. The only configurable attribute is the 889 // default set of backup directories, but that doesn't require any 890 // validation at this point. 891 return true; 892 } 893 894 @Override 895 public ConfigChangeResult applyConfigurationChange(BackupBackendCfg cfg) 896 { 897 final ConfigChangeResult ccr = new ConfigChangeResult(); 898 899 Set<String> values = cfg.getBackupDirectory(); 900 backupDirectories = new LinkedHashMap<>(values.size()); 901 for (String s : values) 902 { 903 File dir = getFileForPath(s); 904 backupDirectories.put(dir, new CachedBackupDirectory(dir)); 905 } 906 907 currentConfig = cfg; 908 return ccr; 909 } 910 911 /** 912 * Create a new child DN from a given parent DN. The child RDN is formed 913 * from a given attribute type and string value. 914 * @param parentDN The DN of the parent. 915 * @param rdnAttrType The attribute type of the RDN. 916 * @param rdnStringValue The string value of the RDN. 917 * @return A new child DN. 918 */ 919 public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType, 920 String rdnStringValue) 921 { 922 ByteString attrValue = ByteString.valueOfUtf8(rdnStringValue); 923 return parentDN.child(new RDN(rdnAttrType, attrValue)); 924 } 925}