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 2014-2016 ForgeRock AS. 015 */ 016package org.opends.server.core; 017 018import static org.forgerock.util.Utils.*; 019import static org.opends.messages.ConfigMessages.*; 020import static org.opends.server.config.ConfigConstants.*; 021 022import java.io.File; 023import java.io.FileReader; 024import java.io.IOException; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Set; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.CopyOnWriteArrayList; 031 032import org.forgerock.i18n.LocalizableMessage; 033import org.forgerock.i18n.LocalizableMessageBuilder; 034import org.forgerock.i18n.slf4j.LocalizedLogger; 035import org.forgerock.opendj.config.server.ConfigChangeResult; 036import org.forgerock.opendj.config.server.ConfigException; 037import org.forgerock.opendj.config.server.spi.ConfigAddListener; 038import org.forgerock.opendj.config.server.spi.ConfigChangeListener; 039import org.forgerock.opendj.config.server.spi.ConfigDeleteListener; 040import org.forgerock.opendj.config.server.spi.ConfigurationRepository; 041import org.forgerock.opendj.ldap.CancelRequestListener; 042import org.forgerock.opendj.ldap.CancelledResultException; 043import org.forgerock.opendj.ldap.DN; 044import org.forgerock.opendj.ldap.Entry; 045import org.forgerock.opendj.ldap.Filter; 046import org.forgerock.opendj.ldap.LdapException; 047import org.forgerock.opendj.ldap.LdapResultHandler; 048import org.forgerock.opendj.ldap.MemoryBackend; 049import org.forgerock.opendj.ldap.RequestContext; 050import org.forgerock.opendj.ldap.ResultCode; 051import org.forgerock.opendj.ldap.SearchResultHandler; 052import org.forgerock.opendj.ldap.SearchScope; 053import org.forgerock.opendj.ldap.requests.Requests; 054import org.forgerock.opendj.ldap.responses.Result; 055import org.forgerock.opendj.ldap.responses.SearchResultEntry; 056import org.forgerock.opendj.ldap.responses.SearchResultReference; 057import org.forgerock.opendj.ldap.schema.Schema; 058import org.forgerock.opendj.ldap.schema.SchemaBuilder; 059import org.forgerock.opendj.ldif.EntryReader; 060import org.forgerock.opendj.ldif.LDIFEntryReader; 061import org.forgerock.util.Utils; 062import org.opends.server.types.DirectoryEnvironmentConfig; 063import org.opends.server.types.DirectoryException; 064import org.opends.server.types.InitializationException; 065 066/** 067 * Responsible for managing configuration entries and listeners on these 068 * entries. 069 */ 070public class ConfigurationHandler implements ConfigurationRepository 071{ 072 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 073 074 private static final String CONFIGURATION_FILE_NAME = "02-config.ldif"; 075 076 private final ServerContext serverContext; 077 078 /** The complete path to the configuration file to use. */ 079 private File configFile; 080 081 /** Indicates whether to start using the last known good configuration. */ 082 private boolean useLastKnownGoodConfig; 083 084 /** Backend containing the configuration entries. */ 085 private MemoryBackend backend; 086 087 /** The config root entry. */ 088 private Entry rootEntry; 089 090 /** The add/delete/change listeners on configuration entries. */ 091 private final ConcurrentHashMap<DN, EntryListeners> listeners = new ConcurrentHashMap<>(); 092 093 /** Schema with configuration-related elements. */ 094 private Schema configEnabledSchema; 095 096 /** 097 * Creates a new instance. 098 * 099 * @param serverContext 100 * The server context. 101 */ 102 public ConfigurationHandler(final ServerContext serverContext) 103 { 104 this.serverContext = serverContext; 105 } 106 107 /** 108 * Initialize the configuration. 109 * 110 * @throws InitializationException 111 * If an error occurs during the initialization. 112 */ 113 public void initialize() throws InitializationException 114 { 115 final DirectoryEnvironmentConfig environment = serverContext.getEnvironment(); 116 useLastKnownGoodConfig = environment.useLastKnownGoodConfiguration(); 117 configFile = findConfigFileToUse(environment.getConfigFile()); 118 119 configEnabledSchema = loadConfigEnabledSchema(); 120 loadConfiguration(configFile, configEnabledSchema); 121 } 122 123 /** Holds add, change and delete listeners for a given configuration entry. */ 124 private static class EntryListeners { 125 126 /** The set of add listeners that have been registered with this entry. */ 127 private final CopyOnWriteArrayList<ConfigAddListener> addListeners = new CopyOnWriteArrayList<>(); 128 /** The set of change listeners that have been registered with this entry. */ 129 private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners = new CopyOnWriteArrayList<>(); 130 /** The set of delete listeners that have been registered with this entry. */ 131 private final CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners = new CopyOnWriteArrayList<>(); 132 133 CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners() 134 { 135 return changeListeners; 136 } 137 138 void registerChangeListener(final ConfigChangeListener listener) 139 { 140 changeListeners.add(listener); 141 } 142 143 boolean deregisterChangeListener(final ConfigChangeListener listener) 144 { 145 return changeListeners.remove(listener); 146 } 147 148 CopyOnWriteArrayList<ConfigAddListener> getAddListeners() 149 { 150 return addListeners; 151 } 152 153 void registerAddListener(final ConfigAddListener listener) 154 { 155 addListeners.addIfAbsent(listener); 156 } 157 158 void deregisterAddListener(final ConfigAddListener listener) 159 { 160 addListeners.remove(listener); 161 } 162 163 CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners() 164 { 165 return deleteListeners; 166 } 167 168 void registerDeleteListener(final ConfigDeleteListener listener) 169 { 170 deleteListeners.addIfAbsent(listener); 171 } 172 173 void deregisterDeleteListener(final ConfigDeleteListener listener) 174 { 175 deleteListeners.remove(listener); 176 } 177 178 } 179 180 /** Request context to be used when requesting the internal backend. */ 181 private static final RequestContext UNCANCELLABLE_REQUEST_CONTEXT = 182 new RequestContext() 183 { 184 /** {@inheritDoc} */ 185 @Override 186 public void removeCancelRequestListener(final CancelRequestListener listener) 187 { 188 // nothing to do 189 } 190 191 /** {@inheritDoc} */ 192 @Override 193 public int getMessageID() 194 { 195 return -1; 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 public void checkIfCancelled(final boolean signalTooLate) 201 throws CancelledResultException 202 { 203 // nothing to do 204 } 205 206 /** {@inheritDoc} */ 207 @Override 208 public void addCancelRequestListener(final CancelRequestListener listener) 209 { 210 // nothing to do 211 212 } 213 }; 214 215 /** Handler for search results. */ 216 private static final class ConfigSearchHandler implements SearchResultHandler 217 { 218 private final Set<Entry> entries = new HashSet<>(); 219 220 Set<Entry> getEntries() 221 { 222 return entries; 223 } 224 225 /** {@inheritDoc} */ 226 @Override 227 public boolean handleReference(SearchResultReference reference) 228 { 229 throw new UnsupportedOperationException("Search references are not supported for configuration entries."); 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public boolean handleEntry(SearchResultEntry entry) 235 { 236 entries.add(entry); 237 return true; 238 } 239 } 240 241 /** Handler for LDAP operations. */ 242 private static final class ConfigResultHandler implements LdapResultHandler<Result> { 243 244 private LdapException resultError; 245 246 LdapException getResultError() 247 { 248 return resultError; 249 } 250 251 boolean hasCompletedSuccessfully() { 252 return resultError == null; 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public void handleResult(Result result) 258 { 259 // nothing to do 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public void handleException(LdapException exception) 265 { 266 resultError = exception; 267 } 268 } 269 270 /** 271 * Returns the configuration root entry. 272 * 273 * @return the root entry 274 */ 275 public Entry getRootEntry() { 276 return rootEntry; 277 } 278 279 /** {@inheritDoc} */ 280 @Override 281 public Entry getEntry(final DN dn) throws ConfigException { 282 Entry entry = backend.get(dn); 283 if (entry == null) 284 { 285 // TODO : fix message 286 LocalizableMessage message = LocalizableMessage.raw("Unable to retrieve the configuration entry %s", dn); 287 throw new ConfigException(message); 288 } 289 return entry; 290 } 291 292 /** {@inheritDoc} */ 293 @Override 294 public boolean hasEntry(final DN dn) throws ConfigException { 295 return backend.get(dn) != null; 296 } 297 298 /** {@inheritDoc} */ 299 @Override 300 public Set<DN> getChildren(DN dn) throws ConfigException { 301 final ConfigResultHandler resultHandler = new ConfigResultHandler(); 302 final ConfigSearchHandler searchHandler = new ConfigSearchHandler(); 303 304 backend.handleSearch( 305 UNCANCELLABLE_REQUEST_CONTEXT, 306 Requests.newSearchRequest(dn, SearchScope.SINGLE_LEVEL, Filter.objectClassPresent()), 307 null, searchHandler, resultHandler); 308 309 if (resultHandler.hasCompletedSuccessfully()) 310 { 311 final Set<DN> children = new HashSet<>(); 312 for (final Entry entry : searchHandler.getEntries()) 313 { 314 children.add(entry.getName()); 315 } 316 return children; 317 } 318 else { 319 // TODO : fix message 320 throw new ConfigException( 321 LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", dn), 322 resultHandler.getResultError()); 323 } 324 } 325 326 /** 327 * Retrieves the number of subordinates for the requested entry. 328 * 329 * @param entryDN 330 * The distinguished name of the entry. 331 * @param subtree 332 * {@code true} to include all entries from the requested entry 333 * to the lowest level in the tree or {@code false} to only 334 * include the entries immediately below the requested entry. 335 * @return The number of subordinate entries 336 * @throws ConfigException 337 * If a problem occurs while trying to retrieve the entry. 338 */ 339 public long numSubordinates(final DN entryDN, final boolean subtree) throws ConfigException 340 { 341 final ConfigResultHandler resultHandler = new ConfigResultHandler(); 342 final ConfigSearchHandler searchHandler = new ConfigSearchHandler(); 343 final SearchScope scope = subtree ? SearchScope.SUBORDINATES : SearchScope.SINGLE_LEVEL; 344 backend.handleSearch( 345 UNCANCELLABLE_REQUEST_CONTEXT, 346 Requests.newSearchRequest(entryDN, scope, Filter.objectClassPresent()), 347 null, searchHandler, resultHandler); 348 349 if (resultHandler.hasCompletedSuccessfully()) 350 { 351 return searchHandler.getEntries().size(); 352 } 353 else { 354 // TODO : fix the message 355 throw new ConfigException( 356 LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", entryDN), 357 resultHandler.getResultError()); 358 } 359 } 360 361 /** 362 * Add a configuration entry 363 * <p> 364 * The add is performed only if all Add listeners on the parent entry accept 365 * the changes. Once the change is accepted, entry is effectively added and 366 * all Add listeners are called again to apply the change resulting from this 367 * new entry. 368 * 369 * @param entry 370 * The configuration entry to add. 371 * @throws DirectoryException 372 * If an error occurs. 373 */ 374 public void addEntry(final Entry entry) throws DirectoryException 375 { 376 final DN entryDN = entry.getName(); 377 if (backend.contains(entryDN)) 378 { 379 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, ERR_CONFIG_FILE_ADD_ALREADY_EXISTS.get(entryDN)); 380 } 381 382 final DN parentDN = retrieveParentDN(entryDN); 383 384 // Iterate through add listeners to make sure the new entry is acceptable. 385 final List<ConfigAddListener> addListeners = getAddListeners(parentDN); 386 final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); 387 for (final ConfigAddListener listener : addListeners) 388 { 389 if (!listener.configAddIsAcceptable(entry, unacceptableReason)) 390 { 391 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 392 ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); 393 } 394 } 395 396 // Add the entry. 397 final ConfigResultHandler resultHandler = new ConfigResultHandler(); 398 backend.handleAdd(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newAddRequest(entry), null, resultHandler); 399 400 if (!resultHandler.hasCompletedSuccessfully()) { 401 // TODO fix the message : error when adding config entry 402 // use resultHandler.getResultError() to get the error 403 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 404 ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); 405 } 406 407 // Notify all the add listeners to apply the new configuration entry. 408 ResultCode resultCode = ResultCode.SUCCESS; 409 final List<LocalizableMessage> messages = new LinkedList<>(); 410 for (final ConfigAddListener listener : addListeners) 411 { 412 final ConfigChangeResult result = listener.applyConfigurationAdd(entry); 413 if (result.getResultCode() != ResultCode.SUCCESS) 414 { 415 resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; 416 messages.addAll(result.getMessages()); 417 } 418 419 handleConfigChangeResult(result, entry.getName(), listener.getClass().getName(), "applyConfigurationAdd"); 420 } 421 422 if (resultCode != ResultCode.SUCCESS) 423 { 424 final String reasons = Utils.joinAsString(". ", messages); 425 throw new DirectoryException(resultCode, ERR_CONFIG_FILE_ADD_APPLY_FAILED.get(reasons)); 426 } 427 } 428 429 /** 430 * Delete a configuration entry. 431 * <p> 432 * The delete is performed only if all Delete listeners on the parent entry 433 * accept the changes. Once the change is accepted, entry is effectively 434 * deleted and all Delete listeners are called again to apply the change 435 * resulting from this deletion. 436 * 437 * @param dn 438 * DN of entry to delete. 439 * @throws DirectoryException 440 * If a problem occurs. 441 */ 442 public void deleteEntry(final DN dn) throws DirectoryException 443 { 444 // Entry must exist. 445 if (!backend.contains(dn)) 446 { 447 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 448 ERR_CONFIG_FILE_DELETE_NO_SUCH_ENTRY.get(dn), getMatchedDN(dn), null); 449 } 450 451 // Entry must not have children. 452 try 453 { 454 if (!getChildren(dn).isEmpty()) 455 { 456 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, 457 ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn)); 458 } 459 } 460 catch (ConfigException e) 461 { 462 // TODO : fix message = ERROR BACKEND CONFIG 463 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 464 ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn), e); 465 } 466 467 // TODO : pass in the localizable message (2) 468 final DN parentDN = retrieveParentDN(dn); 469 470 // Iterate through delete listeners to make sure the deletion is acceptable. 471 final List<ConfigDeleteListener> deleteListeners = getDeleteListeners(parentDN); 472 final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); 473 final Entry entry = backend.get(dn); 474 for (final ConfigDeleteListener listener : deleteListeners) 475 { 476 if (!listener.configDeleteIsAcceptable(entry, unacceptableReason)) 477 { 478 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 479 ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entry, parentDN, unacceptableReason)); 480 } 481 } 482 483 // Delete the entry 484 final ConfigResultHandler resultHandler = new ConfigResultHandler(); 485 backend.handleDelete(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newDeleteRequest(dn), null, resultHandler); 486 487 if (!resultHandler.hasCompletedSuccessfully()) { 488 // TODO fix message : error when deleting config entry 489 // use resultHandler.getResultError() to get the error 490 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 491 ERR_CONFIG_FILE_DELETE_REJECTED.get(dn, parentDN, unacceptableReason)); 492 } 493 494 // Notify all the delete listeners that the entry has been removed. 495 ResultCode resultCode = ResultCode.SUCCESS; 496 final List<LocalizableMessage> messages = new LinkedList<>(); 497 for (final ConfigDeleteListener listener : deleteListeners) 498 { 499 final ConfigChangeResult result = listener.applyConfigurationDelete(entry); 500 if (result.getResultCode() != ResultCode.SUCCESS) 501 { 502 resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; 503 messages.addAll(result.getMessages()); 504 } 505 506 handleConfigChangeResult(result, dn, listener.getClass().getName(), "applyConfigurationDelete"); 507 } 508 509 if (resultCode != ResultCode.SUCCESS) 510 { 511 final String reasons = Utils.joinAsString(". ", messages); 512 throw new DirectoryException(resultCode, ERR_CONFIG_FILE_DELETE_APPLY_FAILED.get(reasons)); 513 } 514 } 515 516 /** 517 * Replaces the old configuration entry with the new configuration entry 518 * provided. 519 * <p> 520 * The replacement is performed only if all Change listeners on the entry 521 * accept the changes. Once the change is accepted, entry is effectively 522 * replaced and all Change listeners are called again to apply the change 523 * resulting from the replacement. 524 * 525 * @param oldEntry 526 * The original entry that is being replaced. 527 * @param newEntry 528 * The new entry to use in place of the existing entry with the same 529 * DN. 530 * @throws DirectoryException 531 * If a problem occurs while trying to replace the entry. 532 */ 533 public void replaceEntry(final Entry oldEntry, final Entry newEntry) 534 throws DirectoryException 535 { 536 final DN entryDN = oldEntry.getName(); 537 if (!backend.contains(entryDN)) 538 { 539 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 540 ERR_CONFIG_FILE_MODIFY_NO_SUCH_ENTRY.get(oldEntry), getMatchedDN(entryDN), null); 541 } 542 543 //TODO : add objectclass and attribute to the config schema in order to get this code run 544// if (!Entries.getStructuralObjectClass(oldEntry, configEnabledSchema) 545// .equals(Entries.getStructuralObjectClass(newEntry, configEnabledSchema))) 546// { 547// throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 548// ERR_CONFIG_FILE_MODIFY_STRUCTURAL_CHANGE_NOT_ALLOWED.get(entryDN)); 549// } 550 551 // Iterate through change listeners to make sure the change is acceptable. 552 final List<ConfigChangeListener> changeListeners = getChangeListeners(entryDN); 553 final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); 554 for (ConfigChangeListener listeners : changeListeners) 555 { 556 if (!listeners.configChangeIsAcceptable(newEntry, unacceptableReason)) 557 { 558 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 559 ERR_CONFIG_FILE_MODIFY_REJECTED_BY_CHANGE_LISTENER.get(entryDN, unacceptableReason)); 560 } 561 } 562 563 // Replace the old entry with new entry. 564 final ConfigResultHandler resultHandler = new ConfigResultHandler(); 565 backend.handleModify( 566 UNCANCELLABLE_REQUEST_CONTEXT, 567 Requests.newModifyRequest(oldEntry, newEntry), 568 null, 569 resultHandler); 570 571 if (!resultHandler.hasCompletedSuccessfully()) 572 { 573 // TODO fix message : error when replacing config entry 574 // use resultHandler.getResultError() to get the error 575 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 576 ERR_CONFIG_FILE_DELETE_REJECTED.get(entryDN, entryDN, unacceptableReason)); 577 } 578 579 // Notify all the change listeners of the update. 580 ResultCode resultCode = ResultCode.SUCCESS; 581 final List<LocalizableMessage> messages = new LinkedList<>(); 582 for (final ConfigChangeListener listener : changeListeners) 583 { 584 final ConfigChangeResult result = listener.applyConfigurationChange(newEntry); 585 if (result.getResultCode() != ResultCode.SUCCESS) 586 { 587 resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; 588 messages.addAll(result.getMessages()); 589 } 590 591 handleConfigChangeResult(result, entryDN, listener.getClass().getName(), "applyConfigurationChange"); 592 } 593 594 if (resultCode != ResultCode.SUCCESS) 595 { 596 throw new DirectoryException(resultCode, 597 ERR_CONFIG_FILE_MODIFY_APPLY_FAILED.get(Utils.joinAsString(". ", messages))); 598 } 599 } 600 601 /** {@inheritDoc} */ 602 @Override 603 public void registerAddListener(final DN dn, final ConfigAddListener listener) 604 { 605 getEntryListeners(dn).registerAddListener(listener); 606 } 607 608 /** {@inheritDoc} */ 609 @Override 610 public void registerDeleteListener(final DN dn, final ConfigDeleteListener listener) 611 { 612 getEntryListeners(dn).registerDeleteListener(listener); 613 } 614 615 /** {@inheritDoc} */ 616 @Override 617 public void registerChangeListener(final DN dn, final ConfigChangeListener listener) 618 { 619 getEntryListeners(dn).registerChangeListener(listener); 620 } 621 622 /** {@inheritDoc} */ 623 @Override 624 public void deregisterAddListener(final DN dn, final ConfigAddListener listener) 625 { 626 getEntryListeners(dn).deregisterAddListener(listener); 627 } 628 629 /** {@inheritDoc} */ 630 @Override 631 public void deregisterDeleteListener(final DN dn, final ConfigDeleteListener listener) 632 { 633 getEntryListeners(dn).deregisterDeleteListener(listener); 634 } 635 636 /** {@inheritDoc} */ 637 @Override 638 public boolean deregisterChangeListener(final DN dn, final ConfigChangeListener listener) 639 { 640 return getEntryListeners(dn).deregisterChangeListener(listener); 641 } 642 643 /** {@inheritDoc} */ 644 @Override 645 public List<ConfigAddListener> getAddListeners(final DN dn) 646 { 647 return getEntryListeners(dn).getAddListeners(); 648 } 649 650 /** {@inheritDoc} */ 651 @Override 652 public List<ConfigDeleteListener> getDeleteListeners(final DN dn) 653 { 654 return getEntryListeners(dn).getDeleteListeners(); 655 } 656 657 /** {@inheritDoc} */ 658 @Override 659 public List<ConfigChangeListener> getChangeListeners(final DN dn) 660 { 661 return getEntryListeners(dn).getChangeListeners(); 662 } 663 664 /** Load the configuration-enabled schema that will allow to read configuration file. */ 665 private Schema loadConfigEnabledSchema() throws InitializationException { 666 LDIFEntryReader reader = null; 667 try 668 { 669 final File schemaDir = serverContext.getEnvironment().getSchemaDirectory(); 670 reader = new LDIFEntryReader(new FileReader(new File(schemaDir, CONFIGURATION_FILE_NAME))); 671 reader.setSchema(Schema.getDefaultSchema()); 672 final Entry entry = reader.readEntry(); 673 return new SchemaBuilder(Schema.getDefaultSchema()).addSchema(entry, false).toSchema(); 674 } 675 catch (Exception e) 676 { 677 // TODO : fix message 678 throw new InitializationException(LocalizableMessage.raw("Unable to load config-enabled schema"), e); 679 } 680 finally { 681 closeSilently(reader); 682 } 683 } 684 685 /** 686 * Read configuration entries from provided configuration file. 687 * 688 * @param configFile 689 * LDIF file with configuration entries. 690 * @param schema 691 * Schema to validate entries when reading the config file. 692 * @throws InitializationException 693 * If an errors occurs. 694 */ 695 private void loadConfiguration(final File configFile, final Schema schema) 696 throws InitializationException 697 { 698 EntryReader reader = null; 699 try 700 { 701 reader = getLDIFReader(configFile, schema); 702 backend = new MemoryBackend(schema, reader); 703 } 704 catch (IOException e) 705 { 706 throw new InitializationException( 707 ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile.getAbsolutePath(), e.getCause()), e); 708 } 709 finally 710 { 711 closeSilently(reader); 712 } 713 714 // Check that root entry is the expected one 715 rootEntry = backend.get(DN_CONFIG_ROOT); 716 if (rootEntry == null) 717 { 718 // fix message : we didn't find the expected root in the file 719 throw new InitializationException(ERR_CONFIG_FILE_INVALID_BASE_DN.get( 720 configFile.getAbsolutePath(), "", DN_CONFIG_ROOT)); 721 } 722 } 723 724 /** 725 * Returns the LDIF reader on configuration entries. 726 * <p> 727 * It is the responsability of the caller to ensure that reader 728 * is closed after usage. 729 * 730 * @param configFile 731 * LDIF file containing the configuration entries. 732 * @param schema 733 * Schema to validate entries when reading the config file. 734 * @return the LDIF reader 735 * @throws InitializationException 736 * If an error occurs. 737 */ 738 private EntryReader getLDIFReader(final File configFile, final Schema schema) 739 throws InitializationException 740 { 741 LDIFEntryReader reader = null; 742 try 743 { 744 reader = new LDIFEntryReader(new FileReader(configFile)); 745 reader.setSchema(schema); 746 } 747 catch (Exception e) 748 { 749 throw new InitializationException( 750 ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(configFile.getAbsolutePath(), e), e); 751 } 752 return reader; 753 } 754 755 /** 756 * Returns the entry listeners attached to the provided DN. 757 * <p> 758 * If no listener exist for the provided DN, then a new set of empty listeners 759 * is created and returned. 760 * 761 * @param dn 762 * DN of a configuration entry. 763 * @return the listeners attached to the corresponding configuration entry. 764 */ 765 private EntryListeners getEntryListeners(final DN dn) { 766 EntryListeners entryListeners = listeners.get(dn); 767 if (entryListeners == null) { 768 entryListeners = new EntryListeners(); 769 final EntryListeners previousListeners = listeners.putIfAbsent(dn, entryListeners); 770 if (previousListeners != null) { 771 entryListeners = previousListeners; 772 } 773 } 774 return entryListeners; 775 } 776 777 /** 778 * Returns the parent DN of the configuration entry corresponding to the 779 * provided DN. 780 * 781 * @param entryDN 782 * DN of entry to retrieve the parent from. 783 * @return the parent DN 784 * @throws DirectoryException 785 * If entry has no parent or parent entry does not exist. 786 */ 787 private DN retrieveParentDN(final DN entryDN) throws DirectoryException 788 { 789 final DN parentDN = entryDN.parent(); 790 // Entry must have a parent. 791 if (parentDN == null) 792 { 793 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_ADD_NO_PARENT_DN.get(entryDN)); 794 } 795 796 // Parent entry must exist. 797 if (!backend.contains(parentDN)) 798 { 799 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, 800 ERR_CONFIG_FILE_ADD_NO_PARENT.get(entryDN, parentDN), getMatchedDN(parentDN), null); 801 } 802 return parentDN; 803 } 804 805 /** 806 * Returns the matched DN that is available in the configuration for the 807 * provided DN. 808 */ 809 private DN getMatchedDN(final DN dn) 810 { 811 DN matchedDN = null; 812 DN parentDN = dn.parent(); 813 while (parentDN != null) 814 { 815 if (backend.contains(parentDN)) 816 { 817 matchedDN = parentDN; 818 break; 819 } 820 parentDN = parentDN.parent(); 821 } 822 return matchedDN; 823 } 824 825 /** 826 * Find the actual configuration file to use to load configuration, given the 827 * standard config file. 828 * 829 * @param standardConfigFile 830 * "Standard" configuration file provided. 831 * @return the actual configuration file to use, which is either the standard 832 * config file provided or the config file corresponding to the last 833 * known good configuration 834 * @throws InitializationException 835 * If a problem occurs. 836 */ 837 private File findConfigFileToUse(final File standardConfigFile) throws InitializationException 838 { 839 File configFileToUse = null; 840 if (useLastKnownGoodConfig) 841 { 842 configFileToUse = new File(standardConfigFile + ".startok"); 843 if (! configFileToUse.exists()) 844 { 845 logger.warn(WARN_CONFIG_FILE_NO_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); 846 useLastKnownGoodConfig = false; 847 configFileToUse = standardConfigFile; 848 } 849 else 850 { 851 logger.info(NOTE_CONFIG_FILE_USING_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); 852 } 853 } 854 else 855 { 856 configFileToUse = standardConfigFile; 857 } 858 859 try 860 { 861 if (! configFileToUse.exists()) 862 { 863 throw new InitializationException(ERR_CONFIG_FILE_DOES_NOT_EXIST.get(configFileToUse.getAbsolutePath())); 864 } 865 } 866 catch (Exception e) 867 { 868 throw new InitializationException( 869 ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get(configFileToUse.getAbsolutePath(), e)); 870 } 871 return configFileToUse; 872 } 873 874 /** 875 * Examines the provided result and logs a message if appropriate. If the 876 * result code is anything other than {@code SUCCESS}, then it will log an 877 * error message. If the operation was successful but admin action is 878 * required, then it will log a warning message. If no action is required but 879 * messages were generated, then it will log an informational message. 880 * 881 * @param result 882 * The config change result object that 883 * @param entryDN 884 * The DN of the entry that was added, deleted, or modified. 885 * @param className 886 * The name of the class for the object that generated the provided 887 * result. 888 * @param methodName 889 * The name of the method that generated the provided result. 890 */ 891 private void handleConfigChangeResult(ConfigChangeResult result, DN entryDN, String className, String methodName) 892 { 893 if (result == null) 894 { 895 logger.error(ERR_CONFIG_CHANGE_NO_RESULT, className, methodName, entryDN); 896 return; 897 } 898 899 final ResultCode resultCode = result.getResultCode(); 900 final boolean adminActionRequired = result.adminActionRequired(); 901 final List<LocalizableMessage> messages = result.getMessages(); 902 903 final String messageBuffer = Utils.joinAsString(" ", messages); 904 if (resultCode != ResultCode.SUCCESS) 905 { 906 logger.error(ERR_CONFIG_CHANGE_RESULT_ERROR, className, methodName, entryDN, resultCode, 907 adminActionRequired, messageBuffer); 908 } 909 else if (adminActionRequired) 910 { 911 logger.warn(WARN_CONFIG_CHANGE_RESULT_ACTION_REQUIRED, className, methodName, entryDN, messageBuffer); 912 } 913 else if (messageBuffer.length() > 0) 914 { 915 logger.debug(INFO_CONFIG_CHANGE_RESULT_MESSAGES, className, methodName, entryDN, messageBuffer); 916 } 917 } 918 919}