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 2007-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.LinkedHashSet;
037    import java.util.concurrent.ConcurrentHashMap;
038    
039    import org.opends.server.admin.ClassPropertyDefinition;
040    import org.opends.server.admin.server.ConfigurationAddListener;
041    import org.opends.server.admin.server.ConfigurationChangeListener;
042    import org.opends.server.admin.server.ConfigurationDeleteListener;
043    import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
044    import org.opends.server.admin.std.server.VirtualAttributeCfg;
045    import org.opends.server.admin.std.server.RootCfg;
046    import org.opends.server.admin.server.ServerManagementContext;
047    import org.opends.server.api.VirtualAttributeProvider;
048    import org.opends.server.config.ConfigException;
049    import org.opends.server.types.ConfigChangeResult;
050    import org.opends.server.types.DebugLogLevel;
051    import org.opends.server.types.DirectoryException;
052    import org.opends.server.types.DN;
053    
054    
055    import org.opends.server.types.InitializationException;
056    import org.opends.server.types.ResultCode;
057    import org.opends.server.types.SearchFilter;
058    import org.opends.server.types.VirtualAttributeRule;
059    
060    import static org.opends.server.loggers.debug.DebugLogger.*;
061    import org.opends.server.loggers.debug.DebugTracer;
062    import org.opends.server.loggers.ErrorLogger;
063    import static org.opends.messages.ConfigMessages.*;
064    
065    import static org.opends.server.util.StaticUtils.*;
066    
067    
068    
069    /**
070     * This class defines a utility that will be used to manage the set of
071     * virtual attribute providers defined in the Directory Server.  It will
072     * initialize the providers when the server starts, and then will manage any
073     * additions, removals, or modifications to any virtual attribute providers
074     * while the server is running.
075     */
076    public class VirtualAttributeConfigManager
077           implements ConfigurationChangeListener<VirtualAttributeCfg>,
078                      ConfigurationAddListener<VirtualAttributeCfg>,
079                      ConfigurationDeleteListener<VirtualAttributeCfg>
080    {
081      /**
082       * The tracer object for the debug logger.
083       */
084      private static final DebugTracer TRACER = getTracer();
085    
086      // A mapping between the DNs of the config entries and the associated
087      // virtual attribute rules.
088      private ConcurrentHashMap<DN,VirtualAttributeRule> rules;
089    
090    
091    
092      /**
093       * Creates a new instance of this virtual attribute config manager.
094       */
095      public VirtualAttributeConfigManager()
096      {
097        rules = new ConcurrentHashMap<DN,VirtualAttributeRule>();
098      }
099    
100    
101    
102      /**
103       * Initializes all virtual attribute providers currently defined in the
104       * Directory Server configuration.  This should only be called at Directory
105       * Server startup.
106       *
107       * @throws  ConfigException  If a configuration problem causes the virtual
108       *                           attribute provider initialization process to
109       *                           fail.
110       *
111       * @throws  InitializationException  If a problem occurs while initializing
112       *                                   the virtual attribute providers that is
113       *                                   not related to the server configuration.
114       */
115      public void initializeVirtualAttributes()
116             throws ConfigException, InitializationException
117      {
118        // Get the root configuration object.
119        ServerManagementContext managementContext =
120             ServerManagementContext.getInstance();
121        RootCfg rootConfiguration =
122             managementContext.getRootConfiguration();
123    
124    
125        // Register as an add and delete listener with the root configuration so we
126        // can be notified if any virtual attribute provider entries are added or
127        // removed.
128        rootConfiguration.addVirtualAttributeAddListener(this);
129        rootConfiguration.addVirtualAttributeDeleteListener(this);
130    
131    
132        //Initialize the existing virtual attribute providers.
133        for (String providerName : rootConfiguration.listVirtualAttributes())
134        {
135          VirtualAttributeCfg cfg =
136               rootConfiguration.getVirtualAttribute(providerName);
137          cfg.addChangeListener(this);
138    
139          if (cfg.isEnabled())
140          {
141            String className = cfg.getJavaClass();
142            try
143            {
144              VirtualAttributeProvider<? extends VirtualAttributeCfg> provider =
145                   loadProvider(className, cfg, true);
146    
147              LinkedHashSet<SearchFilter> filters =
148                   new LinkedHashSet<SearchFilter>();
149              for (String filterString : cfg.getFilter())
150              {
151                try
152                {
153                  filters.add(SearchFilter.createFilterFromString(filterString));
154                }
155                catch (DirectoryException de)
156                {
157                  if (debugEnabled())
158                  {
159                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
160                  }
161    
162                  Message message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
163                          filterString, String.valueOf(cfg.dn()),
164                          de.getMessageObject());
165                  throw new ConfigException(message, de);
166                }
167              }
168    
169              if (cfg.getAttributeType().isSingleValue())
170              {
171                if (provider.isMultiValued())
172                {
173                  Message message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER.
174                      get(String.valueOf(cfg.dn()),
175                          cfg.getAttributeType().getNameOrOID(), className);
176                  throw new ConfigException(message);
177                }
178                else if (cfg.getConflictBehavior() ==
179                         VirtualAttributeCfgDefn.ConflictBehavior.
180                              MERGE_REAL_AND_VIRTUAL)
181                {
182                  Message message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES.
183                      get(String.valueOf(cfg.dn()),
184                          cfg.getAttributeType().getNameOrOID());
185                  throw new ConfigException(message);
186                }
187              }
188    
189              VirtualAttributeRule rule =
190                   new VirtualAttributeRule(cfg.getAttributeType(), provider,
191                                            cfg.getBaseDN(), cfg.getGroupDN(),
192                                            filters, cfg.getConflictBehavior());
193              rules.put(cfg.dn(), rule);
194              DirectoryServer.registerVirtualAttribute(rule);
195            }
196            catch (InitializationException ie)
197            {
198              ErrorLogger.logError(ie.getMessageObject());
199              continue;
200            }
201          }
202        }
203      }
204    
205    
206    
207      /**
208       * {@inheritDoc}
209       */
210      public boolean isConfigurationAddAcceptable(
211                          VirtualAttributeCfg configuration,
212                          List<Message> unacceptableReasons)
213      {
214        if (configuration.isEnabled())
215        {
216          // Get the name of the class and make sure we can instantiate it as a
217          // virtual attribute provider.
218          String className = configuration.getJavaClass();
219          try
220          {
221            loadProvider(className, configuration, false);
222          }
223          catch (InitializationException ie)
224          {
225            unacceptableReasons.add(ie.getMessageObject());
226            return false;
227          }
228        }
229    
230        // If there were any search filters provided, then make sure they are all
231        // valid.
232        for (String filterString : configuration.getFilter())
233        {
234          try
235          {
236            SearchFilter.createFilterFromString(filterString);
237          }
238          catch (DirectoryException de)
239          {
240            if (debugEnabled())
241            {
242              TRACER.debugCaught(DebugLogLevel.ERROR, de);
243            }
244    
245            Message message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
246                    filterString,
247                    String.valueOf(configuration.dn()),
248                    de.getMessageObject());
249            unacceptableReasons.add(message);
250            return false;
251          }
252        }
253    
254        // If we've gotten here, then it's fine.
255        return true;
256      }
257    
258    
259    
260      /**
261       * {@inheritDoc}
262       */
263      public ConfigChangeResult applyConfigurationAdd(
264                                     VirtualAttributeCfg configuration)
265      {
266        ResultCode        resultCode          = ResultCode.SUCCESS;
267        boolean           adminActionRequired = false;
268        ArrayList<Message> messages            = new ArrayList<Message>();
269    
270        configuration.addChangeListener(this);
271    
272        if (! configuration.isEnabled())
273        {
274          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
275        }
276    
277        // Make sure that we can parse all of the search filters.
278        LinkedHashSet<SearchFilter> filters =
279             new LinkedHashSet<SearchFilter>();
280        for (String filterString : configuration.getFilter())
281        {
282          try
283          {
284            filters.add(SearchFilter.createFilterFromString(filterString));
285          }
286          catch (DirectoryException de)
287          {
288            if (debugEnabled())
289            {
290              TRACER.debugCaught(DebugLogLevel.ERROR, de);
291            }
292    
293            if (resultCode == ResultCode.SUCCESS)
294            {
295              resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
296            }
297    
298            Message message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
299                    filterString,
300                    String.valueOf(configuration.dn()),
301                    de.getMessageObject());
302            messages.add(message);
303          }
304        }
305    
306        // Get the name of the class and make sure we can instantiate it as a
307        // certificate mapper.
308        VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
309        if (resultCode == ResultCode.SUCCESS)
310        {
311          String className = configuration.getJavaClass();
312          try
313          {
314            provider = loadProvider(className, configuration, true);
315          }
316          catch (InitializationException ie)
317          {
318            resultCode = DirectoryServer.getServerErrorResultCode();
319            messages.add(ie.getMessageObject());
320          }
321        }
322    
323        if (resultCode == ResultCode.SUCCESS)
324        {
325          VirtualAttributeRule rule =
326               new VirtualAttributeRule(configuration.getAttributeType(), provider,
327                                        configuration.getBaseDN(),
328                                        configuration.getGroupDN(),
329                                        filters,
330                                        configuration.getConflictBehavior());
331    
332          rules.put(configuration.dn(), rule);
333          DirectoryServer.registerVirtualAttribute(rule);
334        }
335    
336        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
337      }
338    
339    
340    
341      /**
342       * {@inheritDoc}
343       */
344      public boolean isConfigurationDeleteAcceptable(
345                          VirtualAttributeCfg configuration,
346                          List<Message> unacceptableReasons)
347      {
348        // We will always allow getting rid of a virtual attribute rule.
349        return true;
350      }
351    
352    
353    
354      /**
355       * {@inheritDoc}
356       */
357      public ConfigChangeResult applyConfigurationDelete(
358                                     VirtualAttributeCfg configuration)
359      {
360        ResultCode        resultCode          = ResultCode.SUCCESS;
361        boolean           adminActionRequired = false;
362        ArrayList<Message> messages            = new ArrayList<Message>();
363    
364        VirtualAttributeRule rule = rules.remove(configuration.dn());
365        if (rule != null)
366        {
367          DirectoryServer.deregisterVirtualAttribute(rule);
368          rule.getProvider().finalizeVirtualAttributeProvider();
369        }
370    
371        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
372      }
373    
374    
375    
376      /**
377       * {@inheritDoc}
378       */
379      public boolean isConfigurationChangeAcceptable(
380                          VirtualAttributeCfg configuration,
381                          List<Message> unacceptableReasons)
382      {
383        if (configuration.isEnabled())
384        {
385          // Get the name of the class and make sure we can instantiate it as a
386          // virtual attribute provider.
387          String className = configuration.getJavaClass();
388          try
389          {
390            loadProvider(className, configuration, false);
391          }
392          catch (InitializationException ie)
393          {
394            unacceptableReasons.add(ie.getMessageObject());
395            return false;
396          }
397        }
398    
399        // If there were any search filters provided, then make sure they are all
400        // valid.
401        for (String filterString : configuration.getFilter())
402        {
403          try
404          {
405            SearchFilter.createFilterFromString(filterString);
406          }
407          catch (DirectoryException de)
408          {
409            if (debugEnabled())
410            {
411              TRACER.debugCaught(DebugLogLevel.ERROR, de);
412            }
413    
414            Message message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
415                    filterString,
416                    String.valueOf(configuration.dn()),
417                    de.getMessageObject());
418            unacceptableReasons.add(message);
419            return false;
420          }
421        }
422    
423        // If we've gotten here, then it's fine.
424        return true;
425      }
426    
427    
428    
429      /**
430       * {@inheritDoc}
431       */
432      public ConfigChangeResult applyConfigurationChange(
433                                     VirtualAttributeCfg configuration)
434      {
435        ResultCode        resultCode          = ResultCode.SUCCESS;
436        boolean           adminActionRequired = false;
437        ArrayList<Message> messages            = new ArrayList<Message>();
438    
439    
440        // Get the existing rule if it's already enabled.
441        VirtualAttributeRule existingRule = rules.get(configuration.dn());
442    
443    
444        // If the new configuration has the rule disabled, then disable it if it
445        // is enabled, or do nothing if it's already disabled.
446        if (! configuration.isEnabled())
447        {
448          if (existingRule != null)
449          {
450            DirectoryServer.deregisterVirtualAttribute(existingRule);
451            existingRule.getProvider().finalizeVirtualAttributeProvider();
452          }
453    
454          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
455        }
456    
457    
458        // Make sure that we can parse all of the search filters.
459        LinkedHashSet<SearchFilter> filters =
460             new LinkedHashSet<SearchFilter>();
461        for (String filterString : configuration.getFilter())
462        {
463          try
464          {
465            filters.add(SearchFilter.createFilterFromString(filterString));
466          }
467          catch (DirectoryException de)
468          {
469            if (debugEnabled())
470            {
471              TRACER.debugCaught(DebugLogLevel.ERROR, de);
472            }
473    
474            if (resultCode == ResultCode.SUCCESS)
475            {
476              resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
477            }
478    
479            Message message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
480                    filterString,
481                    String.valueOf(configuration.dn()),
482                    de.getMessageObject());
483            messages.add(message);
484          }
485        }
486    
487        // Get the name of the class and make sure we can instantiate it as a
488        // certificate mapper.
489        VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
490        if (resultCode == ResultCode.SUCCESS)
491        {
492          String className = configuration.getJavaClass();
493          try
494          {
495            provider = loadProvider(className, configuration, true);
496          }
497          catch (InitializationException ie)
498          {
499            resultCode = DirectoryServer.getServerErrorResultCode();
500            messages.add(ie.getMessageObject());
501          }
502        }
503    
504        if (resultCode == ResultCode.SUCCESS)
505        {
506          VirtualAttributeRule rule =
507               new VirtualAttributeRule(configuration.getAttributeType(), provider,
508                                        configuration.getBaseDN(),
509                                        configuration.getGroupDN(),
510                                        filters,
511                                        configuration.getConflictBehavior());
512    
513          rules.put(configuration.dn(), rule);
514          if (existingRule == null)
515          {
516            DirectoryServer.registerVirtualAttribute(rule);
517          }
518          else
519          {
520            DirectoryServer.replaceVirtualAttribute(existingRule, rule);
521            existingRule.getProvider().finalizeVirtualAttributeProvider();
522          }
523        }
524    
525        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
526      }
527    
528    
529    
530      /**
531       * Loads the specified class, instantiates it as a certificate mapper, and
532       * optionally initializes that instance.
533       *
534       * @param  className      The fully-qualified name of the certificate mapper
535       *                        class to load, instantiate, and initialize.
536       * @param  configuration  The configuration to use to initialize the
537       *                        virtual attribute provider.  It must not be
538       *                        {@code null}.
539       * @param  initialize     Indicates whether the virtual attribute provider
540       *                        instance should be initialized.
541       *
542       * @return  The possibly initialized certificate mapper.
543       *
544       * @throws  InitializationException  If a problem occurred while attempting to
545       *                                   initialize the certificate mapper.
546       */
547      private VirtualAttributeProvider<? extends VirtualAttributeCfg>
548                   loadProvider(String className, VirtualAttributeCfg configuration,
549                                boolean initialize)
550              throws InitializationException
551      {
552        try
553        {
554          VirtualAttributeCfgDefn definition =
555               VirtualAttributeCfgDefn.getInstance();
556          ClassPropertyDefinition propertyDefinition =
557               definition.getJavaClassPropertyDefinition();
558          Class<? extends VirtualAttributeProvider> providerClass =
559               propertyDefinition.loadClass(className,
560                                            VirtualAttributeProvider.class);
561          VirtualAttributeProvider<? extends VirtualAttributeCfg> provider =
562               (VirtualAttributeProvider<? extends VirtualAttributeCfg>)
563               providerClass.newInstance();
564    
565          if (initialize)
566          {
567            Method method = provider.getClass().getMethod(
568                "initializeVirtualAttributeProvider",
569                configuration.configurationClass());
570            method.invoke(provider, configuration);
571          }
572          else
573          {
574            Method method =
575                 provider.getClass().getMethod("isConfigurationAcceptable",
576                                               VirtualAttributeCfg.class,
577                                               List.class);
578    
579            List<Message> unacceptableReasons = new ArrayList<Message>();
580            Boolean acceptable = (Boolean) method.invoke(provider, configuration,
581                                                         unacceptableReasons);
582            if (! acceptable)
583            {
584              StringBuilder buffer = new StringBuilder();
585              if (! unacceptableReasons.isEmpty())
586              {
587                Iterator<Message> iterator = unacceptableReasons.iterator();
588                buffer.append(iterator.next());
589                while (iterator.hasNext())
590                {
591                  buffer.append(".  ");
592                  buffer.append(iterator.next());
593                }
594              }
595    
596              Message message = ERR_CONFIG_VATTR_CONFIG_NOT_ACCEPTABLE.get(
597                  String.valueOf(configuration.dn()), buffer.toString());
598              throw new InitializationException(message);
599            }
600          }
601    
602          return provider;
603        }
604        catch (Exception e)
605        {
606          Message message = ERR_CONFIG_VATTR_INITIALIZATION_FAILED.
607              get(className, String.valueOf(configuration.dn()),
608                  stackTraceToSingleLineString(e));
609          throw new InitializationException(message, e);
610        }
611      }
612    }
613