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