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