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 }