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