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