/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2014-2015 ForgeRock AS */ package org.opends.server.core; import static org.forgerock.util.Utils.*; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.config.ConfigConstants.*; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.adapter.server3x.Converters; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.spi.ConfigAddListener; import org.forgerock.opendj.config.server.spi.ConfigChangeListener; import org.forgerock.opendj.config.server.spi.ConfigDeleteListener; import org.forgerock.opendj.config.server.spi.ConfigurationRepository; import org.forgerock.opendj.ldap.CancelRequestListener; import org.forgerock.opendj.ldap.CancelledResultException; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.MemoryBackend; import org.forgerock.opendj.ldap.RequestContext; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.LdapResultHandler; import org.forgerock.opendj.ldap.SearchResultHandler; import org.forgerock.opendj.ldap.SearchScope; import org.forgerock.opendj.ldap.requests.Requests; import org.forgerock.opendj.ldap.responses.Result; import org.forgerock.opendj.ldap.responses.SearchResultEntry; import org.forgerock.opendj.ldap.responses.SearchResultReference; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.forgerock.opendj.ldif.EntryReader; import org.forgerock.opendj.ldif.LDIFEntryReader; import org.forgerock.util.Utils; import org.opends.server.types.DirectoryEnvironmentConfig; import org.opends.server.types.DirectoryException; import org.opends.server.types.InitializationException; /** * Responsible for managing configuration entries and listeners on these * entries. */ public class ConfigurationHandler implements ConfigurationRepository { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private static final String CONFIGURATION_FILE_NAME = "02-config.ldif"; private final ServerContext serverContext; /** The complete path to the configuration file to use. */ private File configFile; /** Indicates whether to start using the last known good configuration. */ private boolean useLastKnownGoodConfig; /** Backend containing the configuration entries. */ private MemoryBackend backend; /** The config root entry. */ private Entry rootEntry; /** The add/delete/change listeners on configuration entries. */ private final ConcurrentHashMap<DN, EntryListeners> listeners = new ConcurrentHashMap<>(); /** Schema with configuration-related elements. */ private Schema configEnabledSchema; /** * Creates a new instance. * * @param serverContext * The server context. */ public ConfigurationHandler(final ServerContext serverContext) { this.serverContext = serverContext; } /** * Initialize the configuration. * * @throws InitializationException * If an error occurs during the initialization. */ public void initialize() throws InitializationException { final DirectoryEnvironmentConfig environment = serverContext.getEnvironment(); useLastKnownGoodConfig = environment.useLastKnownGoodConfiguration(); configFile = findConfigFileToUse(environment.getConfigFile()); configEnabledSchema = loadConfigEnabledSchema(); loadConfiguration(configFile, configEnabledSchema); } /** Holds add, change and delete listeners for a given configuration entry. */ private static class EntryListeners { /** The set of add listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigAddListener> addListeners = new CopyOnWriteArrayList<>(); /** The set of change listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners = new CopyOnWriteArrayList<>(); /** The set of delete listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners = new CopyOnWriteArrayList<>(); CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners() { return changeListeners; } void registerChangeListener(final ConfigChangeListener listener) { changeListeners.add(listener); } boolean deregisterChangeListener(final ConfigChangeListener listener) { return changeListeners.remove(listener); } CopyOnWriteArrayList<ConfigAddListener> getAddListeners() { return addListeners; } void registerAddListener(final ConfigAddListener listener) { addListeners.addIfAbsent(listener); } void deregisterAddListener(final ConfigAddListener listener) { addListeners.remove(listener); } CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners() { return deleteListeners; } void registerDeleteListener(final ConfigDeleteListener listener) { deleteListeners.addIfAbsent(listener); } void deregisterDeleteListener(final ConfigDeleteListener listener) { deleteListeners.remove(listener); } } /** Request context to be used when requesting the internal backend. */ private static final RequestContext UNCANCELLABLE_REQUEST_CONTEXT = new RequestContext() { /** {@inheritDoc} */ @Override public void removeCancelRequestListener(final CancelRequestListener listener) { // nothing to do } /** {@inheritDoc} */ @Override public int getMessageID() { return -1; } /** {@inheritDoc} */ @Override public void checkIfCancelled(final boolean signalTooLate) throws CancelledResultException { // nothing to do } /** {@inheritDoc} */ @Override public void addCancelRequestListener(final CancelRequestListener listener) { // nothing to do } }; /** Handler for search results. */ private static final class ConfigSearchHandler implements SearchResultHandler { private final Set<Entry> entries = new HashSet<>(); Set<Entry> getEntries() { return entries; } /** {@inheritDoc} */ @Override public boolean handleReference(SearchResultReference reference) { throw new UnsupportedOperationException("Search references are not supported for configuration entries."); } /** {@inheritDoc} */ @Override public boolean handleEntry(SearchResultEntry entry) { entries.add(entry); return true; } } /** Handler for LDAP operations. */ private static final class ConfigResultHandler implements LdapResultHandler<Result> { private LdapException resultError; LdapException getResultError() { return resultError; } boolean hasCompletedSuccessfully() { return resultError == null; } /** {@inheritDoc} */ @Override public void handleResult(Result result) { // nothing to do } /** {@inheritDoc} */ @Override public void handleException(LdapException exception) { resultError = exception; } } /** * Returns the configuration root entry. * * @return the root entry */ public Entry getRootEntry() { return rootEntry; } /** {@inheritDoc} */ @Override public Entry getEntry(final DN dn) throws ConfigException { Entry entry = backend.get(dn); if (entry == null) { // TODO : fix message LocalizableMessage message = LocalizableMessage.raw("Unable to retrieve the configuration entry %s", dn); throw new ConfigException(message); } return entry; } /** {@inheritDoc} */ @Override public boolean hasEntry(final DN dn) throws ConfigException { return backend.get(dn) != null; } /** {@inheritDoc} */ @Override public Set<DN> getChildren(DN dn) throws ConfigException { final ConfigResultHandler resultHandler = new ConfigResultHandler(); final ConfigSearchHandler searchHandler = new ConfigSearchHandler(); backend.handleSearch( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newSearchRequest(dn, SearchScope.SINGLE_LEVEL, Filter.objectClassPresent()), null, searchHandler, resultHandler); if (resultHandler.hasCompletedSuccessfully()) { final Set<DN> children = new HashSet<>(); for (final Entry entry : searchHandler.getEntries()) { children.add(entry.getName()); } return children; } else { // TODO : fix message throw new ConfigException( LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", dn), resultHandler.getResultError()); } } /** * Retrieves the number of subordinates for the requested entry. * * @param entryDN * The distinguished name of the entry. * @param subtree * {@code true} to include all entries from the requested entry * to the lowest level in the tree or {@code false} to only * include the entries immediately below the requested entry. * @return The number of subordinate entries * @throws ConfigException * If a problem occurs while trying to retrieve the entry. */ public long numSubordinates(final DN entryDN, final boolean subtree) throws ConfigException { final ConfigResultHandler resultHandler = new ConfigResultHandler(); final ConfigSearchHandler searchHandler = new ConfigSearchHandler(); final SearchScope scope = subtree ? SearchScope.SUBORDINATES : SearchScope.SINGLE_LEVEL; backend.handleSearch( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newSearchRequest(entryDN, scope, Filter.objectClassPresent()), null, searchHandler, resultHandler); if (resultHandler.hasCompletedSuccessfully()) { return searchHandler.getEntries().size(); } else { // TODO : fix the message throw new ConfigException( LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", entryDN), resultHandler.getResultError()); } } /** * Add a configuration entry * <p> * The add is performed only if all Add listeners on the parent entry accept * the changes. Once the change is accepted, entry is effectively added and * all Add listeners are called again to apply the change resulting from this * new entry. * * @param entry * The configuration entry to add. * @throws DirectoryException * If an error occurs. */ public void addEntry(final Entry entry) throws DirectoryException { final DN entryDN = entry.getName(); if (backend.contains(entryDN)) { throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, ERR_CONFIG_FILE_ADD_ALREADY_EXISTS.get(entryDN)); } final DN parentDN = retrieveParentDN(entryDN); // Iterate through add listeners to make sure the new entry is acceptable. final List<ConfigAddListener> addListeners = getAddListeners(parentDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); for (final ConfigAddListener listener : addListeners) { if (!listener.configAddIsAcceptable(entry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); } } // Add the entry. final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleAdd(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newAddRequest(entry), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix the message : error when adding config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); } // Notify all the add listeners to apply the new configuration entry. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<>(); for (final ConfigAddListener listener : addListeners) { final ConfigChangeResult result = listener.applyConfigurationAdd(entry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, entry.getName(), listener.getClass().getName(), "applyConfigurationAdd"); } if (resultCode != ResultCode.SUCCESS) { final String reasons = Utils.joinAsString(". ", messages); throw new DirectoryException(resultCode, ERR_CONFIG_FILE_ADD_APPLY_FAILED.get(reasons)); } } /** * Delete a configuration entry. * <p> * The delete is performed only if all Delete listeners on the parent entry * accept the changes. Once the change is accepted, entry is effectively * deleted and all Delete listeners are called again to apply the change * resulting from this deletion. * * @param dn * DN of entry to delete. * @throws DirectoryException * If a problem occurs. */ public void deleteEntry(final DN dn) throws DirectoryException { // Entry must exist. if (!backend.contains(dn)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_DELETE_NO_SUCH_ENTRY.get(dn), Converters.to(getMatchedDN(dn)), null); } // Entry must not have children. try { if (!getChildren(dn).isEmpty()) { throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn)); } } catch (ConfigException e) { // TODO : fix message = ERROR BACKEND CONFIG throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn), e); } // TODO : pass in the localizable message (2) final DN parentDN = retrieveParentDN(dn); // Iterate through delete listeners to make sure the deletion is acceptable. final List<ConfigDeleteListener> deleteListeners = getDeleteListeners(parentDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); final Entry entry = backend.get(dn); for (final ConfigDeleteListener listener : deleteListeners) { if (!listener.configDeleteIsAcceptable(entry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entry, parentDN, unacceptableReason)); } } // Delete the entry final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleDelete(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newDeleteRequest(dn), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix message : error when deleting config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_REJECTED.get(dn, parentDN, unacceptableReason)); } // Notify all the delete listeners that the entry has been removed. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<>(); for (final ConfigDeleteListener listener : deleteListeners) { final ConfigChangeResult result = listener.applyConfigurationDelete(entry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, dn, listener.getClass().getName(), "applyConfigurationDelete"); } if (resultCode != ResultCode.SUCCESS) { final String reasons = Utils.joinAsString(". ", messages); throw new DirectoryException(resultCode, ERR_CONFIG_FILE_DELETE_APPLY_FAILED.get(reasons)); } } /** * Replaces the old configuration entry with the new configuration entry * provided. * <p> * The replacement is performed only if all Change listeners on the entry * accept the changes. Once the change is accepted, entry is effectively * replaced and all Change listeners are called again to apply the change * resulting from the replacement. * * @param oldEntry * The original entry that is being replaced. * @param newEntry * The new entry to use in place of the existing entry with the same * DN. * @throws DirectoryException * If a problem occurs while trying to replace the entry. */ public void replaceEntry(final Entry oldEntry, final Entry newEntry) throws DirectoryException { final DN entryDN = oldEntry.getName(); if (!backend.contains(entryDN)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_MODIFY_NO_SUCH_ENTRY.get(oldEntry), Converters.to(getMatchedDN(entryDN)), null); } //TODO : add objectclass and attribute to the config schema in order to get this code run // if (!Entries.getStructuralObjectClass(oldEntry, configEnabledSchema) // .equals(Entries.getStructuralObjectClass(newEntry, configEnabledSchema))) // { // throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, // ERR_CONFIG_FILE_MODIFY_STRUCTURAL_CHANGE_NOT_ALLOWED.get(entryDN)); // } // Iterate through change listeners to make sure the change is acceptable. final List<ConfigChangeListener> changeListeners = getChangeListeners(entryDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); for (ConfigChangeListener listeners : changeListeners) { if (!listeners.configChangeIsAcceptable(newEntry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_MODIFY_REJECTED_BY_CHANGE_LISTENER.get(entryDN, unacceptableReason)); } } // Replace the old entry with new entry. final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleModify( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newModifyRequest(oldEntry, newEntry), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix message : error when replacing config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_REJECTED.get(entryDN, entryDN, unacceptableReason)); } // Notify all the change listeners of the update. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<>(); for (final ConfigChangeListener listener : changeListeners) { final ConfigChangeResult result = listener.applyConfigurationChange(newEntry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, entryDN, listener.getClass().getName(), "applyConfigurationChange"); } if (resultCode != ResultCode.SUCCESS) { throw new DirectoryException(resultCode, ERR_CONFIG_FILE_MODIFY_APPLY_FAILED.get(Utils.joinAsString(". ", messages))); } } /** {@inheritDoc} */ @Override public void registerAddListener(final DN dn, final ConfigAddListener listener) { getEntryListeners(dn).registerAddListener(listener); } /** {@inheritDoc} */ @Override public void registerDeleteListener(final DN dn, final ConfigDeleteListener listener) { getEntryListeners(dn).registerDeleteListener(listener); } /** {@inheritDoc} */ @Override public void registerChangeListener(final DN dn, final ConfigChangeListener listener) { getEntryListeners(dn).registerChangeListener(listener); } /** {@inheritDoc} */ @Override public void deregisterAddListener(final DN dn, final ConfigAddListener listener) { getEntryListeners(dn).deregisterAddListener(listener); } /** {@inheritDoc} */ @Override public void deregisterDeleteListener(final DN dn, final ConfigDeleteListener listener) { getEntryListeners(dn).deregisterDeleteListener(listener); } /** {@inheritDoc} */ @Override public boolean deregisterChangeListener(final DN dn, final ConfigChangeListener listener) { return getEntryListeners(dn).deregisterChangeListener(listener); } /** {@inheritDoc} */ @Override public List<ConfigAddListener> getAddListeners(final DN dn) { return getEntryListeners(dn).getAddListeners(); } /** {@inheritDoc} */ @Override public List<ConfigDeleteListener> getDeleteListeners(final DN dn) { return getEntryListeners(dn).getDeleteListeners(); } /** {@inheritDoc} */ @Override public List<ConfigChangeListener> getChangeListeners(final DN dn) { return getEntryListeners(dn).getChangeListeners(); } /** Load the configuration-enabled schema that will allow to read configuration file. */ private Schema loadConfigEnabledSchema() throws InitializationException { LDIFEntryReader reader = null; try { final File schemaDir = serverContext.getEnvironment().getSchemaDirectory(); reader = new LDIFEntryReader(new FileReader(new File(schemaDir, CONFIGURATION_FILE_NAME))); reader.setSchema(Schema.getDefaultSchema()); final Entry entry = reader.readEntry(); return new SchemaBuilder(Schema.getDefaultSchema()).addSchema(entry, false).toSchema(); } catch (Exception e) { // TODO : fix message throw new InitializationException(LocalizableMessage.raw("Unable to load config-enabled schema"), e); } finally { closeSilently(reader); } } /** * Read configuration entries from provided configuration file. * * @param configFile * LDIF file with configuration entries. * @param schema * Schema to validate entries when reading the config file. * @throws InitializationException * If an errors occurs. */ private void loadConfiguration(final File configFile, final Schema schema) throws InitializationException { EntryReader reader = null; try { reader = getLDIFReader(configFile, schema); backend = new MemoryBackend(schema, reader); } catch (IOException e) { throw new InitializationException( ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile.getAbsolutePath(), e.getCause()), e); } finally { closeSilently(reader); } // Check that root entry is the expected one rootEntry = backend.get(DN_CONFIG_ROOT); if (rootEntry == null) { // fix message : we didn't find the expected root in the file throw new InitializationException(ERR_CONFIG_FILE_INVALID_BASE_DN.get( configFile.getAbsolutePath(), "", DN_CONFIG_ROOT)); } } /** * Returns the LDIF reader on configuration entries. * <p> * It is the responsability of the caller to ensure that reader * is closed after usage. * * @param configFile * LDIF file containing the configuration entries. * @param schema * Schema to validate entries when reading the config file. * @return the LDIF reader * @throws InitializationException * If an error occurs. */ private EntryReader getLDIFReader(final File configFile, final Schema schema) throws InitializationException { LDIFEntryReader reader = null; try { reader = new LDIFEntryReader(new FileReader(configFile)); reader.setSchema(schema); } catch (Exception e) { throw new InitializationException( ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(configFile.getAbsolutePath(), e), e); } return reader; } /** * Returns the entry listeners attached to the provided DN. * <p> * If no listener exist for the provided DN, then a new set of empty listeners * is created and returned. * * @param dn * DN of a configuration entry. * @return the listeners attached to the corresponding configuration entry. */ private EntryListeners getEntryListeners(final DN dn) { EntryListeners entryListeners = listeners.get(dn); if (entryListeners == null) { entryListeners = new EntryListeners(); final EntryListeners previousListeners = listeners.putIfAbsent(dn, entryListeners); if (previousListeners != null) { entryListeners = previousListeners; } } return entryListeners; } /** * Returns the parent DN of the configuration entry corresponding to the * provided DN. * * @param entryDN * DN of entry to retrieve the parent from. * @return the parent DN * @throws DirectoryException * If entry has no parent or parent entry does not exist. */ private DN retrieveParentDN(final DN entryDN) throws DirectoryException { final DN parentDN = entryDN.parent(); // Entry must have a parent. if (parentDN == null) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_ADD_NO_PARENT_DN.get(entryDN)); } // Parent entry must exist. if (!backend.contains(parentDN)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_ADD_NO_PARENT.get(entryDN, parentDN), Converters.to(getMatchedDN(parentDN)), null); } return parentDN; } /** * Returns the matched DN that is available in the configuration for the * provided DN. */ private DN getMatchedDN(final DN dn) { DN matchedDN = null; DN parentDN = dn.parent(); while (parentDN != null) { if (backend.contains(parentDN)) { matchedDN = parentDN; break; } parentDN = parentDN.parent(); } return matchedDN; } /** * Find the actual configuration file to use to load configuration, given the * standard config file. * * @param standardConfigFile * "Standard" configuration file provided. * @return the actual configuration file to use, which is either the standard * config file provided or the config file corresponding to the last * known good configuration * @throws InitializationException * If a problem occurs. */ private File findConfigFileToUse(final File standardConfigFile) throws InitializationException { File configFileToUse = null; if (useLastKnownGoodConfig) { configFileToUse = new File(standardConfigFile + ".startok"); if (! configFileToUse.exists()) { logger.warn(WARN_CONFIG_FILE_NO_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); useLastKnownGoodConfig = false; configFileToUse = standardConfigFile; } else { logger.info(NOTE_CONFIG_FILE_USING_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); } } else { configFileToUse = standardConfigFile; } try { if (! configFileToUse.exists()) { throw new InitializationException(ERR_CONFIG_FILE_DOES_NOT_EXIST.get(configFileToUse.getAbsolutePath())); } } catch (Exception e) { throw new InitializationException( ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get(configFileToUse.getAbsolutePath(), e)); } return configFileToUse; } /** * Examines the provided result and logs a message if appropriate. If the * result code is anything other than {@code SUCCESS}, then it will log an * error message. If the operation was successful but admin action is * required, then it will log a warning message. If no action is required but * messages were generated, then it will log an informational message. * * @param result * The config change result object that * @param entryDN * The DN of the entry that was added, deleted, or modified. * @param className * The name of the class for the object that generated the provided * result. * @param methodName * The name of the method that generated the provided result. */ private void handleConfigChangeResult(ConfigChangeResult result, DN entryDN, String className, String methodName) { if (result == null) { logger.error(ERR_CONFIG_CHANGE_NO_RESULT, className, methodName, entryDN); return; } final ResultCode resultCode = result.getResultCode(); final boolean adminActionRequired = result.adminActionRequired(); final List<LocalizableMessage> messages = result.getMessages(); final String messageBuffer = Utils.joinAsString(" ", messages); if (resultCode != ResultCode.SUCCESS) { logger.error(ERR_CONFIG_CHANGE_RESULT_ERROR, className, methodName, entryDN, resultCode, adminActionRequired, messageBuffer); } else if (adminActionRequired) { logger.warn(WARN_CONFIG_CHANGE_RESULT_ACTION_REQUIRED, className, methodName, entryDN, messageBuffer); } else if (messageBuffer.length() > 0) { logger.debug(INFO_CONFIG_CHANGE_RESULT_MESSAGES, className, methodName, entryDN, messageBuffer); } } }