/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions copyright 2013 ForgeRock AS */ package org.opends.server.protocols.jmx; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.util.StaticUtils.*; import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.ConnectionHandlerCfg; import org.opends.server.admin.std.server.JMXConnectionHandlerCfg; import org.opends.server.api.AlertGenerator; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionHandler; import org.opends.server.api.ServerShutdownListener; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.HostPort; import org.opends.server.types.InitializationException; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * This class defines a connection handler that will be used for * communicating with administrative clients over JMX. The connection * handler is responsible for accepting new connections, reading * requests from the clients and parsing them as operations. A single * request handler should be used. */ public final class JmxConnectionHandler extends ConnectionHandler<JMXConnectionHandlerCfg> implements ServerShutdownListener, AlertGenerator, ConfigurationChangeListener<JMXConnectionHandlerCfg> { private static final String WILDCARD_ADDRESS = "0.0.0.0"; /** * Key that may be placed into a JMX connection environment map to * provide a custom <code>javax.net.ssl.TrustManager</code> array * for a connection. */ public static final String TRUST_MANAGER_ARRAY_KEY = "org.opends.server.protocol.jmx.ssl.trust.manager.array"; // The fully-qualified name of this class. private static final String CLASS_NAME = "org.opends.server.protocols.jmx.JMXConnectionHandler"; // The list of active client connection. private LinkedList<ClientConnection> connectionList; // The current configuration state. private JMXConnectionHandlerCfg currentConfig; // The JMX RMI Connector associated with the Connection handler. private RmiConnector rmiConnector; // The unique name for this connection handler. private String connectionHandlerName; // The protocol used to communicate with clients. private String protocol; // The set of listeners for this connection handler. private LinkedList<HostPort> listeners = new LinkedList<HostPort>(); /** * Creates a new instance of this JMX connection handler. It must be * initialized before it may be used. */ public JmxConnectionHandler() { super("JMX Connection Handler Thread"); this.connectionList = new LinkedList<ClientConnection>(); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( JMXConnectionHandlerCfg config) { // Create variables to include in the response. ResultCode resultCode = ResultCode.SUCCESS; ArrayList<Message> messages = new ArrayList<Message>(); // Determine whether or not the RMI connection needs restarting. boolean rmiConnectorRestart = false; boolean portChanged = false; if (currentConfig.getListenPort() != config.getListenPort()) { rmiConnectorRestart = true; portChanged = true; } if (currentConfig.isUseSSL() != config.isUseSSL()) { rmiConnectorRestart = true; } if (((currentConfig.getSSLCertNickname() != null) && !currentConfig.getSSLCertNickname().equals( config.getSSLCertNickname())) || ((config.getSSLCertNickname() != null) && !config.getSSLCertNickname().equals( currentConfig.getSSLCertNickname()))) { rmiConnectorRestart = true; } // Save the configuration. currentConfig = config; // Restart the connector if required. if (rmiConnectorRestart) { if (config.isUseSSL()) { protocol = "JMX+SSL"; } else { protocol = "JMX"; } listeners.clear(); listeners.add(new HostPort(config.getListenPort())); rmiConnector.finalizeConnectionHandler(portChanged); try { rmiConnector.initialize(); } catch (RuntimeException e) { resultCode = DirectoryServer.getServerErrorResultCode(); messages.add(Message.raw(e.getMessage())); } } // If the port number has changed then update the JMX port information // stored in the system properties. if (portChanged) { String key = protocol + "_port"; String value = String.valueOf(config.getListenPort()); System.clearProperty(key); System.setProperty(key, value); } // Return configuration result. return new ConfigChangeResult(resultCode, false, messages); } /** * {@inheritDoc} */ @Override public void finalizeConnectionHandler(Message finalizeReason) { // Make sure that we don't get notified of any more changes. currentConfig.removeJMXChangeListener(this); // We should also close the RMI registry. rmiConnector.finalizeConnectionHandler(true); } /** * Retrieves information about the set of alerts that this generator * may produce. The map returned should be between the notification * type for a particular notification and the human-readable * description for that notification. This alert generator must not * generate any alerts with types that are not contained in this * list. * * @return Information about the set of alerts that this generator * may produce. */ @Override public LinkedHashMap<String, String> getAlerts() { LinkedHashMap<String, String> alerts = new LinkedHashMap<String, String>(); return alerts; } /** * Retrieves the fully-qualified name of the Java class for this * alert generator implementation. * * @return The fully-qualified name of the Java class for this alert * generator implementation. */ @Override public String getClassName() { return CLASS_NAME; } /** * Retrieves the set of active client connections that have been * established through this connection handler. * * @return The set of active client connections that have been * established through this connection handler. */ @Override public Collection<ClientConnection> getClientConnections() { return connectionList; } /** * Retrieves the DN of the configuration entry with which this alert * generator is associated. * * @return The DN of the configuration entry with which this alert * generator is associated. */ @Override public DN getComponentEntryDN() { return currentConfig.dn(); } /** * Retrieves the DN of the key manager provider that should be used * for operations associated with this connection handler which need * access to a key manager. * * @return The DN of the key manager provider that should be used * for operations associated with this connection handler * which need access to a key manager, or {@code null} if no * key manager provider has been configured for this * connection handler. */ public DN getKeyManagerProviderDN() { return currentConfig.getKeyManagerProviderDN(); } /** * Get the JMX connection handler's listen port. * * @return Returns the JMX connection handler's listen port. */ public int getListenPort() { return currentConfig.getListenPort(); } /** * Get the JMX connection handler's RMI connector. * * @return Returns the JMX connection handler's RMI connector. */ public RmiConnector getRMIConnector() { return rmiConnector; } /** * {@inheritDoc} */ @Override public String getShutdownListenerName() { return connectionHandlerName; } /** * Retrieves the nickname of the server certificate that should be * used in conjunction with this JMX connection handler. * * @return The nickname of the server certificate that should be * used in conjunction with this JMX connection handler. */ public String getSSLServerCertNickname() { return currentConfig.getSSLCertNickname(); } /** * {@inheritDoc} */ @Override public void initializeConnectionHandler(JMXConnectionHandlerCfg config) throws ConfigException, InitializationException { // Configuration is ok. currentConfig = config; // Attempt to bind to the listen port to verify whether the connection // handler will be able to start. try { if (StaticUtils.isAddressInUse( new InetSocketAddress(config.getListenPort()).getAddress(), config.getListenPort(), true)) { throw new IOException( ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); } } catch (Exception e) { Message message = ERR_CONNHANDLER_CANNOT_BIND.get("JMX", String.valueOf(config.dn()), WILDCARD_ADDRESS, config.getListenPort(), getExceptionMessage(e)); logError(message); throw new InitializationException(message); } if (config.isUseSSL()) { protocol = "JMX+SSL"; } else { protocol = "JMX"; } listeners.clear(); listeners.add(new HostPort(WILDCARD_ADDRESS, config.getListenPort())); connectionHandlerName = "JMX Connection Handler " + config.getListenPort(); // Create a system property to store the JMX port the server is // listening to. This information can be displayed with jinfo. System.setProperty( protocol + "_port", String.valueOf(config.getListenPort())); // Create the associated RMI Connector. rmiConnector = new RmiConnector(DirectoryServer.getJMXMBeanServer(), this); // Register this as a change listener. config.addJMXChangeListener(this); } /** * {@inheritDoc} */ @Override public String getConnectionHandlerName() { return connectionHandlerName; } /** * {@inheritDoc} */ @Override public String getProtocol() { return protocol; } /** * {@inheritDoc} */ @Override public Collection<HostPort> getListeners() { return listeners; } /** * {@inheritDoc} */ @Override() public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, List<Message> unacceptableReasons) { JMXConnectionHandlerCfg config = (JMXConnectionHandlerCfg) configuration; if ((currentConfig == null) || (!currentConfig.isEnabled() && config.isEnabled()) || (currentConfig.getListenPort() != config.getListenPort())) { // Attempt to bind to the listen port to verify whether the connection // handler will be able to start. try { if (StaticUtils.isAddressInUse( new InetSocketAddress(config.getListenPort()).getAddress(), config.getListenPort(), true)) { throw new IOException( ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); } } catch (Exception e) { Message message = ERR_CONNHANDLER_CANNOT_BIND.get("JMX", String.valueOf(config.dn()), WILDCARD_ADDRESS, config.getListenPort(), getExceptionMessage(e)); unacceptableReasons.add(message); return false; } } return isConfigurationChangeAcceptable(config, unacceptableReasons); } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( JMXConnectionHandlerCfg config, List<Message> unacceptableReasons) { // All validation is performed by the admin framework. return true; } /** * Determines whether or not clients are allowed to connect over JMX * using SSL. * * @return Returns <code>true</code> if clients are allowed to * connect over JMX using SSL. */ public boolean isUseSSL() { return currentConfig.isUseSSL(); } /** * {@inheritDoc} */ @Override public void processServerShutdown(Message reason) { // We should also close the RMI registry. rmiConnector.finalizeConnectionHandler(true); } /** * Registers a client connection with this JMX connection handler. * * @param connection * The client connection. */ public void registerClientConnection(ClientConnection connection) { connectionList.add(connection); } /** * {@inheritDoc} */ @Override public void run() { try { rmiConnector.initialize(); } catch (RuntimeException e) { } } /** * {@inheritDoc} */ @Override public void toString(StringBuilder buffer) { buffer.append(connectionHandlerName); } }