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