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