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 }