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-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.core;
028
029import static org.opends.messages.ConfigMessages.*;
030import static org.opends.messages.CoreMessages.*;
031import static org.opends.server.util.StaticUtils.*;
032
033import java.util.List;
034import java.util.Map;
035import java.util.concurrent.ConcurrentHashMap;
036
037import org.forgerock.i18n.LocalizableMessage;
038import org.forgerock.i18n.slf4j.LocalizedLogger;
039import org.forgerock.opendj.config.server.ConfigException;
040import org.opends.server.admin.AdministrationConnector;
041import org.opends.server.admin.ClassPropertyDefinition;
042import org.opends.server.admin.server.ConfigurationAddListener;
043import org.opends.server.admin.server.ConfigurationChangeListener;
044import org.opends.server.admin.server.ConfigurationDeleteListener;
045import org.opends.server.admin.server.ServerManagementContext;
046import org.opends.server.admin.std.meta.ConnectionHandlerCfgDefn;
047import org.opends.server.admin.std.server.AdministrationConnectorCfg;
048import org.opends.server.admin.std.server.ConnectionHandlerCfg;
049import org.opends.server.admin.std.server.RootCfg;
050import org.opends.server.api.ConnectionHandler;
051import org.opends.server.protocols.ldap.LDAPConnectionHandler;
052import org.forgerock.opendj.config.server.ConfigChangeResult;
053import org.opends.server.types.DN;
054import org.opends.server.types.InitializationException;
055
056/**
057 * This class defines a utility that will be used to manage the
058 * configuration for the set of connection handlers defined in the
059 * Directory Server. It will perform the necessary initialization of
060 * those connection handlers when the server is first started, and
061 * then will manage any changes to them while the server is running.
062 */
063public class ConnectionHandlerConfigManager implements
064    ConfigurationAddListener<ConnectionHandlerCfg>,
065    ConfigurationDeleteListener<ConnectionHandlerCfg>,
066    ConfigurationChangeListener<ConnectionHandlerCfg> {
067  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068
069
070  /**
071   * The mapping between configuration entry DNs and their corresponding
072   * connection handler implementations.
073   */
074  private final Map<DN, ConnectionHandler<?>> connectionHandlers;
075
076  private final ServerContext serverContext;
077
078  /**
079   * Creates a new instance of this connection handler config manager.
080   *
081   * @param serverContext
082   *            The server context.
083   */
084  public ConnectionHandlerConfigManager(ServerContext serverContext) {
085    this.serverContext = serverContext;
086    connectionHandlers = new ConcurrentHashMap<>();
087  }
088
089  /** {@inheritDoc} */
090  @Override
091  public ConfigChangeResult applyConfigurationAdd(
092      ConnectionHandlerCfg configuration) {
093    final ConfigChangeResult ccr = new ConfigChangeResult();
094
095    // Register as a change listener for this connection handler entry
096    // so that we will be notified of any changes that may be made to it.
097    configuration.addChangeListener(this);
098
099    // Ignore this connection handler if it is disabled.
100    if (configuration.isEnabled()) {
101      // The connection handler needs to be enabled.
102      DN dn = configuration.dn();
103      try {
104        // Attempt to start the connection handler.
105        ConnectionHandler<? extends ConnectionHandlerCfg> connectionHandler =
106          getConnectionHandler(configuration);
107        connectionHandler.start();
108
109        // Put this connection handler in the hash so that we will be
110        // able to find it if it is altered.
111        connectionHandlers.put(dn, connectionHandler);
112
113        // Register the connection handler with the Directory Server.
114        DirectoryServer.registerConnectionHandler(connectionHandler);
115      } catch (ConfigException e) {
116        logger.traceException(e);
117
118        ccr.addMessage(e.getMessageObject());
119        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
120      } catch (Exception e) {
121        logger.traceException(e);
122        ccr.addMessage(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
123            configuration.getJavaClass(), dn, stackTraceToSingleLineString(e)));
124        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
125      }
126    }
127
128    return ccr;
129  }
130
131
132
133  /** {@inheritDoc} */
134  @Override
135  public ConfigChangeResult applyConfigurationChange(
136      ConnectionHandlerCfg configuration) {
137    // Attempt to get the existing connection handler. This will only
138    // succeed if it was enabled.
139    DN dn = configuration.dn();
140    ConnectionHandler<?> connectionHandler = connectionHandlers.get(dn);
141
142    final ConfigChangeResult ccr = new ConfigChangeResult();
143
144    // See whether the connection handler should be enabled.
145    if (connectionHandler == null) {
146      if (configuration.isEnabled()) {
147        // The connection handler needs to be enabled.
148        try {
149          // Attempt to start the connection handler.
150          connectionHandler = getConnectionHandler(configuration);
151          connectionHandler.start();
152
153          // Put this connection handler in the hash so that we will
154          // be able to find it if it is altered.
155          connectionHandlers.put(dn, connectionHandler);
156
157          // Register the connection handler with the Directory
158          // Server.
159          DirectoryServer.registerConnectionHandler(connectionHandler);
160        } catch (ConfigException e) {
161          logger.traceException(e);
162
163          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
164          ccr.addMessage(e.getMessageObject());
165        } catch (Exception e) {
166          logger.traceException(e);
167
168          ccr.addMessage(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
169              configuration.getJavaClass(), dn, stackTraceToSingleLineString(e)));
170          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
171        }
172      }
173    } else {
174      if (configuration.isEnabled()) {
175        // The connection handler is currently active, so we don't
176        // need to do anything. Changes to the class name cannot be
177        // applied dynamically, so if the class name did change then
178        // indicate that administrative action is required for that
179        // change to take effect.
180        String className = configuration.getJavaClass();
181        if (!className.equals(connectionHandler.getClass().getName())) {
182          ccr.setAdminActionRequired(true);
183        }
184      } else {
185        // We need to disable the connection handler.
186        DirectoryServer
187            .deregisterConnectionHandler(connectionHandler);
188        connectionHandlers.remove(dn);
189
190
191        connectionHandler.finalizeConnectionHandler(
192                INFO_CONNHANDLER_CLOSED_BY_DISABLE.get());
193      }
194    }
195
196    return ccr;
197  }
198
199
200
201  /** {@inheritDoc} */
202  @Override
203  public ConfigChangeResult applyConfigurationDelete(
204      ConnectionHandlerCfg configuration) {
205    final ConfigChangeResult ccr = new ConfigChangeResult();
206
207    // See if the entry is registered as a connection handler. If so,
208    // deregister and stop it. We'll try to leave any established
209    // connections alone if possible.
210    DN dn = configuration.dn();
211    ConnectionHandler<?> connectionHandler = connectionHandlers.get(dn);
212    if (connectionHandler != null) {
213      DirectoryServer.deregisterConnectionHandler(connectionHandler);
214      connectionHandlers.remove(dn);
215
216      connectionHandler.finalizeConnectionHandler(
217              INFO_CONNHANDLER_CLOSED_BY_DELETE.get());
218    }
219
220    return ccr;
221  }
222
223
224
225  /**
226   * Initializes the configuration associated with the Directory
227   * Server connection handlers. This should only be called at
228   * Directory Server startup.
229   *
230   * @throws ConfigException
231   *           If a critical configuration problem prevents the
232   *           connection handler initialization from succeeding.
233   * @throws InitializationException
234   *           If a problem occurs while initializing the connection
235   *           handlers that is not related to the server
236   *           configuration.
237   */
238  public void initializeConnectionHandlerConfig()
239      throws ConfigException, InitializationException {
240    // Clear the set of connection handlers in case of in-core restart.
241    connectionHandlers.clear();
242
243    // Initialize the admin connector.
244    initializeAdministrationConnectorConfig();
245
246    // Get the root configuration which acts as the parent of all
247    // connection handlers.
248    ServerManagementContext context = ServerManagementContext
249        .getInstance();
250    RootCfg root = context.getRootConfiguration();
251
252    // Register as an add and delete listener so that we can
253    // be notified if new connection handlers are added or existing
254    // connection handlers are removed.
255    root.addConnectionHandlerAddListener(this);
256    root.addConnectionHandlerDeleteListener(this);
257
258    // Initialize existing connection handles.
259    for (String name : root.listConnectionHandlers()) {
260      ConnectionHandlerCfg config = root
261          .getConnectionHandler(name);
262
263      // Register as a change listener for this connection handler
264      // entry so that we will be notified of any changes that may be
265      // made to it.
266      config.addChangeListener(this);
267
268      // Ignore this connection handler if it is disabled.
269      if (config.isEnabled()) {
270        // Note that we don't want to start the connection handler
271        // because we're still in the startup process. Therefore, we
272        // will not do so and allow the server to start it at the very
273        // end of the initialization process.
274        ConnectionHandler<? extends ConnectionHandlerCfg> connectionHandler =
275             getConnectionHandler(config);
276
277        // Put this connection handler in the hash so that we will be
278        // able to find it if it is altered.
279        connectionHandlers.put(config.dn(), connectionHandler);
280
281        // Register the connection handler with the Directory Server.
282        DirectoryServer.registerConnectionHandler(connectionHandler);
283      }
284    }
285  }
286
287
288
289  private void initializeAdministrationConnectorConfig()
290    throws ConfigException, InitializationException {
291
292    RootCfg root =
293      ServerManagementContext.getInstance().getRootConfiguration();
294    AdministrationConnectorCfg administrationConnectorCfg =
295      root.getAdministrationConnector();
296
297    AdministrationConnector ac = new AdministrationConnector(serverContext);
298    ac.initializeAdministrationConnector(administrationConnectorCfg);
299
300    // Put this connection handler in the hash so that we will be
301    // able to find it if it is altered.
302    LDAPConnectionHandler connectionHandler = ac.getConnectionHandler();
303    connectionHandlers.put(administrationConnectorCfg.dn(), connectionHandler);
304
305    // Register the connection handler with the Directory Server.
306    DirectoryServer.registerConnectionHandler(connectionHandler);
307  }
308
309
310  /** {@inheritDoc} */
311  @Override
312  public boolean isConfigurationAddAcceptable(
313      ConnectionHandlerCfg configuration,
314      List<LocalizableMessage> unacceptableReasons) {
315    return !configuration.isEnabled()
316        || isJavaClassAcceptable(configuration, unacceptableReasons);
317  }
318
319
320
321  /** {@inheritDoc} */
322  @Override
323  public boolean isConfigurationChangeAcceptable(
324      ConnectionHandlerCfg configuration,
325      List<LocalizableMessage> unacceptableReasons) {
326    return !configuration.isEnabled()
327        || isJavaClassAcceptable(configuration, unacceptableReasons);
328  }
329
330
331
332  /** {@inheritDoc} */
333  @Override
334  public boolean isConfigurationDeleteAcceptable(
335      ConnectionHandlerCfg configuration,
336      List<LocalizableMessage> unacceptableReasons) {
337    // A delete should always be acceptable, so just return true.
338    return true;
339  }
340
341
342
343  /** Load and initialize the connection handler named in the config. */
344  private <T extends ConnectionHandlerCfg> ConnectionHandler<T> getConnectionHandler(
345      T config) throws ConfigException
346  {
347    String className = config.getJavaClass();
348    ConnectionHandlerCfgDefn d = ConnectionHandlerCfgDefn.getInstance();
349    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
350
351    try {
352      @SuppressWarnings("rawtypes")
353      Class<? extends ConnectionHandler> theClass =
354          pd.loadClass(className, ConnectionHandler.class);
355      ConnectionHandler<T> connectionHandler = theClass.newInstance();
356
357      connectionHandler.initializeConnectionHandler(serverContext, config);
358
359      return connectionHandler;
360    } catch (Exception e) {
361      logger.traceException(e);
362
363      LocalizableMessage message = ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
364          className, config.dn(), stackTraceToSingleLineString(e));
365      throw new ConfigException(message, e);
366    }
367  }
368
369
370
371  /**
372   * Determines whether or not the new configuration's implementation
373   * class is acceptable.
374   */
375  private boolean isJavaClassAcceptable(
376      ConnectionHandlerCfg config,
377      List<LocalizableMessage> unacceptableReasons) {
378    String className = config.getJavaClass();
379    ConnectionHandlerCfgDefn d = ConnectionHandlerCfgDefn.getInstance();
380    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
381
382    try {
383      ConnectionHandler<?> connectionHandler = connectionHandlers.get(config.dn());
384      if (connectionHandler == null) {
385        @SuppressWarnings("rawtypes")
386        Class<? extends ConnectionHandler> theClass =
387            pd.loadClass(className, ConnectionHandler.class);
388        connectionHandler = theClass.newInstance();
389      }
390
391      return connectionHandler.isConfigurationAcceptable(config, unacceptableReasons);
392    } catch (Exception e) {
393      logger.traceException(e);
394
395      unacceptableReasons.add(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
396          className, config.dn(), stackTraceToSingleLineString(e)));
397      return false;
398    }
399  }
400}