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.protocols.jmx;
028    import java.io.IOException;
029    import org.opends.messages.Message;
030    
031    
032    
033    import static org.opends.server.loggers.ErrorLogger.logError;
034    import static org.opends.messages.ProtocolMessages.*;
035    
036    import static org.opends.server.util.StaticUtils.*;
037    
038    import java.net.InetSocketAddress;
039    import java.util.ArrayList;
040    import java.util.Collection;
041    import java.util.LinkedHashMap;
042    import java.util.LinkedList;
043    import java.util.List;
044    
045    import org.opends.server.admin.server.ConfigurationChangeListener;
046    import org.opends.server.admin.std.server.ConnectionHandlerCfg;
047    import org.opends.server.admin.std.server.JMXConnectionHandlerCfg;
048    import org.opends.server.api.AlertGenerator;
049    import org.opends.server.api.ClientConnection;
050    import org.opends.server.api.ConnectionHandler;
051    import org.opends.server.api.ServerShutdownListener;
052    import org.opends.server.config.ConfigException;
053    import org.opends.server.core.DirectoryServer;
054    import org.opends.server.types.ConfigChangeResult;
055    import org.opends.server.types.DN;
056    
057    
058    import org.opends.server.types.HostPort;
059    import org.opends.server.types.InitializationException;
060    import org.opends.server.types.ResultCode;
061    import org.opends.server.util.StaticUtils;
062    
063    
064    
065    /**
066     * This class defines a connection handler that will be used for
067     * communicating with administrative clients over JMX. The connection
068     * handler is responsible for accepting new connections, reading
069     * requests from the clients and parsing them as operations. A single
070     * request handler should be used.
071     */
072    public final class JmxConnectionHandler extends
073        ConnectionHandler<JMXConnectionHandlerCfg> implements
074        ServerShutdownListener, AlertGenerator,
075        ConfigurationChangeListener<JMXConnectionHandlerCfg> {
076    
077      /**
078       * Key that may be placed into a JMX connection environment map to
079       * provide a custom <code>javax.net.ssl.TrustManager</code> array
080       * for a connection.
081       */
082      public static final String TRUST_MANAGER_ARRAY_KEY =
083        "org.opends.server.protocol.jmx.ssl.trust.manager.array";
084    
085      // The fully-qualified name of this class.
086      private static final String CLASS_NAME =
087        "org.opends.server.protocols.jmx.JMXConnectionHandler";
088    
089      // The list of active client connection.
090      private LinkedList<ClientConnection> connectionList;
091    
092      // The current configuration state.
093      private JMXConnectionHandlerCfg currentConfig;
094    
095      // The JMX RMI Connector associated with the Connection handler.
096      private RmiConnector rmiConnector;
097    
098      // The unique name for this connection handler.
099      private String connectionHandlerName;
100    
101      // The protocol used to communicate with clients.
102      private String protocol;
103    
104      // The set of listeners for this connection handler.
105      private LinkedList<HostPort> listeners = new LinkedList<HostPort>();
106    
107      /**
108       * Creates a new instance of this JMX connection handler. It must be
109       * initialized before it may be used.
110       */
111      public JmxConnectionHandler() {
112        super("JMX Connection Handler Thread");
113    
114        this.connectionList = new LinkedList<ClientConnection>();
115      }
116    
117    
118    
119      /**
120       * {@inheritDoc}
121       */
122      public ConfigChangeResult applyConfigurationChange(
123          JMXConnectionHandlerCfg config) {
124        // Create variables to include in the response.
125        ResultCode resultCode = ResultCode.SUCCESS;
126        ArrayList<Message> messages = new ArrayList<Message>();
127    
128        // Determine whether or not the RMI connection needs restarting.
129        boolean rmiConnectorRestart = false;
130        boolean portChanged = false;
131    
132        if (currentConfig.getListenPort() != config.getListenPort()) {
133          rmiConnectorRestart = true;
134          portChanged = true;
135        }
136    
137        if (currentConfig.isUseSSL() != config.isUseSSL()) {
138          rmiConnectorRestart = true;
139        }
140    
141        if (((currentConfig.getSSLCertNickname() != null) &&
142              !currentConfig.getSSLCertNickname().equals(
143              config.getSSLCertNickname())) ||
144            ((config.getSSLCertNickname() != null) &&
145              !config.getSSLCertNickname().equals(
146              currentConfig.getSSLCertNickname()))) {
147          rmiConnectorRestart = true;
148        }
149    
150        // Save the configuration.
151        currentConfig = config;
152    
153        // Restart the connector if required.
154        if (rmiConnectorRestart) {
155          if (config.isUseSSL()) {
156            protocol = "JMX+SSL";
157          } else {
158            protocol = "JMX";
159          }
160    
161          listeners.clear();
162          listeners.add(new HostPort(config.getListenPort()));
163    
164          rmiConnector.finalizeConnectionHandler(true, portChanged);
165          try
166          {
167            rmiConnector.initialize();
168          }
169          catch (RuntimeException e)
170          {
171            resultCode = DirectoryServer.getServerErrorResultCode();
172            messages.add(Message.raw(e.getMessage()));
173          }
174        }
175    
176        // Return configuration result.
177        return new ConfigChangeResult(resultCode, false, messages);
178      }
179    
180    
181    
182      /**
183       * Closes this connection handler so that it will no longer accept
184       * new client connections. It may or may not disconnect existing
185       * client connections based on the provided flag.
186       *
187       * @param finalizeReason
188       *          The reason that this connection handler should be
189       *          finalized.
190       * @param closeConnections
191       *          Indicates whether any established client connections
192       *          associated with the connection handler should also be
193       *          closed.
194       */
195      public void finalizeConnectionHandler(Message finalizeReason,
196          boolean closeConnections) {
197        // Make sure that we don't get notified of any more changes.
198        currentConfig.removeJMXChangeListener(this);
199    
200        // We should also close the RMI registry.
201        rmiConnector.finalizeConnectionHandler(closeConnections, true);
202      }
203    
204    
205    
206      /**
207       * Retrieves information about the set of alerts that this generator
208       * may produce. The map returned should be between the notification
209       * type for a particular notification and the human-readable
210       * description for that notification. This alert generator must not
211       * generate any alerts with types that are not contained in this
212       * list.
213       *
214       * @return Information about the set of alerts that this generator
215       *         may produce.
216       */
217      public LinkedHashMap<String, String> getAlerts() {
218        LinkedHashMap<String, String> alerts = new LinkedHashMap<String, String>();
219    
220        return alerts;
221      }
222    
223    
224    
225      /**
226       * Retrieves the fully-qualified name of the Java class for this
227       * alert generator implementation.
228       *
229       * @return The fully-qualified name of the Java class for this alert
230       *         generator implementation.
231       */
232      public String getClassName() {
233        return CLASS_NAME;
234      }
235    
236    
237    
238      /**
239       * Retrieves the set of active client connections that have been
240       * established through this connection handler.
241       *
242       * @return The set of active client connections that have been
243       *         established through this connection handler.
244       */
245      public Collection<ClientConnection> getClientConnections() {
246        return connectionList;
247      }
248    
249    
250    
251      /**
252       * Retrieves the DN of the configuration entry with which this alert
253       * generator is associated.
254       *
255       * @return The DN of the configuration entry with which this alert
256       *         generator is associated.
257       */
258      public DN getComponentEntryDN() {
259        return currentConfig.dn();
260      }
261    
262    
263    
264      /**
265       * Retrieves the DN of the key manager provider that should be used
266       * for operations associated with this connection handler which need
267       * access to a key manager.
268       *
269       * @return The DN of the key manager provider that should be used
270       *         for operations associated with this connection handler
271       *         which need access to a key manager, or {@code null} if no
272       *         key manager provider has been configured for this
273       *         connection handler.
274       */
275      public DN getKeyManagerProviderDN() {
276        return currentConfig.getKeyManagerProviderDN();
277      }
278    
279    
280    
281      /**
282       * Get the JMX connection handler's listen port.
283       *
284       * @return Returns the JMX connection handler's listen port.
285       */
286      public int getListenPort() {
287        return currentConfig.getListenPort();
288      }
289    
290    
291    
292      /**
293       * Get the JMX connection handler's RMI connector.
294       *
295       * @return Returns the JMX connection handler's RMI connector.
296       */
297      public RmiConnector getRMIConnector() {
298        return rmiConnector;
299      }
300    
301    
302    
303      /**
304       * {@inheritDoc}
305       */
306      public String getShutdownListenerName() {
307        return connectionHandlerName;
308      }
309    
310    
311    
312      /**
313       * Retrieves the nickname of the server certificate that should be
314       * used in conjunction with this JMX connection handler.
315       *
316       * @return The nickname of the server certificate that should be
317       *         used in conjunction with this JMX connection handler.
318       */
319      public String getSSLServerCertNickname() {
320        return currentConfig.getSSLCertNickname();
321      }
322    
323    
324    
325      /**
326       * {@inheritDoc}
327       */
328      public void initializeConnectionHandler(JMXConnectionHandlerCfg config)
329             throws ConfigException, InitializationException
330      {
331        // Configuration is ok.
332        currentConfig = config;
333    
334        // Attempt to bind to the listen port to verify whether the connection
335        // handler will be able to start.
336        try
337        {
338          if (StaticUtils.isAddressInUse(
339            new InetSocketAddress(config.getListenPort()).getAddress(),
340            config.getListenPort(), true)) {
341            throw new IOException(
342              ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
343          }
344        }
345        catch (Exception e)
346        {
347          Message message = ERR_JMX_CONNHANDLER_CANNOT_BIND.
348              get(String.valueOf(config.dn()), config.getListenPort(),
349                  getExceptionMessage(e));
350          logError(message);
351          throw new InitializationException(message);
352        }
353    
354        if (config.isUseSSL()) {
355          protocol = "JMX+SSL";
356        } else {
357          protocol = "JMX";
358        }
359    
360        listeners.clear();
361        listeners.add(new HostPort("0.0.0.0", config.getListenPort()));
362        connectionHandlerName = "JMX Connection Handler " + config.getListenPort();
363    
364        // Create the associated RMI Connector.
365        rmiConnector = new RmiConnector(DirectoryServer.getJMXMBeanServer(), this);
366    
367        // Register this as a change listener.
368        config.addJMXChangeListener(this);
369      }
370    
371    
372    
373      /**
374       * {@inheritDoc}
375       */
376      public String getConnectionHandlerName() {
377        return connectionHandlerName;
378      }
379    
380    
381    
382      /**
383       * {@inheritDoc}
384       */
385      public String getProtocol() {
386        return protocol;
387      }
388    
389    
390    
391      /**
392       * {@inheritDoc}
393       */
394      public Collection<HostPort> getListeners() {
395        return listeners;
396      }
397    
398    
399    
400      /**
401       * {@inheritDoc}
402       */
403      @Override()
404      public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
405                                               List<Message> unacceptableReasons)
406      {
407        JMXConnectionHandlerCfg config = (JMXConnectionHandlerCfg) configuration;
408    
409        if ((currentConfig == null) ||
410            (!currentConfig.isEnabled() && config.isEnabled()) ||
411            (currentConfig.getListenPort() != config.getListenPort())) {
412          // Attempt to bind to the listen port to verify whether the connection
413          // handler will be able to start.
414          try {
415            if (StaticUtils.isAddressInUse(
416              new InetSocketAddress(config.getListenPort()).getAddress(),
417              config.getListenPort(), true)) {
418              throw new IOException(
419                ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
420            }
421          } catch (Exception e) {
422            Message message = ERR_JMX_CONNHANDLER_CANNOT_BIND.get(
423              String.valueOf(config.dn()), config.getListenPort(),
424              getExceptionMessage(e));
425            unacceptableReasons.add(message);
426            return false;
427          }
428        }
429    
430        return isConfigurationChangeAcceptable(config, unacceptableReasons);
431      }
432    
433    
434    
435      /**
436       * {@inheritDoc}
437       */
438      public boolean isConfigurationChangeAcceptable(
439          JMXConnectionHandlerCfg config,
440          List<Message> unacceptableReasons) {
441        // All validation is performed by the admin framework.
442        return true;
443      }
444    
445    
446    
447      /**
448       * Determines whether or not clients are allowed to connect over JMX
449       * using SSL.
450       *
451       * @return Returns <code>true</code> if clients are allowed to
452       *         connect over JMX using SSL.
453       */
454      public boolean isUseSSL() {
455        return currentConfig.isUseSSL();
456      }
457    
458    
459    
460      /**
461       * {@inheritDoc}
462       */
463      public void processServerShutdown(Message reason) {
464        // We should also close the RMI registry.
465        rmiConnector.finalizeConnectionHandler(true, true);
466      }
467    
468    
469    
470      /**
471       * Registers a client connection with this JMX connection handler.
472       *
473       * @param connection
474       *          The client connection.
475       */
476      public void registerClientConnection(ClientConnection connection) {
477        connectionList.add(connection);
478      }
479    
480    
481    
482      /**
483       * {@inheritDoc}
484       */
485      public void run() {
486        try
487        {
488          rmiConnector.initialize();
489        }
490        catch (RuntimeException e)
491        {
492        }
493      }
494    
495    
496    
497      /**
498       * {@inheritDoc}
499       */
500      public void toString(StringBuilder buffer) {
501        buffer.append(connectionHandlerName);
502      }
503    }