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