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.Collection; 034import java.util.List; 035import java.util.concurrent.ConcurrentHashMap; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.config.server.ConfigException; 040import org.forgerock.opendj.ldap.schema.MatchingRule; 041import org.forgerock.util.Utils; 042import org.opends.server.admin.ClassPropertyDefinition; 043import org.opends.server.admin.server.ConfigurationAddListener; 044import org.opends.server.admin.server.ConfigurationChangeListener; 045import org.opends.server.admin.server.ConfigurationDeleteListener; 046import org.opends.server.admin.server.ServerManagementContext; 047import org.opends.server.admin.std.meta.MatchingRuleCfgDefn; 048import org.opends.server.admin.std.server.MatchingRuleCfg; 049import org.opends.server.admin.std.server.RootCfg; 050import org.opends.server.api.MatchingRuleFactory; 051import org.opends.server.types.AttributeType; 052import org.forgerock.opendj.config.server.ConfigChangeResult; 053import org.opends.server.types.DN; 054import org.opends.server.types.DirectoryException; 055import org.opends.server.types.InitializationException; 056import org.opends.server.types.MatchingRuleUse; 057 058/** 059 * This class defines a utility that will be used to manage the set of matching 060 * rules defined in the Directory Server. It wil initialize the rules when the 061 * server starts, and then will manage any additions, removals, or modifications 062 * to any matching rules while the server is running. 063 */ 064public class MatchingRuleConfigManager 065 implements ConfigurationChangeListener<MatchingRuleCfg>, 066 ConfigurationAddListener<MatchingRuleCfg>, 067 ConfigurationDeleteListener<MatchingRuleCfg> 068 069{ 070 071 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 072 073 /** 074 * A mapping between the DNs of the config entries and the associated matching 075 * rule Factories. 076 */ 077 private ConcurrentHashMap<DN,MatchingRuleFactory> matchingRuleFactories; 078 079 /** Creates a new instance of this matching rule config manager. */ 080 public MatchingRuleConfigManager() 081 { 082 matchingRuleFactories = new ConcurrentHashMap<>(); 083 } 084 085 086 087 /** 088 * Initializes all matching rules after reading all the Matching Rule 089 * factories currently defined in the Directory Server configuration. 090 * This should only be called at Directory Server startup. 091 * 092 * @throws ConfigException If a configuration problem causes the matching 093 * rule initialization process to fail. 094 * 095 * @throws InitializationException If a problem occurs while initializing 096 * the matching rules that is not related to 097 * the server configuration. 098 */ 099 public void initializeMatchingRules() 100 throws ConfigException, InitializationException 101 { 102 // Get the root configuration object. 103 ServerManagementContext managementContext = 104 ServerManagementContext.getInstance(); 105 RootCfg rootConfiguration = 106 managementContext.getRootConfiguration(); 107 108 109 // Register as an add and delete listener with the root configuration so we 110 // can be notified if any matching rule entries are added or removed. 111 rootConfiguration.addMatchingRuleAddListener(this); 112 rootConfiguration.addMatchingRuleDeleteListener(this); 113 114 115 //Initialize the existing matching rules. 116 for (String name : rootConfiguration.listMatchingRules()) 117 { 118 MatchingRuleCfg mrConfiguration = rootConfiguration.getMatchingRule(name); 119 mrConfiguration.addChangeListener(this); 120 121 if (mrConfiguration.isEnabled()) 122 { 123 String className = mrConfiguration.getJavaClass(); 124 try 125 { 126 MatchingRuleFactory<?> factory = 127 loadMatchingRuleFactory(className, mrConfiguration, true); 128 129 try 130 { 131 for(MatchingRule matchingRule: factory.getMatchingRules()) 132 { 133 DirectoryServer.registerMatchingRule(matchingRule, false); 134 } 135 matchingRuleFactories.put(mrConfiguration.dn(), factory); 136 } 137 catch (DirectoryException de) 138 { 139 logger.warn(WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR, mrConfiguration.dn(), de.getMessageObject()); 140 continue; 141 } 142 } 143 catch (InitializationException ie) 144 { 145 logger.error(ie.getMessageObject()); 146 continue; 147 } 148 } 149 } 150 } 151 152 153 154 /** {@inheritDoc} */ 155 @Override 156 public boolean isConfigurationAddAcceptable(MatchingRuleCfg configuration, 157 List<LocalizableMessage> unacceptableReasons) 158 { 159 if (configuration.isEnabled()) 160 { 161 // Get the name of the class and make sure we can instantiate it as a 162 // matching rule Factory. 163 String className = configuration.getJavaClass(); 164 try 165 { 166 loadMatchingRuleFactory(className, configuration, false); 167 } 168 catch (InitializationException ie) 169 { 170 unacceptableReasons.add(ie.getMessageObject()); 171 return false; 172 } 173 } 174 175 // If we've gotten here, then it's fine. 176 return true; 177 } 178 179 180 181 /** {@inheritDoc} */ 182 @Override 183 public ConfigChangeResult applyConfigurationAdd(MatchingRuleCfg configuration) 184 { 185 final ConfigChangeResult ccr = new ConfigChangeResult(); 186 187 configuration.addChangeListener(this); 188 189 if (! configuration.isEnabled()) 190 { 191 return ccr; 192 } 193 194 MatchingRuleFactory<?> factory = null; 195 196 // Get the name of the class and make sure we can instantiate it as a 197 // matching rule Factory. 198 String className = configuration.getJavaClass(); 199 try 200 { 201 factory = loadMatchingRuleFactory(className, configuration, true); 202 203 for (MatchingRule matchingRule: factory.getMatchingRules()) 204 { 205 DirectoryServer.registerMatchingRule(matchingRule, false); 206 } 207 matchingRuleFactories.put(configuration.dn(),factory); 208 } 209 catch (DirectoryException de) 210 { 211 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 212 ccr.addMessage(WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get( 213 configuration.dn(), de.getMessageObject())); 214 } 215 catch (InitializationException ie) 216 { 217 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 218 ccr.addMessage(ie.getMessageObject()); 219 } 220 221 return ccr; 222 } 223 224 225 226 /** {@inheritDoc} */ 227 @Override 228 public boolean isConfigurationDeleteAcceptable(MatchingRuleCfg configuration, 229 List<LocalizableMessage> unacceptableReasons) 230 { 231 // If the matching rule is enabled, then check to see if there are any 232 // defined attribute types or matching rule uses that use the matching rule. 233 // If so, then don't allow it to be deleted. 234 boolean configAcceptable = true; 235 MatchingRuleFactory<?> factory = matchingRuleFactories.get(configuration.dn()); 236 for(MatchingRule matchingRule: factory.getMatchingRules()) 237 { 238 if (matchingRule != null) 239 { 240 for (AttributeType at : DirectoryServer.getAttributeTypes().values()) 241 { 242 final String attr = at.getNameOrOID(); 243 if (!isDeleteAcceptable(at.getApproximateMatchingRule(), matchingRule, attr, unacceptableReasons) 244 || !isDeleteAcceptable(at.getEqualityMatchingRule(), matchingRule, attr, unacceptableReasons) 245 || !isDeleteAcceptable(at.getOrderingMatchingRule(), matchingRule, attr, unacceptableReasons) 246 || !isDeleteAcceptable(at.getSubstringMatchingRule(), matchingRule, attr, unacceptableReasons)) 247 { 248 configAcceptable = false; 249 continue; 250 } 251 } 252 253 final String oid = matchingRule.getOID(); 254 for (MatchingRuleUse mru : 255 DirectoryServer.getMatchingRuleUses().values()) 256 { 257 if (oid.equals(mru.getMatchingRule().getOID())) 258 { 259 LocalizableMessage message = 260 WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_MRU.get( 261 matchingRule.getNameOrOID(), mru.getNameOrOID()); 262 unacceptableReasons.add(message); 263 264 configAcceptable = false; 265 continue; 266 } 267 } 268 } 269 } 270 271 return configAcceptable; 272 } 273 274 private boolean isDeleteAcceptable(MatchingRule mr, MatchingRule matchingRule, String attr, 275 List<LocalizableMessage> unacceptableReasons) 276 { 277 if (mr != null && matchingRule.getOID().equals(mr.getOID())) 278 { 279 unacceptableReasons.add(WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_AT.get(matchingRule.getNameOrOID(), attr)); 280 return false; 281 } 282 return true; 283 } 284 285 286 /** {@inheritDoc} */ 287 @Override 288 public ConfigChangeResult applyConfigurationDelete(MatchingRuleCfg configuration) 289 { 290 final ConfigChangeResult ccr = new ConfigChangeResult(); 291 292 MatchingRuleFactory<?> factory = matchingRuleFactories.remove(configuration.dn()); 293 if (factory != null) 294 { 295 for(MatchingRule matchingRule: factory.getMatchingRules()) 296 { 297 DirectoryServer.deregisterMatchingRule(matchingRule); 298 } 299 factory.finalizeMatchingRule(); 300 } 301 302 return ccr; 303 } 304 305 306 307 /** {@inheritDoc} */ 308 @Override 309 public boolean isConfigurationChangeAcceptable(MatchingRuleCfg configuration, 310 List<LocalizableMessage> unacceptableReasons) 311 { 312 boolean configAcceptable = true; 313 if (configuration.isEnabled()) 314 { 315 // Get the name of the class and make sure we can instantiate it as a 316 // matching rule Factory. 317 String className = configuration.getJavaClass(); 318 try 319 { 320 loadMatchingRuleFactory(className, configuration, false); 321 } 322 catch (InitializationException ie) 323 { 324 unacceptableReasons.add(ie.getMessageObject()); 325 configAcceptable = false; 326 } 327 } 328 else 329 { 330 // If the matching rule is currently enabled and the change would make it 331 // disabled, then only allow it if the matching rule isn't already in use. 332 MatchingRuleFactory<?> factory = matchingRuleFactories.get(configuration.dn()); 333 if(factory == null) 334 { 335 //Factory was disabled again. 336 return configAcceptable; 337 } 338 for(MatchingRule matchingRule: factory.getMatchingRules()) 339 { 340 if (matchingRule != null) 341 { 342 for (AttributeType at : DirectoryServer.getAttributeTypes().values()) 343 { 344 final String attr = at.getNameOrOID(); 345 if (!isDisableAcceptable(at.getApproximateMatchingRule(), matchingRule, attr, unacceptableReasons) 346 || !isDisableAcceptable(at.getEqualityMatchingRule(), matchingRule, attr, unacceptableReasons) 347 || !isDisableAcceptable(at.getOrderingMatchingRule(), matchingRule, attr, unacceptableReasons) 348 || !isDisableAcceptable(at.getSubstringMatchingRule(), matchingRule, attr, unacceptableReasons)) 349 { 350 configAcceptable = false; 351 continue; 352 } 353 } 354 355 final String oid = matchingRule.getOID(); 356 for (MatchingRuleUse mru : 357 DirectoryServer.getMatchingRuleUses().values()) 358 { 359 if (oid.equals(mru.getMatchingRule().getOID())) 360 { 361 LocalizableMessage message = 362 WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_MRU.get( 363 matchingRule.getNameOrOID(), mru.getNameOrOID()); 364 unacceptableReasons.add(message); 365 366 configAcceptable = false; 367 continue; 368 } 369 } 370 } 371 } 372 } 373 return configAcceptable; 374 } 375 376 private boolean isDisableAcceptable(MatchingRule mr, MatchingRule matchingRule, 377 String attrNameOrOID, Collection<LocalizableMessage> unacceptableReasons) 378 { 379 if (mr != null && matchingRule.getOID().equals(mr.getOID())) 380 { 381 unacceptableReasons.add( 382 WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_AT.get(matchingRule.getNameOrOID(), attrNameOrOID)); 383 return false; 384 } 385 return true; 386 } 387 388 /** {@inheritDoc} */ 389 @Override 390 public ConfigChangeResult applyConfigurationChange( 391 MatchingRuleCfg configuration) 392 { 393 final ConfigChangeResult ccr = new ConfigChangeResult(); 394 395 396 // Get the existing matching rule factory if it's already enabled. 397 MatchingRuleFactory<?> existingFactory = 398 matchingRuleFactories.get(configuration.dn()); 399 400 401 // If the new configuration has the matching rule disabled, then disable it 402 // if it is enabled, or do nothing if it's already disabled. 403 if (! configuration.isEnabled()) 404 { 405 if (existingFactory != null) 406 { 407 for(MatchingRule existingRule: existingFactory.getMatchingRules()) 408 { 409 DirectoryServer.deregisterMatchingRule(existingRule); 410 } 411 matchingRuleFactories.remove(configuration.dn()); 412 existingFactory.finalizeMatchingRule(); 413 } 414 return ccr; 415 } 416 417 418 // Get the class for the matching rule. If the matching rule is already 419 // enabled, then we shouldn't do anything with it although if the class has 420 // changed then we'll at least need to indicate that administrative action 421 // is required. If the matching rule is disabled, then instantiate the 422 // class and initialize and register it as a matching rule. 423 String className = configuration.getJavaClass(); 424 if (existingFactory != null) 425 { 426 if (! className.equals(existingFactory.getClass().getName())) 427 { 428 ccr.setAdminActionRequired(true); 429 } 430 431 return ccr; 432 } 433 434 MatchingRuleFactory<?> factory = null; 435 try 436 { 437 factory = loadMatchingRuleFactory(className, configuration, true); 438 439 for (MatchingRule matchingRule: factory.getMatchingRules()) 440 { 441 DirectoryServer.registerMatchingRule(matchingRule, false); 442 } 443 matchingRuleFactories.put(configuration.dn(), factory); 444 } 445 catch (DirectoryException de) 446 { 447 ccr.addMessage(WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(configuration.dn(), de.getMessageObject())); 448 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 449 } 450 catch (InitializationException ie) 451 { 452 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 453 ccr.addMessage(ie.getMessageObject()); 454 } 455 456 return ccr; 457 } 458 459 460 461 /** 462 * Loads the specified class, instantiates it as an attribute syntax, and 463 * optionally initializes that instance. 464 * 465 * @param className The fully-qualified name of the attribute syntax 466 * class to load, instantiate, and initialize. 467 * @param configuration The configuration to use to initialize the attribute 468 * syntax. It must not be {@code null}. 469 * @param initialize Indicates whether the matching rule instance should 470 * be initialized. 471 * 472 * @return The possibly initialized attribute syntax. 473 * 474 * @throws InitializationException If a problem occurred while attempting to 475 * initialize the attribute syntax. 476 */ 477 private MatchingRuleFactory loadMatchingRuleFactory(String className, 478 MatchingRuleCfg configuration, 479 boolean initialize) 480 throws InitializationException 481 { 482 try 483 { 484 MatchingRuleFactory factory = null; 485 MatchingRuleCfgDefn definition = MatchingRuleCfgDefn.getInstance(); 486 ClassPropertyDefinition propertyDefinition = definition.getJavaClassPropertyDefinition(); 487 Class<? extends MatchingRuleFactory> matchingRuleFactoryClass = 488 propertyDefinition.loadClass(className, 489 MatchingRuleFactory.class); 490 factory = matchingRuleFactoryClass.newInstance(); 491 492 if (initialize) 493 { 494 factory.initializeMatchingRule(configuration); 495 } 496 else 497 { 498 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 499 if (!factory.isConfigurationAcceptable(configuration, unacceptableReasons)) 500 { 501 String reasons = Utils.joinAsString(". ", unacceptableReasons); 502 throw new InitializationException( 503 ERR_CONFIG_SCHEMA_MR_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons)); 504 } 505 } 506 507 return factory; 508 } 509 catch (Exception e) 510 { 511 LocalizableMessage message = ERR_CONFIG_SCHEMA_MR_CANNOT_INITIALIZE. 512 get(className, configuration.dn(), stackTraceToSingleLineString(e)); 513 throw new InitializationException(message, e); 514 } 515 } 516}