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