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}