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