001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import static org.opends.messages.ConfigMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.util.ArrayList; 033import java.util.List; 034import java.util.concurrent.ConcurrentHashMap; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.opendj.config.server.ConfigException; 038import org.forgerock.util.Utils; 039import org.opends.server.admin.ClassPropertyDefinition; 040import org.opends.server.admin.server.ConfigurationAddListener; 041import org.opends.server.admin.server.ConfigurationChangeListener; 042import org.opends.server.admin.server.ConfigurationDeleteListener; 043import org.opends.server.admin.server.ServerManagementContext; 044import org.opends.server.admin.std.meta.AccountStatusNotificationHandlerCfgDefn; 045import org.opends.server.admin.std.server.AccountStatusNotificationHandlerCfg; 046import org.opends.server.admin.std.server.RootCfg; 047import org.opends.server.api.AccountStatusNotificationHandler; 048import org.forgerock.opendj.config.server.ConfigChangeResult; 049import org.opends.server.types.DN; 050import org.opends.server.types.InitializationException; 051 052/** 053 * This class defines a utility that will be used to manage the set of account 054 * status notification handlers defined in the Directory Server. It will 055 * initialize the handlers when the server starts, and then will manage any 056 * additions, removals, or modifications to any notification handlers while the 057 * server is running. 058 */ 059public class AccountStatusNotificationHandlerConfigManager 060 implements 061 ConfigurationChangeListener <AccountStatusNotificationHandlerCfg>, 062 ConfigurationAddListener <AccountStatusNotificationHandlerCfg>, 063 ConfigurationDeleteListener <AccountStatusNotificationHandlerCfg> 064{ 065 066 /** 067 * A mapping between the DNs of the config entries and the associated 068 * notification handlers. 069 */ 070 private final ConcurrentHashMap<DN,AccountStatusNotificationHandler> notificationHandlers; 071 072 private final ServerContext serverContext; 073 074 /** 075 * Creates a new instance of this account status notification handler config 076 * manager. 077 * @param serverContext 078 * The server context. 079 */ 080 public AccountStatusNotificationHandlerConfigManager(ServerContext serverContext) 081 { 082 this.serverContext = serverContext; 083 notificationHandlers = new ConcurrentHashMap<>(); 084 } 085 086 087 088 /** 089 * Initializes all account status notification handlers currently defined in 090 * the Directory Server configuration. This should only be called at 091 * Directory Server startup. 092 * 093 * @throws ConfigException If a configuration problem causes the 094 * notification handler initialization process to 095 * fail. 096 * 097 * @throws InitializationException If a problem occurs while initializing 098 * the account status notification handlers 099 * that is not related to the server 100 * configuration. 101 */ 102 public void initializeNotificationHandlers() 103 throws ConfigException, InitializationException 104 { 105 // Get the root configuration object. 106 ServerManagementContext managementContext = 107 ServerManagementContext.getInstance(); 108 RootCfg rootConfiguration = 109 managementContext.getRootConfiguration(); 110 111 // Register as an add and delete listener with the root configuration so 112 // we can be notified if any account status notification handler entry 113 // is added or removed. 114 rootConfiguration.addAccountStatusNotificationHandlerAddListener (this); 115 rootConfiguration.addAccountStatusNotificationHandlerDeleteListener (this); 116 117 // Initialize existing account status notification handlers. 118 for (String handlerName: 119 rootConfiguration.listAccountStatusNotificationHandlers()) 120 { 121 // Get the account status notification handler's configuration. 122 AccountStatusNotificationHandlerCfg config = 123 rootConfiguration.getAccountStatusNotificationHandler (handlerName); 124 125 // Register as a change listener for this notification handler 126 // entry so that we will be notified of any changes that may be 127 // made to it. 128 config.addChangeListener (this); 129 130 // Ignore this notification handler if it is disabled. 131 if (config.isEnabled()) 132 { 133 // Load the notification handler implementation class. 134 String className = config.getJavaClass(); 135 loadAndInstallNotificationHandler (className, config); 136 } 137 } 138 } 139 140 141 142 /** {@inheritDoc} */ 143 @Override 144 public boolean isConfigurationChangeAcceptable( 145 AccountStatusNotificationHandlerCfg configuration, 146 List<LocalizableMessage> unacceptableReasons 147 ) 148 { 149 // returned status -- all is fine by default 150 boolean status = true; 151 152 if (configuration.isEnabled()) 153 { 154 // Get the name of the class and make sure we can instantiate it as an 155 // entry cache. 156 String className = configuration.getJavaClass(); 157 try 158 { 159 // Load the class but don't initialize it. 160 loadNotificationHandler(className, configuration, true); 161 } 162 catch (InitializationException ie) 163 { 164 unacceptableReasons.add(ie.getMessageObject()); 165 status = false; 166 } 167 } 168 169 return status; 170 } 171 172 173 174 /** {@inheritDoc} */ 175 @Override 176 public ConfigChangeResult applyConfigurationChange( 177 AccountStatusNotificationHandlerCfg configuration 178 ) 179 { 180 final ConfigChangeResult changeResult = new ConfigChangeResult(); 181 182 // Get the configuration entry DN and the associated handler class. 183 DN configEntryDN = configuration.dn(); 184 AccountStatusNotificationHandler handler = notificationHandlers.get(configEntryDN); 185 186 // If the new configuration has the notification handler disabled, 187 // then remove it from the mapping list and clean it. 188 if (! configuration.isEnabled()) 189 { 190 if (handler != null) 191 { 192 uninstallNotificationHandler (configEntryDN); 193 } 194 195 return changeResult; 196 } 197 198 // At this point, new configuration is enabled... 199 // If the current notification handler is already enabled then we 200 // don't do anything unless the class has changed in which case we 201 // should indicate that administrative action is required. 202 String newClassName = configuration.getJavaClass(); 203 if (handler != null) 204 { 205 String curClassName = handler.getClass().getName(); 206 boolean classIsNew = !newClassName.equals(curClassName); 207 if (classIsNew) 208 { 209 changeResult.setAdminActionRequired (true); 210 } 211 return changeResult; 212 } 213 214 // New entry cache is enabled and there were no previous one. 215 // Instantiate the new class and initialize it. 216 try 217 { 218 loadAndInstallNotificationHandler (newClassName, configuration); 219 } 220 catch (InitializationException ie) 221 { 222 changeResult.addMessage (ie.getMessageObject()); 223 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); 224 return changeResult; 225 } 226 227 return changeResult; 228 } 229 230 231 232 /** {@inheritDoc} */ 233 @Override 234 public boolean isConfigurationAddAcceptable( 235 AccountStatusNotificationHandlerCfg configuration, 236 List<LocalizableMessage> unacceptableReasons 237 ) 238 { 239 // returned status -- all is fine by default 240 boolean status = true; 241 242 // Make sure that no entry already exists with the specified DN. 243 DN configEntryDN = configuration.dn(); 244 if (notificationHandlers.containsKey(configEntryDN)) 245 { 246 unacceptableReasons.add(ERR_CONFIG_ACCTNOTHANDLER_EXISTS.get(configEntryDN)); 247 status = false; 248 } 249 // If configuration is enabled then check that notification class 250 // can be instantiated. 251 else if (configuration.isEnabled()) 252 { 253 // Get the name of the class and make sure we can instantiate it as 254 // an entry cache. 255 String className = configuration.getJavaClass(); 256 try 257 { 258 // Load the class but don't initialize it. 259 loadNotificationHandler (className, configuration, false); 260 } 261 catch (InitializationException ie) 262 { 263 unacceptableReasons.add (ie.getMessageObject()); 264 status = false; 265 } 266 } 267 268 return status; 269 } 270 271 272 273 /** {@inheritDoc} */ 274 @Override 275 public ConfigChangeResult applyConfigurationAdd( 276 AccountStatusNotificationHandlerCfg configuration 277 ) 278 { 279 final ConfigChangeResult changeResult = new ConfigChangeResult(); 280 281 // Register a change listener with it so we can be notified of changes 282 // to it over time. 283 configuration.addChangeListener(this); 284 285 if (configuration.isEnabled()) 286 { 287 // Instantiate the class as an entry cache and initialize it. 288 String className = configuration.getJavaClass(); 289 try 290 { 291 loadAndInstallNotificationHandler (className, configuration); 292 } 293 catch (InitializationException ie) 294 { 295 changeResult.addMessage (ie.getMessageObject()); 296 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); 297 return changeResult; 298 } 299 } 300 301 return changeResult; 302 } 303 304 305 306 /** {@inheritDoc} */ 307 @Override 308 public boolean isConfigurationDeleteAcceptable( 309 AccountStatusNotificationHandlerCfg configuration, 310 List<LocalizableMessage> unacceptableReasons 311 ) 312 { 313 // A delete should always be acceptable, so just return true. 314 return true; 315 } 316 317 318 319 /** {@inheritDoc} */ 320 @Override 321 public ConfigChangeResult applyConfigurationDelete( 322 AccountStatusNotificationHandlerCfg configuration 323 ) 324 { 325 uninstallNotificationHandler (configuration.dn()); 326 return new ConfigChangeResult(); 327 } 328 329 330 /** 331 * Loads the specified class, instantiates it as a notification handler, 332 * and optionally initializes that instance. Any initialized notification 333 * handler is registered in the server. 334 * 335 * @param className The fully-qualified name of the notification handler 336 * class to load, instantiate, and initialize. 337 * @param configuration The configuration to use to initialize the 338 * notification handler, or {@code null} if the 339 * notification handler should not be initialized. 340 * 341 * @throws InitializationException If a problem occurred while attempting 342 * to initialize the notification handler. 343 */ 344 private void loadAndInstallNotificationHandler( 345 String className, 346 AccountStatusNotificationHandlerCfg configuration 347 ) 348 throws InitializationException 349 { 350 // Load the notification handler class... 351 AccountStatusNotificationHandler 352 <? extends AccountStatusNotificationHandlerCfg> handlerClass; 353 handlerClass = loadNotificationHandler (className, configuration, true); 354 355 // ... and install the entry cache in the server. 356 DN configEntryDN = configuration.dn(); 357 notificationHandlers.put (configEntryDN, handlerClass); 358 DirectoryServer.registerAccountStatusNotificationHandler( 359 configEntryDN, 360 handlerClass 361 ); 362 } 363 364 365 /** 366 * Loads the specified class, instantiates it as a notification handler, 367 * and optionally initializes that instance. 368 * 369 * @param className The fully-qualified name of the notification handler 370 * class to load, instantiate, and initialize. 371 * @param configuration The configuration to use to initialize the 372 * notification handler. It must not be {@code null}. 373 * @param initialize Indicates whether the account status notification 374 * handler instance should be initialized. 375 * 376 * @return The possibly initialized notification handler. 377 * 378 * @throws InitializationException If a problem occurred while attempting 379 * to initialize the notification handler. 380 */ 381 private <T extends AccountStatusNotificationHandlerCfg> 382 AccountStatusNotificationHandler<T> loadNotificationHandler( 383 String className, T configuration, boolean initialize) 384 throws InitializationException 385 { 386 try 387 { 388 final AccountStatusNotificationHandlerCfgDefn definition = 389 AccountStatusNotificationHandlerCfgDefn.getInstance(); 390 final ClassPropertyDefinition propertyDefinition = 391 definition.getJavaClassPropertyDefinition(); 392 final Class<? extends AccountStatusNotificationHandler> handlerClass = 393 propertyDefinition.loadClass(className, 394 AccountStatusNotificationHandler.class); 395 final AccountStatusNotificationHandler<T> notificationHandler = 396 handlerClass.newInstance(); 397 398 if (initialize) 399 { 400 notificationHandler.initializeStatusNotificationHandler(configuration); 401 } 402 else 403 { 404 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 405 if (!notificationHandler.isConfigurationAcceptable(configuration, 406 unacceptableReasons)) 407 { 408 String reasons = Utils.joinAsString(". ", unacceptableReasons); 409 throw new InitializationException( 410 ERR_CONFIG_ACCTNOTHANDLER_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons)); 411 } 412 } 413 414 return notificationHandler; 415 } 416 catch (Exception e) 417 { 418 LocalizableMessage message = ERR_CONFIG_ACCTNOTHANDLER_INITIALIZATION_FAILED.get( 419 className, configuration.dn(), stackTraceToSingleLineString(e)); 420 throw new InitializationException(message, e); 421 } 422 } 423 424 425 /** 426 * Remove a notification handler that has been installed in the server. 427 * 428 * @param configEntryDN the DN of the configuration entry associated to 429 * the notification handler to remove 430 */ 431 private void uninstallNotificationHandler( 432 DN configEntryDN 433 ) 434 { 435 AccountStatusNotificationHandler handler = 436 notificationHandlers.remove (configEntryDN); 437 if (handler != null) 438 { 439 DirectoryServer.deregisterAccountStatusNotificationHandler ( 440 configEntryDN 441 ); 442 handler.finalizeStatusNotificationHandler(); 443 } 444 } 445} 446