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