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.core; 018 019import static org.opends.messages.ConfigMessages.*; 020import static org.opends.server.util.StaticUtils.*; 021 022import java.util.List; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.forgerock.i18n.LocalizableMessage; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.config.server.ConfigException; 028import org.forgerock.opendj.config.ClassPropertyDefinition; 029import org.forgerock.opendj.config.server.ConfigurationAddListener; 030import org.forgerock.opendj.config.server.ConfigurationChangeListener; 031import org.forgerock.opendj.config.server.ConfigurationDeleteListener; 032import org.forgerock.opendj.server.config.meta.ExtendedOperationHandlerCfgDefn; 033import org.forgerock.opendj.server.config.server.ExtendedOperationHandlerCfg; 034import org.forgerock.opendj.server.config.server.RootCfg; 035import org.opends.server.api.ExtendedOperationHandler; 036import org.forgerock.opendj.config.server.ConfigChangeResult; 037import org.forgerock.opendj.ldap.DN; 038import org.opends.server.types.InitializationException; 039 040/** 041 * This class defines a utility that will be used to manage the set of extended 042 * operation handlers defined in the Directory Server. It will initialize the 043 * handlers when the server starts, and then will manage any additions, 044 * removals, or modifications of any extended operation handlers while the 045 * server is running. 046 */ 047public class ExtendedOperationConfigManager implements 048 ConfigurationChangeListener<ExtendedOperationHandlerCfg>, 049 ConfigurationAddListener<ExtendedOperationHandlerCfg>, 050 ConfigurationDeleteListener<ExtendedOperationHandlerCfg> 051{ 052 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 053 054 /** 055 * A mapping between the DNs of the config entries and the associated extended 056 * operation handlers. 057 */ 058 private final ConcurrentHashMap<DN,ExtendedOperationHandler> handlers; 059 060 private final ServerContext serverContext; 061 062 /** 063 * Creates a new instance of this extended operation config manager. 064 * 065 * @param serverContext 066 * The server context. 067 */ 068 public ExtendedOperationConfigManager(ServerContext serverContext) 069 { 070 this.serverContext = serverContext; 071 handlers = new ConcurrentHashMap<>(); 072 } 073 074 /** 075 * Initializes all extended operation handlers currently defined in the 076 * Directory Server configuration. This should only be called at Directory 077 * Server startup. 078 * 079 * @throws ConfigException If a configuration problem causes the extended 080 * operation handler initialization process to fail. 081 * 082 * @throws InitializationException If a problem occurs while initializing 083 * the extended operation handler that is 084 * not related to the server configuration. 085 */ 086 public void initializeExtendedOperationHandlers() 087 throws ConfigException, InitializationException 088 { 089 RootCfg root = serverContext.getRootConfig(); 090 root.addExtendedOperationHandlerAddListener(this); 091 root.addExtendedOperationHandlerDeleteListener(this); 092 093 // Initialize existing handlers. 094 for (String name : root.listExtendedOperationHandlers()) 095 { 096 // Get the handler's configuration. 097 // This will decode and validate its properties. 098 ExtendedOperationHandlerCfg config = 099 root.getExtendedOperationHandler(name); 100 101 // Register as a change listener for this handler so that we can be 102 // notified when it is disabled or enabled. 103 config.addChangeListener(this); 104 105 // Ignore this handler if it is disabled. 106 if (config.isEnabled()) 107 { 108 // Load the handler's implementation class and initialize it. 109 ExtendedOperationHandler handler = getHandler(config); 110 111 // Put this handler in the hash map so that we will be able to find 112 // it if it is deleted or disabled. 113 handlers.put(config.dn(), handler); 114 } 115 } 116 } 117 118 @Override 119 public ConfigChangeResult applyConfigurationDelete( 120 ExtendedOperationHandlerCfg configuration) 121 { 122 final ConfigChangeResult ccr = new ConfigChangeResult(); 123 // See if the entry is registered as an extended operation handler. 124 // If so, deregister it and finalize the handler. 125 ExtendedOperationHandler handler = handlers.remove(configuration.dn()); 126 if (handler != null) 127 { 128 handler.finalizeExtendedOperationHandler(); 129 } 130 return ccr; 131 } 132 133 @Override 134 public boolean isConfigurationChangeAcceptable( 135 ExtendedOperationHandlerCfg configuration, 136 List<LocalizableMessage> unacceptableReasons) 137 { 138 return !configuration.isEnabled() 139 || isJavaClassAcceptable(configuration, unacceptableReasons); 140 } 141 142 @Override 143 public ConfigChangeResult applyConfigurationChange( 144 ExtendedOperationHandlerCfg configuration) 145 { 146 // Attempt to get the existing handler. This will only 147 // succeed if it was enabled. 148 DN dn = configuration.dn(); 149 ExtendedOperationHandler handler = handlers.get(dn); 150 151 final ConfigChangeResult ccr = new ConfigChangeResult(); 152 153 // See whether the handler should be enabled. 154 if (handler == null) { 155 if (configuration.isEnabled()) { 156 // The handler needs to be enabled. 157 try { 158 handler = getHandler(configuration); 159 160 // Put this handler in the hash so that we will 161 // be able to find it if it is altered. 162 handlers.put(dn, handler); 163 } catch (ConfigException e) { 164 logger.traceException(e); 165 166 ccr.addMessage(e.getMessageObject()); 167 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 168 } catch (Exception e) { 169 logger.traceException(e); 170 171 ccr.addMessage(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get( 172 configuration.getJavaClass(), dn, stackTraceToSingleLineString(e))); 173 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 174 } 175 } 176 } else { 177 if (configuration.isEnabled()) { 178 // The handler is currently active, so we don't 179 // need to do anything. Changes to the class name cannot be 180 // applied dynamically, so if the class name did change then 181 // indicate that administrative action is required for that 182 // change to take effect. 183 String className = configuration.getJavaClass(); 184 if (!className.equals(handler.getClass().getName())) { 185 ccr.setAdminActionRequired(true); 186 } 187 } else { 188 // We need to disable the connection handler. 189 190 handlers.remove(dn); 191 192 handler.finalizeExtendedOperationHandler(); 193 } 194 } 195 196 return ccr; 197 } 198 199 @Override 200 public boolean isConfigurationAddAcceptable( 201 ExtendedOperationHandlerCfg configuration, 202 List<LocalizableMessage> unacceptableReasons) 203 { 204 return isConfigurationChangeAcceptable(configuration, unacceptableReasons); 205 } 206 207 @Override 208 public ConfigChangeResult applyConfigurationAdd( 209 ExtendedOperationHandlerCfg configuration) 210 { 211 final ConfigChangeResult ccr = new ConfigChangeResult(); 212 213 // Register as a change listener for this connection handler entry 214 // so that we will be notified of any changes that may be made to 215 // it. 216 configuration.addChangeListener(this); 217 218 // Ignore this connection handler if it is disabled. 219 if (configuration.isEnabled()) 220 { 221 // The connection handler needs to be enabled. 222 DN dn = configuration.dn(); 223 try { 224 ExtendedOperationHandler handler = getHandler(configuration); 225 226 // Put this connection handler in the hash so that we will be 227 // able to find it if it is altered. 228 handlers.put(dn, handler); 229 } 230 catch (ConfigException e) 231 { 232 logger.traceException(e); 233 234 ccr.addMessage(e.getMessageObject()); 235 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 236 } 237 catch (Exception e) 238 { 239 logger.traceException(e); 240 241 ccr.addMessage(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get( 242 configuration.getJavaClass(), dn, stackTraceToSingleLineString(e))); 243 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 244 } 245 } 246 247 return ccr; 248 } 249 250 @Override 251 public boolean isConfigurationDeleteAcceptable( 252 ExtendedOperationHandlerCfg configuration, 253 List<LocalizableMessage> unacceptableReasons) 254 { 255 // A delete should always be acceptable, so just return true. 256 return true; 257 } 258 259 /** Load and initialize the handler named in the config. */ 260 private ExtendedOperationHandler getHandler( 261 ExtendedOperationHandlerCfg config) throws ConfigException 262 { 263 String className = config.getJavaClass(); 264 ExtendedOperationHandlerCfgDefn d = 265 ExtendedOperationHandlerCfgDefn.getInstance(); 266 ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition(); 267 268 try 269 { 270 Class<? extends ExtendedOperationHandler> theClass = 271 pd.loadClass(className, ExtendedOperationHandler.class); 272 ExtendedOperationHandler extendedOperationHandler = theClass.newInstance(); 273 274 extendedOperationHandler.initializeExtendedOperationHandler(config); 275 276 return extendedOperationHandler; 277 } 278 catch (Exception e) 279 { 280 logger.traceException(e); 281 throw new ConfigException(ERR_CONFIG_EXTOP_INVALID_CLASS.get(className, config.dn(), e), e); 282 } 283 } 284 285 /** Determines whether the new configuration's implementation class is acceptable. */ 286 private boolean isJavaClassAcceptable(ExtendedOperationHandlerCfg config, 287 List<LocalizableMessage> unacceptableReasons) 288 { 289 String className = config.getJavaClass(); 290 ExtendedOperationHandlerCfgDefn d = 291 ExtendedOperationHandlerCfgDefn.getInstance(); 292 ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition(); 293 294 try { 295 Class<? extends ExtendedOperationHandler> theClass = 296 pd.loadClass(className, ExtendedOperationHandler.class); 297 ExtendedOperationHandler extOpHandler = theClass.newInstance(); 298 299 return extOpHandler.isConfigurationAcceptable(config, unacceptableReasons); 300 } 301 catch (Exception e) 302 { 303 logger.traceException(e); 304 unacceptableReasons.add(ERR_CONFIG_EXTOP_INVALID_CLASS.get(className, config.dn(), e)); 305 return false; 306 } 307 } 308}