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 2007-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.core;
028
029import static org.forgerock.opendj.adapter.server3x.Converters.*;
030import static org.opends.messages.ConfigMessages.*;
031import static org.opends.server.util.StaticUtils.*;
032
033import java.util.*;
034import java.util.Map.Entry;
035import java.util.concurrent.ConcurrentHashMap;
036import java.util.concurrent.ConcurrentMap;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.config.server.ConfigChangeResult;
041import org.forgerock.opendj.config.server.ConfigException;
042import org.forgerock.opendj.ldap.ResultCode;
043import org.forgerock.util.Utils;
044import org.opends.server.admin.ClassPropertyDefinition;
045import org.opends.server.admin.server.ConfigurationAddListener;
046import org.opends.server.admin.server.ConfigurationChangeListener;
047import org.opends.server.admin.server.ConfigurationDeleteListener;
048import org.opends.server.admin.server.ServerManagementContext;
049import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
050import org.opends.server.admin.std.server.RootCfg;
051import org.opends.server.admin.std.server.VirtualAttributeCfg;
052import org.opends.server.api.VirtualAttributeProvider;
053import org.opends.server.types.*;
054
055/**
056 * This class defines a utility that will be used to manage the set of
057 * virtual attribute providers defined in the Directory Server.  It will
058 * initialize the providers when the server starts, and then will manage any
059 * additions, removals, or modifications to any virtual attribute providers
060 * while the server is running.
061 */
062public class VirtualAttributeConfigManager
063       implements ConfigurationChangeListener<VirtualAttributeCfg>,
064                  ConfigurationAddListener<VirtualAttributeCfg>,
065                  ConfigurationDeleteListener<VirtualAttributeCfg>
066{
067  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068
069  /**
070   * A mapping between the DNs of the config entries and the associated virtual
071   * attribute rules.
072   */
073  private final ConcurrentMap<DN, VirtualAttributeRule> rules = new ConcurrentHashMap<>();
074
075  private final ServerContext serverContext;
076
077  /**
078   * Creates a new instance of this virtual attribute config manager.
079   *
080   * @param serverContext
081   *            The server context.
082   */
083  public VirtualAttributeConfigManager(ServerContext serverContext)
084  {
085    this.serverContext = serverContext;
086  }
087
088  /**
089   * Initializes all virtual attribute providers currently defined in the
090   * Directory Server configuration. This should only be called at Directory
091   * Server startup.
092   *
093   * @throws ConfigException
094   *           If a configuration problem causes the virtual attribute provider
095   *           initialization process to fail.
096   * @throws InitializationException
097   *           If a problem occurs while initializing the virtual attribute
098   *           providers that is not related to the server configuration.
099   */
100  public void initializeVirtualAttributes()
101         throws ConfigException, InitializationException
102  {
103    // Get the root configuration object.
104    ServerManagementContext managementContext =
105         ServerManagementContext.getInstance();
106    RootCfg rootConfiguration = managementContext.getRootConfiguration();
107
108
109    // Register as an add and delete listener with the root configuration so we
110    // can be notified if any virtual attribute provider entries are added or
111    // removed.
112    rootConfiguration.addVirtualAttributeAddListener(this);
113    rootConfiguration.addVirtualAttributeDeleteListener(this);
114
115
116    //Initialize the existing virtual attribute providers.
117    for (String providerName : rootConfiguration.listVirtualAttributes())
118    {
119      VirtualAttributeCfg cfg =
120           rootConfiguration.getVirtualAttribute(providerName);
121      cfg.addChangeListener(this);
122
123      if (cfg.isEnabled())
124      {
125        String className = cfg.getJavaClass();
126        try
127        {
128          VirtualAttributeProvider<? extends VirtualAttributeCfg> provider =
129               loadProvider(className, cfg, true);
130
131          Map<LocalizableMessage, DirectoryException> reasons =
132              new LinkedHashMap<>();
133          Set<SearchFilter> filters = buildFilters(cfg, reasons);
134          if (!reasons.isEmpty())
135          {
136            Entry<LocalizableMessage, DirectoryException> entry =
137                reasons.entrySet().iterator().next();
138            throw new ConfigException(entry.getKey(), entry.getValue());
139          }
140
141          if (cfg.getAttributeType().isSingleValue())
142          {
143            if (provider.isMultiValued())
144            {
145              LocalizableMessage message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER.
146                  get(cfg.dn(), cfg.getAttributeType().getNameOrOID(), className);
147              throw new ConfigException(message);
148            }
149            else if (cfg.getConflictBehavior() ==
150                     VirtualAttributeCfgDefn.ConflictBehavior.
151                          MERGE_REAL_AND_VIRTUAL)
152            {
153              LocalizableMessage message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES.
154                  get(cfg.dn(), cfg.getAttributeType().getNameOrOID());
155              throw new ConfigException(message);
156            }
157          }
158
159          VirtualAttributeRule rule = createRule(cfg, provider, filters);
160          rules.put(cfg.dn(), rule);
161        }
162        catch (InitializationException ie)
163        {
164          logger.error(ie.getMessageObject());
165          continue;
166        }
167      }
168    }
169  }
170
171  private VirtualAttributeRule createRule(VirtualAttributeCfg cfg,
172      VirtualAttributeProvider<? extends VirtualAttributeCfg> provider,
173      Set<SearchFilter> filters)
174  {
175    return new VirtualAttributeRule(cfg.getAttributeType(), provider,
176           cfg.getBaseDN(),
177           from(cfg.getScope()),
178           cfg.getGroupDN(),
179           filters,
180           cfg.getConflictBehavior());
181  }
182
183  /** {@inheritDoc} */
184  @Override
185  public boolean isConfigurationAddAcceptable(
186                      VirtualAttributeCfg configuration,
187                      List<LocalizableMessage> unacceptableReasons)
188  {
189    if (configuration.isEnabled())
190    {
191      // Get the name of the class and make sure we can instantiate it as a
192      // virtual attribute provider.
193      String className = configuration.getJavaClass();
194      try
195      {
196        loadProvider(className, configuration, false);
197      }
198      catch (InitializationException ie)
199      {
200        unacceptableReasons.add(ie.getMessageObject());
201        return false;
202      }
203    }
204
205    // If there were any search filters provided, then make sure they are all
206    // valid.
207    return areFiltersAcceptable(configuration, unacceptableReasons);
208  }
209
210  private Set<SearchFilter> buildFilters(VirtualAttributeCfg cfg,
211      Map<LocalizableMessage, DirectoryException> unacceptableReasons)
212  {
213    Set<SearchFilter> filters = new LinkedHashSet<>();
214    for (String filterString : cfg.getFilter())
215    {
216      try
217      {
218        filters.add(SearchFilter.createFilterFromString(filterString));
219      }
220      catch (DirectoryException de)
221      {
222        logger.traceException(de);
223
224        LocalizableMessage message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get(
225            filterString, cfg.dn(), de.getMessageObject());
226        unacceptableReasons.put(message, de);
227      }
228    }
229    return filters;
230  }
231
232  /** {@inheritDoc} */
233  @Override
234  public ConfigChangeResult applyConfigurationAdd(
235                                 VirtualAttributeCfg configuration)
236  {
237    final ConfigChangeResult ccr = new ConfigChangeResult();
238
239    configuration.addChangeListener(this);
240
241    if (! configuration.isEnabled())
242    {
243      return ccr;
244    }
245
246    // Make sure that we can parse all of the search filters.
247    Map<LocalizableMessage, DirectoryException> reasons =
248        new LinkedHashMap<>();
249    Set<SearchFilter> filters = buildFilters(configuration, reasons);
250    if (!reasons.isEmpty())
251    {
252      ccr.getMessages().addAll(reasons.keySet());
253      ccr.setResultCodeIfSuccess(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
254    }
255
256    // Get the name of the class and make sure we can instantiate it as a
257    // certificate mapper.
258    VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
259    if (ccr.getResultCode() == ResultCode.SUCCESS)
260    {
261      String className = configuration.getJavaClass();
262      try
263      {
264        provider = loadProvider(className, configuration, true);
265      }
266      catch (InitializationException ie)
267      {
268        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
269        ccr.addMessage(ie.getMessageObject());
270      }
271    }
272
273    if (ccr.getResultCode() == ResultCode.SUCCESS)
274    {
275      VirtualAttributeRule rule = createRule(configuration, provider, filters);
276      rules.put(configuration.dn(), rule);
277    }
278
279    return ccr;
280  }
281
282  /** {@inheritDoc} */
283  @Override
284  public boolean isConfigurationDeleteAcceptable(
285                      VirtualAttributeCfg configuration,
286                      List<LocalizableMessage> unacceptableReasons)
287  {
288    // We will always allow getting rid of a virtual attribute rule.
289    return true;
290  }
291
292  /** {@inheritDoc} */
293  @Override
294  public ConfigChangeResult applyConfigurationDelete(
295                                 VirtualAttributeCfg configuration)
296  {
297    final ConfigChangeResult ccr = new ConfigChangeResult();
298
299    VirtualAttributeRule rule = rules.remove(configuration.dn());
300    if (rule != null)
301    {
302      rule.getProvider().finalizeVirtualAttributeProvider();
303    }
304
305    return ccr;
306  }
307
308  /** {@inheritDoc} */
309  @Override
310  public boolean isConfigurationChangeAcceptable(
311                      VirtualAttributeCfg configuration,
312                      List<LocalizableMessage> unacceptableReasons)
313  {
314    if (configuration.isEnabled())
315    {
316      // Get the name of the class and make sure we can instantiate it as a
317      // virtual attribute provider.
318      String className = configuration.getJavaClass();
319      try
320      {
321        loadProvider(className, configuration, false);
322      }
323      catch (InitializationException ie)
324      {
325        unacceptableReasons.add(ie.getMessageObject());
326        return false;
327      }
328    }
329
330    // If there were any search filters provided, then make sure they are all
331    // valid.
332    return areFiltersAcceptable(configuration, unacceptableReasons);
333  }
334
335  private boolean areFiltersAcceptable(VirtualAttributeCfg cfg,
336      List<LocalizableMessage> unacceptableReasons)
337  {
338    Map<LocalizableMessage, DirectoryException> reasons =
339        new LinkedHashMap<>();
340    buildFilters(cfg, reasons);
341    if (!reasons.isEmpty())
342    {
343      unacceptableReasons.addAll(reasons.keySet());
344      return false;
345    }
346    return true;
347  }
348
349  /** {@inheritDoc} */
350  @Override
351  public ConfigChangeResult applyConfigurationChange(
352                                 VirtualAttributeCfg configuration)
353  {
354    final ConfigChangeResult ccr = new ConfigChangeResult();
355
356
357    // Get the existing rule if it's already enabled.
358    VirtualAttributeRule existingRule = rules.get(configuration.dn());
359
360
361    // If the new configuration has the rule disabled, then disable it if it
362    // is enabled, or do nothing if it's already disabled.
363    if (! configuration.isEnabled())
364    {
365      if (existingRule != null)
366      {
367        rules.remove(configuration.dn());
368        existingRule.getProvider().finalizeVirtualAttributeProvider();
369      }
370
371      return ccr;
372    }
373
374
375    // Make sure that we can parse all of the search filters.
376    Map<LocalizableMessage, DirectoryException> reasons =
377        new LinkedHashMap<>();
378    Set<SearchFilter> filters = buildFilters(configuration, reasons);
379    if (!reasons.isEmpty())
380    {
381      ccr.getMessages().addAll(reasons.keySet());
382      ccr.setResultCodeIfSuccess(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
383    }
384
385    // Get the name of the class and make sure we can instantiate it as a
386    // certificate mapper.
387    VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
388    if (ccr.getResultCode() == ResultCode.SUCCESS)
389    {
390      String className = configuration.getJavaClass();
391      try
392      {
393        provider = loadProvider(className, configuration, true);
394      }
395      catch (InitializationException ie)
396      {
397        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
398        ccr.addMessage(ie.getMessageObject());
399      }
400    }
401
402    if (ccr.getResultCode() == ResultCode.SUCCESS)
403    {
404      VirtualAttributeRule rule = createRule(configuration, provider, filters);
405      rules.put(configuration.dn(), rule);
406      if (existingRule != null)
407      {
408        existingRule.getProvider().finalizeVirtualAttributeProvider();
409      }
410    }
411
412    return ccr;
413  }
414
415
416
417  /**
418   * Loads the specified class, instantiates it as a certificate mapper, and
419   * optionally initializes that instance.
420   *
421   * @param  className      The fully-qualified name of the certificate mapper
422   *                        class to load, instantiate, and initialize.
423   * @param  cfg            The configuration to use to initialize the
424   *                        virtual attribute provider.  It must not be
425   *                        {@code null}.
426   * @param  initialize     Indicates whether the virtual attribute provider
427   *                        instance should be initialized.
428   *
429   * @return  The possibly initialized certificate mapper.
430   *
431   * @throws  InitializationException  If a problem occurred while attempting to
432   *                                   initialize the certificate mapper.
433   */
434  @SuppressWarnings({ "rawtypes", "unchecked" })
435  private VirtualAttributeProvider<? extends VirtualAttributeCfg>
436               loadProvider(String className, VirtualAttributeCfg cfg,
437                            boolean initialize)
438          throws InitializationException
439  {
440    try
441    {
442      VirtualAttributeCfgDefn definition =
443           VirtualAttributeCfgDefn.getInstance();
444      ClassPropertyDefinition propertyDefinition =
445           definition.getJavaClassPropertyDefinition();
446      Class<? extends VirtualAttributeProvider> providerClass =
447           propertyDefinition.loadClass(className,
448                                        VirtualAttributeProvider.class);
449      VirtualAttributeProvider provider = providerClass.newInstance();
450
451      if (initialize)
452      {
453        provider.initializeVirtualAttributeProvider(cfg);
454      }
455      else
456      {
457        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
458        if (!provider.isConfigurationAcceptable(cfg, unacceptableReasons))
459        {
460          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
461          LocalizableMessage message = ERR_CONFIG_VATTR_CONFIG_NOT_ACCEPTABLE.get(cfg.dn(), reasons);
462          throw new InitializationException(message);
463        }
464      }
465
466      return provider;
467    }
468    catch (Exception e)
469    {
470      LocalizableMessage message = ERR_CONFIG_VATTR_INITIALIZATION_FAILED.
471          get(className, cfg.dn(), stackTraceToSingleLineString(e));
472      throw new InitializationException(message, e);
473    }
474  }
475
476  /**
477   * Retrieves the collection of registered virtual attribute rules.
478   *
479   * @return The collection of registered virtual attribute rules.
480   */
481  public Collection<VirtualAttributeRule> getVirtualAttributes()
482  {
483    return this.rules.values();
484  }
485
486  /**
487   * Registers the provided virtual attribute rule.
488   *
489   * @param rule
490   *          The virtual attribute rule to be registered.
491   */
492  public void register(VirtualAttributeRule rule)
493  {
494    rules.put(getDummyDN(rule), rule);
495  }
496
497  /**
498   * Deregisters the provided virtual attribute rule.
499   *
500   * @param rule
501   *          The virtual attribute rule to be deregistered.
502   */
503  public void deregister(VirtualAttributeRule rule)
504  {
505    rules.remove(getDummyDN(rule));
506  }
507
508  private DN getDummyDN(VirtualAttributeRule rule)
509  {
510    try
511    {
512      String name = rule.getAttributeType().getNameOrOID();
513      return DN.valueOf("cn=" + name + ",cn=Virtual Attributes,cn=config");
514    }
515    catch (DirectoryException e)
516    {
517      // should never happen
518      throw new RuntimeException(e);
519    }
520  }
521}