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.List;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.forgerock.i18n.LocalizableMessage;
026import org.forgerock.i18n.slf4j.LocalizedLogger;
027import org.forgerock.opendj.config.server.ConfigException;
028import org.forgerock.opendj.config.ClassPropertyDefinition;
029import org.forgerock.opendj.config.server.ConfigurationAddListener;
030import org.forgerock.opendj.config.server.ConfigurationChangeListener;
031import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
032import org.forgerock.opendj.server.config.meta.ExtendedOperationHandlerCfgDefn;
033import org.forgerock.opendj.server.config.server.ExtendedOperationHandlerCfg;
034import org.forgerock.opendj.server.config.server.RootCfg;
035import org.opends.server.api.ExtendedOperationHandler;
036import org.forgerock.opendj.config.server.ConfigChangeResult;
037import org.forgerock.opendj.ldap.DN;
038import org.opends.server.types.InitializationException;
039
040/**
041 * This class defines a utility that will be used to manage the set of extended
042 * operation handlers defined in the Directory Server.  It will initialize the
043 * handlers when the server starts, and then will manage any additions,
044 * removals, or modifications of any extended operation handlers while the
045 * server is running.
046 */
047public class ExtendedOperationConfigManager implements
048     ConfigurationChangeListener<ExtendedOperationHandlerCfg>,
049     ConfigurationAddListener<ExtendedOperationHandlerCfg>,
050     ConfigurationDeleteListener<ExtendedOperationHandlerCfg>
051{
052  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
053
054  /**
055   * A mapping between the DNs of the config entries and the associated extended
056   * operation handlers.
057   */
058  private final ConcurrentHashMap<DN,ExtendedOperationHandler> handlers;
059
060  private final ServerContext serverContext;
061
062  /**
063   * Creates a new instance of this extended operation config manager.
064   *
065   * @param serverContext
066   *            The server context.
067   */
068  public ExtendedOperationConfigManager(ServerContext serverContext)
069  {
070    this.serverContext = serverContext;
071    handlers = new ConcurrentHashMap<>();
072  }
073
074  /**
075   * Initializes all extended operation handlers currently defined in the
076   * Directory Server configuration.  This should only be called at Directory
077   * Server startup.
078   *
079   * @throws  ConfigException  If a configuration problem causes the extended
080   *                           operation handler initialization process to fail.
081   *
082   * @throws  InitializationException  If a problem occurs while initializing
083   *                                   the extended operation handler that is
084   *                                   not related to the server configuration.
085   */
086  public void initializeExtendedOperationHandlers()
087         throws ConfigException, InitializationException
088  {
089    RootCfg root = serverContext.getRootConfig();
090    root.addExtendedOperationHandlerAddListener(this);
091    root.addExtendedOperationHandlerDeleteListener(this);
092
093    // Initialize existing handlers.
094    for (String name : root.listExtendedOperationHandlers())
095    {
096      // Get the handler's configuration.
097      // This will decode and validate its properties.
098      ExtendedOperationHandlerCfg config =
099           root.getExtendedOperationHandler(name);
100
101      // Register as a change listener for this handler so that we can be
102      // notified when it is disabled or enabled.
103      config.addChangeListener(this);
104
105      // Ignore this handler if it is disabled.
106      if (config.isEnabled())
107      {
108        // Load the handler's implementation class and initialize it.
109        ExtendedOperationHandler handler = getHandler(config);
110
111        // Put this handler in the hash map so that we will be able to find
112        // it if it is deleted or disabled.
113        handlers.put(config.dn(), handler);
114      }
115    }
116  }
117
118  @Override
119  public ConfigChangeResult applyConfigurationDelete(
120       ExtendedOperationHandlerCfg configuration)
121  {
122    final ConfigChangeResult ccr = new ConfigChangeResult();
123    // See if the entry is registered as an extended operation handler.
124    // If so, deregister it and finalize the handler.
125    ExtendedOperationHandler handler = handlers.remove(configuration.dn());
126    if (handler != null)
127    {
128      handler.finalizeExtendedOperationHandler();
129    }
130    return ccr;
131  }
132
133  @Override
134  public boolean isConfigurationChangeAcceptable(
135       ExtendedOperationHandlerCfg configuration,
136       List<LocalizableMessage> unacceptableReasons)
137  {
138    return !configuration.isEnabled()
139        || isJavaClassAcceptable(configuration, unacceptableReasons);
140  }
141
142  @Override
143  public ConfigChangeResult applyConfigurationChange(
144       ExtendedOperationHandlerCfg configuration)
145  {
146    // Attempt to get the existing handler. This will only
147    // succeed if it was enabled.
148    DN dn = configuration.dn();
149    ExtendedOperationHandler handler = handlers.get(dn);
150
151    final ConfigChangeResult ccr = new ConfigChangeResult();
152
153    // See whether the handler should be enabled.
154    if (handler == null) {
155      if (configuration.isEnabled()) {
156        // The handler needs to be enabled.
157        try {
158          handler = getHandler(configuration);
159
160          // Put this handler in the hash so that we will
161          // be able to find it if it is altered.
162          handlers.put(dn, handler);
163        } catch (ConfigException e) {
164          logger.traceException(e);
165
166          ccr.addMessage(e.getMessageObject());
167          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
168        } catch (Exception e) {
169          logger.traceException(e);
170
171          ccr.addMessage(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get(
172              configuration.getJavaClass(), dn, stackTraceToSingleLineString(e)));
173          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
174        }
175      }
176    } else {
177      if (configuration.isEnabled()) {
178        // The handler is currently active, so we don't
179        // need to do anything. Changes to the class name cannot be
180        // applied dynamically, so if the class name did change then
181        // indicate that administrative action is required for that
182        // change to take effect.
183        String className = configuration.getJavaClass();
184        if (!className.equals(handler.getClass().getName())) {
185          ccr.setAdminActionRequired(true);
186        }
187      } else {
188        // We need to disable the connection handler.
189
190        handlers.remove(dn);
191
192        handler.finalizeExtendedOperationHandler();
193      }
194    }
195
196    return ccr;
197  }
198
199  @Override
200  public boolean isConfigurationAddAcceptable(
201       ExtendedOperationHandlerCfg configuration,
202       List<LocalizableMessage> unacceptableReasons)
203  {
204    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
205  }
206
207  @Override
208  public ConfigChangeResult applyConfigurationAdd(
209       ExtendedOperationHandlerCfg configuration)
210  {
211    final ConfigChangeResult ccr = new ConfigChangeResult();
212
213    // Register as a change listener for this connection handler entry
214    // so that we will be notified of any changes that may be made to
215    // it.
216    configuration.addChangeListener(this);
217
218    // Ignore this connection handler if it is disabled.
219    if (configuration.isEnabled())
220    {
221      // The connection handler needs to be enabled.
222      DN dn = configuration.dn();
223      try {
224        ExtendedOperationHandler handler = getHandler(configuration);
225
226        // Put this connection handler in the hash so that we will be
227        // able to find it if it is altered.
228        handlers.put(dn, handler);
229      }
230      catch (ConfigException e)
231      {
232        logger.traceException(e);
233
234        ccr.addMessage(e.getMessageObject());
235        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
236      }
237      catch (Exception e)
238      {
239        logger.traceException(e);
240
241        ccr.addMessage(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get(
242            configuration.getJavaClass(), dn, stackTraceToSingleLineString(e)));
243        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
244      }
245    }
246
247    return ccr;
248  }
249
250  @Override
251  public boolean isConfigurationDeleteAcceptable(
252       ExtendedOperationHandlerCfg configuration,
253       List<LocalizableMessage> unacceptableReasons)
254  {
255    // A delete should always be acceptable, so just return true.
256    return true;
257  }
258
259  /** Load and initialize the handler named in the config. */
260  private ExtendedOperationHandler getHandler(
261      ExtendedOperationHandlerCfg config) throws ConfigException
262  {
263    String className = config.getJavaClass();
264    ExtendedOperationHandlerCfgDefn d =
265        ExtendedOperationHandlerCfgDefn.getInstance();
266    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
267
268    try
269    {
270      Class<? extends ExtendedOperationHandler> theClass =
271          pd.loadClass(className, ExtendedOperationHandler.class);
272      ExtendedOperationHandler extendedOperationHandler = theClass.newInstance();
273
274      extendedOperationHandler.initializeExtendedOperationHandler(config);
275
276      return extendedOperationHandler;
277    }
278    catch (Exception e)
279    {
280      logger.traceException(e);
281      throw new ConfigException(ERR_CONFIG_EXTOP_INVALID_CLASS.get(className, config.dn(), e), e);
282    }
283  }
284
285  /** Determines whether the new configuration's implementation class is acceptable. */
286  private boolean isJavaClassAcceptable(ExtendedOperationHandlerCfg config,
287                                        List<LocalizableMessage> unacceptableReasons)
288  {
289    String className = config.getJavaClass();
290    ExtendedOperationHandlerCfgDefn d =
291        ExtendedOperationHandlerCfgDefn.getInstance();
292    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
293
294    try {
295      Class<? extends ExtendedOperationHandler> theClass =
296          pd.loadClass(className, ExtendedOperationHandler.class);
297      ExtendedOperationHandler extOpHandler = theClass.newInstance();
298
299      return extOpHandler.isConfigurationAcceptable(config, unacceptableReasons);
300    }
301    catch (Exception e)
302    {
303      logger.traceException(e);
304      unacceptableReasons.add(ERR_CONFIG_EXTOP_INVALID_CLASS.get(className, config.dn(), e));
305      return false;
306    }
307  }
308}