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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import static org.opends.server.loggers.debug.DebugLogger.*;
033    import org.opends.server.loggers.debug.DebugTracer;
034    import static org.opends.messages.ConfigMessages.*;
035    import static org.opends.messages.CoreMessages.*;
036    
037    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
038    
039    import java.lang.reflect.Method;
040    import java.util.ArrayList;
041    import java.util.List;
042    import java.util.concurrent.ConcurrentHashMap;
043    
044    import org.opends.server.admin.ClassPropertyDefinition;
045    import org.opends.server.admin.server.ConfigurationAddListener;
046    import org.opends.server.admin.server.ConfigurationChangeListener;
047    import org.opends.server.admin.server.ConfigurationDeleteListener;
048    import org.opends.server.admin.server.ServerManagementContext;
049    import org.opends.server.admin.std.meta.*;
050    import org.opends.server.admin.std.server.ConnectionHandlerCfg;
051    import org.opends.server.admin.std.server.RootCfg;
052    import org.opends.server.api.ConnectionHandler;
053    import org.opends.server.config.ConfigException;
054    import org.opends.server.types.ConfigChangeResult;
055    import org.opends.server.types.DN;
056    import org.opends.server.types.DebugLogLevel;
057    import org.opends.server.types.InitializationException;
058    import org.opends.server.types.ResultCode;
059    
060    
061    /**
062     * This class defines a utility that will be used to manage the
063     * configuration for the set of connection handlers defined in the
064     * Directory Server. It will perform the necessary initialization of
065     * those connection handlers when the server is first started, and
066     * then will manage any changes to them while the server is running.
067     */
068    public class ConnectionHandlerConfigManager implements
069        ConfigurationAddListener<ConnectionHandlerCfg>,
070        ConfigurationDeleteListener<ConnectionHandlerCfg>,
071        ConfigurationChangeListener<ConnectionHandlerCfg> {
072      /**
073       * The tracer object for the debug logger.
074       */
075      private static final DebugTracer TRACER = getTracer();
076    
077    
078      // The mapping between configuration entry DNs and their
079      // corresponding connection handler implementations.
080      private ConcurrentHashMap<DN, ConnectionHandler> connectionHandlers;
081    
082    
083    
084      /**
085       * Creates a new instance of this connection handler config manager.
086       */
087      public ConnectionHandlerConfigManager() {
088        // No implementation is required.
089      }
090    
091    
092    
093      /**
094       * {@inheritDoc}
095       */
096      public ConfigChangeResult applyConfigurationAdd(
097          ConnectionHandlerCfg configuration) {
098        // Default result code.
099        ResultCode resultCode = ResultCode.SUCCESS;
100        boolean adminActionRequired = false;
101        ArrayList<Message> messages = new ArrayList<Message>();
102    
103        // Register as a change listener for this connection handler entry
104        // so that we will be notified of any changes that may be made to
105        // it.
106        configuration.addChangeListener(this);
107    
108        // Ignore this connection handler if it is disabled.
109        if (configuration.isEnabled()) {
110          // The connection handler needs to be enabled.
111          DN dn = configuration.dn();
112          try {
113            // Attempt to start the connection handler.
114            ConnectionHandler<? extends ConnectionHandlerCfg> connectionHandler =
115              getConnectionHandler(configuration);
116            connectionHandler.start();
117    
118            // Put this connection handler in the hash so that we will be
119            // able to find it if it is altered.
120            connectionHandlers.put(dn, connectionHandler);
121    
122            // Register the connection handler with the Directory Server.
123            DirectoryServer.registerConnectionHandler(connectionHandler);
124          } catch (ConfigException e) {
125            if (debugEnabled())
126            {
127              TRACER.debugCaught(DebugLogLevel.ERROR, e);
128            }
129    
130            messages.add(e.getMessageObject());
131            resultCode = DirectoryServer.getServerErrorResultCode();
132          } catch (Exception e) {
133            if (debugEnabled())
134            {
135              TRACER.debugCaught(DebugLogLevel.ERROR, e);
136            }
137    
138    
139            messages.add(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
140                    String.valueOf(configuration.getJavaClass()),
141                    String.valueOf(dn),
142                stackTraceToSingleLineString(e)));
143            resultCode = DirectoryServer.getServerErrorResultCode();
144          }
145        }
146    
147        // Return the configuration result.
148        return new ConfigChangeResult(resultCode, adminActionRequired,
149            messages);
150      }
151    
152    
153    
154      /**
155       * {@inheritDoc}
156       */
157      public ConfigChangeResult applyConfigurationChange(
158          ConnectionHandlerCfg configuration) {
159        // Attempt to get the existing connection handler. This will only
160        // succeed if it was enabled.
161        DN dn = configuration.dn();
162        ConnectionHandler connectionHandler = connectionHandlers.get(dn);
163    
164        // Default result code.
165        ResultCode resultCode = ResultCode.SUCCESS;
166        boolean adminActionRequired = false;
167        ArrayList<Message> messages = new ArrayList<Message>();
168    
169        // See whether the connection handler should be enabled.
170        if (connectionHandler == null) {
171          if (configuration.isEnabled()) {
172            // The connection handler needs to be enabled.
173            try {
174              // Attempt to start the connection handler.
175              connectionHandler = getConnectionHandler(configuration);
176              connectionHandler.start();
177    
178              // Put this connection handler in the hash so that we will
179              // be able to find it if it is altered.
180              connectionHandlers.put(dn, connectionHandler);
181    
182              // Register the connection handler with the Directory
183              // Server.
184              DirectoryServer.registerConnectionHandler(
185                   (ConnectionHandler<? extends ConnectionHandlerCfg>)
186                   connectionHandler);
187            } catch (ConfigException e) {
188              if (debugEnabled())
189              {
190                TRACER.debugCaught(DebugLogLevel.ERROR, e);
191              }
192    
193              messages.add(e.getMessageObject());
194              resultCode = DirectoryServer.getServerErrorResultCode();
195            } catch (Exception e) {
196              if (debugEnabled())
197              {
198                TRACER.debugCaught(DebugLogLevel.ERROR, e);
199              }
200    
201              messages.add(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
202                      String.valueOf(configuration
203                  .getJavaClass()), String.valueOf(dn),
204                  stackTraceToSingleLineString(e)));
205              resultCode = DirectoryServer.getServerErrorResultCode();
206            }
207          }
208        } else {
209          if (configuration.isEnabled()) {
210            // The connection handler is currently active, so we don't
211            // need to do anything. Changes to the class name cannot be
212            // applied dynamically, so if the class name did change then
213            // indicate that administrative action is required for that
214            // change to take effect.
215            String className = configuration.getJavaClass();
216            if (!className.equals(connectionHandler.getClass().getName())) {
217              adminActionRequired = true;
218            }
219          } else {
220            // We need to disable the connection handler.
221            DirectoryServer
222                .deregisterConnectionHandler(connectionHandler);
223            connectionHandlers.remove(dn);
224    
225    
226            connectionHandler.finalizeConnectionHandler(
227                    INFO_CONNHANDLER_CLOSED_BY_DISABLE.get(), false);
228          }
229        }
230    
231        // Return the configuration result.
232        return new ConfigChangeResult(resultCode, adminActionRequired,
233            messages);
234      }
235    
236    
237    
238      /**
239       * {@inheritDoc}
240       */
241      public ConfigChangeResult applyConfigurationDelete(
242          ConnectionHandlerCfg configuration) {
243    
244        // Default result code.
245        ResultCode resultCode = ResultCode.SUCCESS;
246        boolean adminActionRequired = false;
247    
248        // See if the entry is registered as a connection handler. If so,
249        // deregister and stop it. We'll try to leave any established
250        // connections alone if possible.
251        DN dn = configuration.dn();
252        ConnectionHandler connectionHandler = connectionHandlers.get(dn);
253        if (connectionHandler != null) {
254          DirectoryServer.deregisterConnectionHandler(connectionHandler);
255          connectionHandlers.remove(dn);
256    
257          connectionHandler.finalizeConnectionHandler(
258                  INFO_CONNHANDLER_CLOSED_BY_DELETE.get(),
259                  false);
260        }
261    
262        return new ConfigChangeResult(resultCode, adminActionRequired);
263      }
264    
265    
266    
267      /**
268       * Initializes the configuration associated with the Directory
269       * Server connection handlers. This should only be called at
270       * Directory Server startup.
271       *
272       * @throws ConfigException
273       *           If a critical configuration problem prevents the
274       *           connection handler initialization from succeeding.
275       * @throws InitializationException
276       *           If a problem occurs while initializing the connection
277       *           handlers that is not related to the server
278       *           configuration.
279       */
280      public void initializeConnectionHandlerConfig()
281          throws ConfigException, InitializationException {
282        connectionHandlers = new ConcurrentHashMap<DN, ConnectionHandler>();
283    
284        // Get the root configuration which acts as the parent of all
285        // connection handlers.
286        ServerManagementContext context = ServerManagementContext
287            .getInstance();
288        RootCfg root = context.getRootConfiguration();
289    
290        // Register as an add and delete listener so that we can
291        // be notified if new connection handlers are added or existing
292        // connection handlers are removed.
293        root.addConnectionHandlerAddListener(this);
294        root.addConnectionHandlerDeleteListener(this);
295    
296        // Initialize existing connection handles.
297        for (String name : root.listConnectionHandlers()) {
298          ConnectionHandlerCfg config = root
299              .getConnectionHandler(name);
300    
301          // Register as a change listener for this connection handler
302          // entry so that we will be notified of any changes that may be
303          // made to it.
304          config.addChangeListener(this);
305    
306          // Ignore this connection handler if it is disabled.
307          if (config.isEnabled()) {
308            // Note that we don't want to start the connection handler
309            // because we're still in the startup process. Therefore, we
310            // will not do so and allow the server to start it at the very
311            // end of the initialization process.
312            ConnectionHandler<? extends ConnectionHandlerCfg> connectionHandler =
313                 getConnectionHandler(config);
314    
315            // Put this connection handler in the hash so that we will be
316            // able to find it if it is altered.
317            connectionHandlers.put(config.dn(), connectionHandler);
318    
319            // Register the connection handler with the Directory Server.
320            DirectoryServer.registerConnectionHandler(connectionHandler);
321          }
322        }
323      }
324    
325    
326    
327      /**
328       * {@inheritDoc}
329       */
330      public boolean isConfigurationAddAcceptable(
331          ConnectionHandlerCfg configuration,
332          List<Message> unacceptableReasons) {
333        if (configuration.isEnabled()) {
334          // It's enabled so always validate the class.
335          return isJavaClassAcceptable(configuration, unacceptableReasons);
336        } else {
337          // It's disabled so ignore it.
338          return true;
339        }
340      }
341    
342    
343    
344      /**
345       * {@inheritDoc}
346       */
347      public boolean isConfigurationChangeAcceptable(
348          ConnectionHandlerCfg configuration,
349          List<Message> unacceptableReasons) {
350        if (configuration.isEnabled()) {
351          // It's enabled so always validate the class.
352          return isJavaClassAcceptable(configuration, unacceptableReasons);
353        } else {
354          // It's disabled so ignore it.
355          return true;
356        }
357      }
358    
359    
360    
361      /**
362       * {@inheritDoc}
363       */
364      public boolean isConfigurationDeleteAcceptable(
365          ConnectionHandlerCfg configuration,
366          List<Message> unacceptableReasons) {
367        // A delete should always be acceptable, so just return true.
368        return true;
369      }
370    
371    
372    
373      // Load and initialize the connection handler named in the config.
374      private ConnectionHandler<? extends ConnectionHandlerCfg>
375                   getConnectionHandler(ConnectionHandlerCfg config)
376              throws ConfigException
377      {
378        String className = config.getJavaClass();
379        ConnectionHandlerCfgDefn d =
380          ConnectionHandlerCfgDefn.getInstance();
381        ClassPropertyDefinition pd = d
382            .getJavaClassPropertyDefinition();
383    
384        // Load the class and cast it to a connection handler.
385        Class<? extends ConnectionHandler> theClass;
386        ConnectionHandler connectionHandler;
387    
388        try {
389          theClass = pd.loadClass(className, ConnectionHandler.class);
390          connectionHandler = theClass.newInstance();
391        } catch (Exception e) {
392          if (debugEnabled())
393          {
394            TRACER.debugCaught(DebugLogLevel.ERROR, e);
395          }
396    
397          Message message = ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.
398              get(String.valueOf(className), String.valueOf(config.dn()),
399                  stackTraceToSingleLineString(e));
400          throw new ConfigException(message, e);
401        }
402    
403        // Perform the necessary initialization for the connection
404        // handler.
405        try {
406          // Determine the initialization method to use: it must take a
407          // single parameter which is the exact type of the configuration
408          // object.
409          Method method = theClass.getMethod("initializeConnectionHandler", config
410              .configurationClass());
411    
412          method.invoke(connectionHandler, config);
413        } catch (Exception e) {
414          if (debugEnabled())
415          {
416            TRACER.debugCaught(DebugLogLevel.ERROR, e);
417          }
418    
419          Message message = ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.
420              get(String.valueOf(className), String.valueOf(config.dn()),
421                  stackTraceToSingleLineString(e));
422          throw new ConfigException(message, e);
423        }
424    
425        // The connection handler has been successfully initialized.
426        return (ConnectionHandler<? extends ConnectionHandlerCfg>)
427               connectionHandler;
428      }
429    
430    
431    
432      // Determines whether or not the new configuration's implementation
433      // class is acceptable.
434      private boolean isJavaClassAcceptable(
435          ConnectionHandlerCfg config,
436          List<Message> unacceptableReasons) {
437        String className = config.getJavaClass();
438        ConnectionHandlerCfgDefn d =
439          ConnectionHandlerCfgDefn.getInstance();
440        ClassPropertyDefinition pd = d
441            .getJavaClassPropertyDefinition();
442    
443        // Load the class and cast it to a connection handler.
444        ConnectionHandler connectionHandler = null;
445        Class<? extends ConnectionHandler> theClass;
446        try {
447          connectionHandler = connectionHandlers.get(config.dn());
448          theClass = pd.loadClass(className, ConnectionHandler.class);
449          if (connectionHandler == null) {
450            connectionHandler = theClass.newInstance();
451          }
452        } catch (Exception e) {
453          if (debugEnabled())
454          {
455            TRACER.debugCaught(DebugLogLevel.ERROR, e);
456          }
457    
458          unacceptableReasons.add(
459                  ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
460                          String.valueOf(className),
461                          String.valueOf(config.dn()),
462                          stackTraceToSingleLineString(e)));
463          return false;
464        }
465    
466        // Perform the necessary initialization for the connection
467        // handler.
468        try {
469          // Determine the initialization method to use: it must take a
470          // single parameter which is the exact type of the configuration
471          // object.
472          Method method = theClass.getMethod("isConfigurationAcceptable",
473                                             ConnectionHandlerCfg.class,
474                                             List.class);
475          Boolean acceptable = (Boolean) method.invoke(connectionHandler, config,
476                                                       unacceptableReasons);
477    
478          if (! acceptable)
479          {
480            return false;
481          }
482        } catch (Exception e) {
483          if (debugEnabled())
484          {
485            TRACER.debugCaught(DebugLogLevel.ERROR, e);
486          }
487    
488          unacceptableReasons.add(ERR_CONFIG_CONNHANDLER_CANNOT_INITIALIZE.get(
489                  String.valueOf(className), String.valueOf(config.dn()),
490                  stackTraceToSingleLineString(e)));
491          return false;
492        }
493    
494        // The class is valid as far as we can tell.
495        return true;
496      }
497    }