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