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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.lang.reflect.Method;
033    import java.util.ArrayList;
034    import java.util.Iterator;
035    import java.util.List;
036    import java.util.concurrent.ConcurrentHashMap;
037    
038    import org.opends.server.admin.ClassPropertyDefinition;
039    import org.opends.server.admin.server.ConfigurationAddListener;
040    import org.opends.server.admin.server.ConfigurationChangeListener;
041    import org.opends.server.admin.server.ConfigurationDeleteListener;
042    import org.opends.server.admin.std.meta.ApproximateMatchingRuleCfgDefn;
043    import org.opends.server.admin.std.meta.EqualityMatchingRuleCfgDefn;
044    import org.opends.server.admin.std.meta.OrderingMatchingRuleCfgDefn;
045    import org.opends.server.admin.std.meta.SubstringMatchingRuleCfgDefn;
046    import org.opends.server.admin.std.server.ApproximateMatchingRuleCfg;
047    import org.opends.server.admin.std.server.EqualityMatchingRuleCfg;
048    import org.opends.server.admin.std.server.MatchingRuleCfg;
049    import org.opends.server.admin.std.server.OrderingMatchingRuleCfg;
050    import org.opends.server.admin.std.server.SubstringMatchingRuleCfg;
051    import org.opends.server.admin.std.server.RootCfg;
052    import org.opends.server.admin.server.ServerManagementContext;
053    import org.opends.server.api.ApproximateMatchingRule;
054    import org.opends.server.api.EqualityMatchingRule;
055    import org.opends.server.api.MatchingRule;
056    import org.opends.server.api.OrderingMatchingRule;
057    import org.opends.server.api.SubstringMatchingRule;
058    import org.opends.server.config.ConfigException;
059    import org.opends.server.types.AttributeType;
060    import org.opends.server.types.ConfigChangeResult;
061    import org.opends.server.types.DirectoryException;
062    import org.opends.server.types.DN;
063    
064    
065    import org.opends.server.types.InitializationException;
066    import org.opends.server.types.MatchingRuleUse;
067    import org.opends.server.types.ResultCode;
068    
069    import static org.opends.messages.ConfigMessages.*;
070    
071    import static org.opends.server.util.StaticUtils.*;
072    import org.opends.server.loggers.ErrorLogger;
073    
074    
075    /**
076     * This class defines a utility that will be used to manage the set of matching
077     * rules defined in the Directory Server.  It wil initialize the rules when the
078     * server starts, and then will manage any additions, removals, or modifications
079     * to any matching rules while the server is running.
080     */
081    public class MatchingRuleConfigManager
082           implements ConfigurationChangeListener<MatchingRuleCfg>,
083                      ConfigurationAddListener<MatchingRuleCfg>,
084                      ConfigurationDeleteListener<MatchingRuleCfg>
085    
086    {
087      // A mapping between the DNs of the config entries and the associated matching
088      // rules.
089      private ConcurrentHashMap<DN,MatchingRule> matchingRules;
090    
091    
092    
093      /**
094       * Creates a new instance of this matching rule config manager.
095       */
096      public MatchingRuleConfigManager()
097      {
098        matchingRules = new ConcurrentHashMap<DN,MatchingRule>();
099      }
100    
101    
102    
103      /**
104       * Initializes all matching rules currently defined in the Directory Server
105       * configuration.  This should only be called at Directory Server startup.
106       *
107       * @throws  ConfigException  If a configuration problem causes the matching
108       *                           rule initialization process to fail.
109       *
110       * @throws  InitializationException  If a problem occurs while initializing
111       *                                   the matching rules that is not related to
112       *                                   the server configuration.
113       */
114      public void initializeMatchingRules()
115             throws ConfigException, InitializationException
116      {
117        // Get the root configuration object.
118        ServerManagementContext managementContext =
119             ServerManagementContext.getInstance();
120        RootCfg rootConfiguration =
121             managementContext.getRootConfiguration();
122    
123    
124        // Register as an add and delete listener with the root configuration so we
125        // can be notified if any matching rule entries are added or removed.
126        rootConfiguration.addMatchingRuleAddListener(this);
127        rootConfiguration.addMatchingRuleDeleteListener(this);
128    
129    
130        //Initialize the existing matching rules.
131        for (String name : rootConfiguration.listMatchingRules())
132        {
133          MatchingRuleCfg mrConfiguration = rootConfiguration.getMatchingRule(name);
134          mrConfiguration.addChangeListener(this);
135    
136          if (mrConfiguration.isEnabled())
137          {
138            String className = mrConfiguration.getJavaClass();
139            try
140            {
141              MatchingRule matchingRule =
142                   loadMatchingRule(className, mrConfiguration, true);
143    
144              try
145              {
146                DirectoryServer.registerMatchingRule(matchingRule, false);
147                matchingRules.put(mrConfiguration.dn(), matchingRule);
148              }
149              catch (DirectoryException de)
150              {
151                Message message = WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(
152                    String.valueOf(mrConfiguration.dn()), de.getMessageObject());
153                ErrorLogger.logError(message);
154                continue;
155              }
156            }
157            catch (InitializationException ie)
158            {
159              ErrorLogger.logError(ie.getMessageObject());
160              continue;
161            }
162          }
163        }
164      }
165    
166    
167    
168      /**
169       * {@inheritDoc}
170       */
171      public boolean isConfigurationAddAcceptable(MatchingRuleCfg configuration,
172                          List<Message> unacceptableReasons)
173      {
174        if (configuration.isEnabled())
175        {
176          // Get the name of the class and make sure we can instantiate it as a
177          // matching rule.
178          String className = configuration.getJavaClass();
179          try
180          {
181            loadMatchingRule(className, configuration, false);
182          }
183          catch (InitializationException ie)
184          {
185            unacceptableReasons.add(ie.getMessageObject());
186            return false;
187          }
188        }
189    
190        // If we've gotten here, then it's fine.
191        return true;
192      }
193    
194    
195    
196      /**
197       * {@inheritDoc}
198       */
199      public ConfigChangeResult applyConfigurationAdd(MatchingRuleCfg configuration)
200      {
201        ResultCode        resultCode          = ResultCode.SUCCESS;
202        boolean           adminActionRequired = false;
203        ArrayList<Message> messages            = new ArrayList<Message>();
204    
205        configuration.addChangeListener(this);
206    
207        if (! configuration.isEnabled())
208        {
209          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
210        }
211    
212        MatchingRule matchingRule = null;
213    
214        // Get the name of the class and make sure we can instantiate it as a
215        // matching rule.
216        String className = configuration.getJavaClass();
217        try
218        {
219          matchingRule = loadMatchingRule(className, configuration, true);
220    
221          try
222          {
223            DirectoryServer.registerMatchingRule(matchingRule, false);
224            matchingRules.put(configuration.dn(), matchingRule);
225          }
226          catch (DirectoryException de)
227          {
228            Message message = WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(
229                    String.valueOf(configuration.dn()),
230                    de.getMessageObject());
231            messages.add(message);
232    
233            if (resultCode == ResultCode.SUCCESS)
234            {
235              resultCode = DirectoryServer.getServerErrorResultCode();
236            }
237          }
238        }
239        catch (InitializationException ie)
240        {
241          if (resultCode == ResultCode.SUCCESS)
242          {
243            resultCode = DirectoryServer.getServerErrorResultCode();
244          }
245    
246          messages.add(ie.getMessageObject());
247        }
248    
249        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
250      }
251    
252    
253    
254      /**
255       * {@inheritDoc}
256       */
257      public boolean isConfigurationDeleteAcceptable(MatchingRuleCfg configuration,
258                          List<Message> unacceptableReasons)
259      {
260        // If the matching rule is enabled, then check to see if there are any
261        // defined attribute types or matching rule uses that use the matching rule.
262        // If so, then don't allow it to be deleted.
263        boolean configAcceptable = true;
264        MatchingRule matchingRule = matchingRules.get(configuration.dn());
265        if (matchingRule != null)
266        {
267          String oid = matchingRule.getOID();
268          for (AttributeType at : DirectoryServer.getAttributeTypes().values())
269          {
270            ApproximateMatchingRule amr = at.getApproximateMatchingRule();
271            if ((amr != null) && oid.equals(amr.getOID()))
272            {
273              Message message =
274                      WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_AT.get(
275                              matchingRule.getName(),
276                              at.getNameOrOID());
277              unacceptableReasons.add(message);
278    
279              configAcceptable = false;
280              continue;
281            }
282    
283            EqualityMatchingRule emr = at.getEqualityMatchingRule();
284            if ((emr != null) && oid.equals(emr.getOID()))
285            {
286              Message message =
287                      WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_AT.get(
288                              matchingRule.getName(),
289                              at.getNameOrOID());
290              unacceptableReasons.add(message);
291    
292              configAcceptable = false;
293              continue;
294            }
295    
296            OrderingMatchingRule omr = at.getOrderingMatchingRule();
297            if ((omr != null) && oid.equals(omr.getOID()))
298            {
299              Message message =
300                      WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_AT.get(
301                              matchingRule.getName(),
302                              at.getNameOrOID());
303              unacceptableReasons.add(message);
304    
305              configAcceptable = false;
306              continue;
307            }
308    
309            SubstringMatchingRule smr = at.getSubstringMatchingRule();
310            if ((smr != null) && oid.equals(smr.getOID()))
311            {
312              Message message =
313                      WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_AT.get(
314                              matchingRule.getName(),
315                              at.getNameOrOID());
316              unacceptableReasons.add(message);
317    
318              configAcceptable = false;
319              continue;
320            }
321          }
322    
323          for (MatchingRuleUse mru : DirectoryServer.getMatchingRuleUses().values())
324          {
325            if (oid.equals(mru.getMatchingRule().getOID()))
326            {
327              Message message =
328                      WARN_CONFIG_SCHEMA_CANNOT_DELETE_MR_IN_USE_BY_MRU.get(
329                              matchingRule.getName(),
330                              mru.getName());
331              unacceptableReasons.add(message);
332    
333              configAcceptable = false;
334              continue;
335            }
336          }
337        }
338    
339        return configAcceptable;
340      }
341    
342    
343    
344      /**
345       * {@inheritDoc}
346       */
347      public ConfigChangeResult applyConfigurationDelete(
348                                     MatchingRuleCfg configuration)
349      {
350        ResultCode        resultCode          = ResultCode.SUCCESS;
351        boolean           adminActionRequired = false;
352        ArrayList<Message> messages            = new ArrayList<Message>();
353    
354        MatchingRule matchingRule = matchingRules.remove(configuration.dn());
355        if (matchingRule != null)
356        {
357          DirectoryServer.deregisterMatchingRule(matchingRule);
358          matchingRule.finalizeMatchingRule();
359        }
360    
361        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
362      }
363    
364    
365    
366      /**
367       * {@inheritDoc}
368       */
369      public boolean isConfigurationChangeAcceptable(MatchingRuleCfg configuration,
370                          List<Message> unacceptableReasons)
371      {
372        boolean configAcceptable = true;
373        if (configuration.isEnabled())
374        {
375          // Get the name of the class and make sure we can instantiate it as a
376          // matching rule.
377          String className = configuration.getJavaClass();
378          try
379          {
380            loadMatchingRule(className, configuration, false);
381          }
382          catch (InitializationException ie)
383          {
384            unacceptableReasons.add(ie.getMessageObject());
385            configAcceptable = false;
386          }
387        }
388        else
389        {
390          // If the matching rule is currently enabled and the change would make it
391          // disabled, then only allow it if the matching rule isn't already in use.
392          MatchingRule matchingRule = matchingRules.get(configuration.dn());
393          if (matchingRule != null)
394          {
395            String oid = matchingRule.getOID();
396            for (AttributeType at : DirectoryServer.getAttributeTypes().values())
397            {
398              ApproximateMatchingRule amr = at.getApproximateMatchingRule();
399              if ((amr != null) && oid.equals(amr.getOID()))
400              {
401                Message message =
402                        WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_AT.get(
403                                matchingRule.getName(),
404                                at.getNameOrOID());
405                unacceptableReasons.add(message);
406    
407                configAcceptable = false;
408                continue;
409              }
410    
411              EqualityMatchingRule emr = at.getEqualityMatchingRule();
412              if ((emr != null) && oid.equals(emr.getOID()))
413              {
414                Message message =
415                        WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_AT.get(
416                                matchingRule.getName(),
417                                at.getNameOrOID());
418                unacceptableReasons.add(message);
419    
420                configAcceptable = false;
421                continue;
422              }
423    
424              OrderingMatchingRule omr = at.getOrderingMatchingRule();
425              if ((omr != null) && oid.equals(omr.getOID()))
426              {
427                Message message =
428                        WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_AT.get(
429                                matchingRule.getName(),
430                                at.getNameOrOID());
431                unacceptableReasons.add(message);
432    
433                configAcceptable = false;
434                continue;
435              }
436    
437              SubstringMatchingRule smr = at.getSubstringMatchingRule();
438              if ((smr != null) && oid.equals(smr.getOID()))
439              {
440                Message message = WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_AT
441                        .get(matchingRule.getName(), at.getNameOrOID());
442                unacceptableReasons.add(message);
443    
444                configAcceptable = false;
445                continue;
446              }
447            }
448    
449            for (MatchingRuleUse mru :
450                 DirectoryServer.getMatchingRuleUses().values())
451            {
452              if (oid.equals(mru.getMatchingRule().getOID()))
453              {
454                Message message =
455                        WARN_CONFIG_SCHEMA_CANNOT_DISABLE_MR_IN_USE_BY_MRU.get(
456                                matchingRule.getName(), mru.getName());
457                unacceptableReasons.add(message);
458    
459                configAcceptable = false;
460                continue;
461              }
462            }
463          }
464        }
465    
466        return configAcceptable;
467      }
468    
469    
470    
471      /**
472       * {@inheritDoc}
473       */
474      public ConfigChangeResult applyConfigurationChange(
475                                     MatchingRuleCfg configuration)
476      {
477        ResultCode        resultCode          = ResultCode.SUCCESS;
478        boolean           adminActionRequired = false;
479        ArrayList<Message> messages            = new ArrayList<Message>();
480    
481    
482        // Get the existing matching rule if it's already enabled.
483        MatchingRule existingRule = matchingRules.get(configuration.dn());
484    
485    
486        // If the new configuration has the matching rule disabled, then disable it
487        // if it is enabled, or do nothing if it's already disabled.
488        if (! configuration.isEnabled())
489        {
490          if (existingRule != null)
491          {
492            DirectoryServer.deregisterMatchingRule(existingRule);
493    
494            MatchingRule rule = matchingRules.remove(configuration.dn());
495            if (rule != null)
496            {
497              rule.finalizeMatchingRule();
498            }
499          }
500    
501          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
502        }
503    
504    
505        // Get the class for the matching rule.  If the matching rule is already
506        // enabled, then we shouldn't do anything with it although if the class has
507        // changed then we'll at least need to indicate that administrative action
508        // is required.  If the matching rule is disabled, then instantiate the
509        // class and initialize and register it as a matching rule.
510        String className = configuration.getJavaClass();
511        if (existingRule != null)
512        {
513          if (! className.equals(existingRule.getClass().getName()))
514          {
515            adminActionRequired = true;
516          }
517    
518          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
519        }
520    
521        MatchingRule matchingRule = null;
522        try
523        {
524          matchingRule = loadMatchingRule(className, configuration, true);
525    
526          try
527          {
528            DirectoryServer.registerMatchingRule(matchingRule, false);
529            matchingRules.put(configuration.dn(), matchingRule);
530          }
531          catch (DirectoryException de)
532          {
533            Message message = WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(
534                    String.valueOf(configuration.dn()), de.getMessageObject());
535            messages.add(message);
536    
537            if (resultCode == ResultCode.SUCCESS)
538            {
539              resultCode = DirectoryServer.getServerErrorResultCode();
540            }
541          }
542        }
543        catch (InitializationException ie)
544        {
545          if (resultCode == ResultCode.SUCCESS)
546          {
547            resultCode = DirectoryServer.getServerErrorResultCode();
548          }
549    
550          messages.add(ie.getMessageObject());
551        }
552    
553        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
554      }
555    
556    
557    
558      /**
559       * Loads the specified class, instantiates it as an attribute syntax, and
560       * optionally initializes that instance.
561       *
562       * @param  className      The fully-qualified name of the attribute syntax
563       *                        class to load, instantiate, and initialize.
564       * @param  configuration  The configuration to use to initialize the attribute
565       *                        syntax.  It must not be {@code null}.
566       * @param  initialize     Indicates whether the matching rule instance should
567       *                        be initialized.
568       *
569       * @return  The possibly initialized attribute syntax.
570       *
571       * @throws  InitializationException  If a problem occurred while attempting to
572       *                                   initialize the attribute syntax.
573       */
574      private MatchingRule loadMatchingRule(String className,
575                                            MatchingRuleCfg configuration,
576                                            boolean initialize)
577              throws InitializationException
578      {
579        try
580        {
581          MatchingRule matchingRule = null;
582          if (configuration instanceof ApproximateMatchingRuleCfg)
583          {
584            ApproximateMatchingRuleCfgDefn definition =
585                 ApproximateMatchingRuleCfgDefn.getInstance();
586            ClassPropertyDefinition propertyDefinition =
587                 definition.getJavaClassPropertyDefinition();
588            Class<? extends ApproximateMatchingRule> approximateMatchingRuleClass =
589                 propertyDefinition.loadClass(className,
590                                              ApproximateMatchingRule.class);
591            matchingRule = approximateMatchingRuleClass.newInstance();
592          }
593          else if (configuration instanceof EqualityMatchingRuleCfg)
594          {
595            EqualityMatchingRuleCfgDefn definition =
596                 EqualityMatchingRuleCfgDefn.getInstance();
597            ClassPropertyDefinition propertyDefinition =
598                 definition.getJavaClassPropertyDefinition();
599            Class<? extends EqualityMatchingRule> equalityMatchingRuleClass =
600                 propertyDefinition.loadClass(className,
601                                              EqualityMatchingRule.class);
602            matchingRule = equalityMatchingRuleClass.newInstance();
603          }
604          else if (configuration instanceof OrderingMatchingRuleCfg)
605          {
606            OrderingMatchingRuleCfgDefn definition =
607                 OrderingMatchingRuleCfgDefn.getInstance();
608            ClassPropertyDefinition propertyDefinition =
609                 definition.getJavaClassPropertyDefinition();
610            Class<? extends OrderingMatchingRule> orderingMatchingRuleClass =
611                 propertyDefinition.loadClass(className,
612                                              OrderingMatchingRule.class);
613            matchingRule = orderingMatchingRuleClass.newInstance();
614          }
615          else if (configuration instanceof SubstringMatchingRuleCfg)
616          {
617            SubstringMatchingRuleCfgDefn definition =
618                 SubstringMatchingRuleCfgDefn.getInstance();
619            ClassPropertyDefinition propertyDefinition =
620                 definition.getJavaClassPropertyDefinition();
621            Class<? extends SubstringMatchingRule> substringMatchingRuleClass =
622                 propertyDefinition.loadClass(className,
623                                              SubstringMatchingRule.class);
624            matchingRule = substringMatchingRuleClass.newInstance();
625          }
626          else
627          {
628            throw new AssertionError("Unsupported matching rule type:  " +
629                                     className + " with config type " +
630                                     configuration.getClass().getName());
631          }
632    
633          if (initialize)
634          {
635            Method method = matchingRule.getClass().getMethod(
636                "initializeMatchingRule", configuration.configurationClass());
637            method.invoke(matchingRule, configuration);
638          }
639          else
640          {
641            Method method =
642                 matchingRule.getClass().getMethod("isConfigurationAcceptable",
643                                                   MatchingRuleCfg.class,
644                                                   List.class);
645    
646            List<Message> unacceptableReasons = new ArrayList<Message>();
647            Boolean acceptable = (Boolean) method.invoke(matchingRule,
648                                                         configuration,
649                                                         unacceptableReasons);
650            if (! acceptable)
651            {
652              StringBuilder buffer = new StringBuilder();
653              if (! unacceptableReasons.isEmpty())
654              {
655                Iterator<Message> iterator = unacceptableReasons.iterator();
656                buffer.append(iterator.next());
657                while (iterator.hasNext())
658                {
659                  buffer.append(".  ");
660                  buffer.append(iterator.next());
661                }
662              }
663    
664              Message message = ERR_CONFIG_SCHEMA_MR_CONFIG_NOT_ACCEPTABLE.get(
665                  String.valueOf(configuration.dn()), buffer.toString());
666              throw new InitializationException(message);
667            }
668          }
669    
670          return matchingRule;
671        }
672        catch (Exception e)
673        {
674          Message message = ERR_CONFIG_SCHEMA_MR_CANNOT_INITIALIZE.
675              get(className, String.valueOf(configuration.dn()),
676                  stackTraceToSingleLineString(e));
677          throw new InitializationException(message, e);
678        }
679      }
680    }
681