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