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