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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.protocols.jmx; 028 029import java.net.InetAddress; 030import java.util.Collection; 031import java.util.LinkedList; 032import java.util.concurrent.atomic.AtomicInteger; 033import java.util.concurrent.atomic.AtomicLong; 034 035import javax.management.Notification; 036import javax.management.NotificationListener; 037import javax.management.remote.JMXConnectionNotification; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.i18n.LocalizableMessageBuilder; 041import org.forgerock.i18n.slf4j.LocalizedLogger; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.opends.server.api.ClientConnection; 044import org.opends.server.api.ConnectionHandler; 045import org.opends.server.core.*; 046import org.opends.server.protocols.internal.InternalSearchOperation; 047import org.opends.server.protocols.internal.SearchRequest; 048import org.opends.server.types.*; 049 050import static org.opends.messages.ProtocolMessages.*; 051 052/** 053 * This class defines the set of methods and structures that must be implemented 054 * by a Directory Server client connection. 055 */ 056public class JmxClientConnection 057 extends ClientConnection implements NotificationListener 058{ 059 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 060 061 /** The message ID counter to use for jmx connections. */ 062 private final AtomicInteger nextMessageID; 063 /** The operation ID counter to use for operations on this connection. */ 064 private final AtomicLong nextOperationID; 065 /** The empty operation list for this connection. */ 066 private final LinkedList<Operation> operationList; 067 /** The connection ID for this client connection. */ 068 private final long connectionID; 069 /** The JMX connection ID for this client connection. */ 070 protected String jmxConnectionID; 071 /** The reference to the connection handler that accepted this connection. */ 072 private final JmxConnectionHandler jmxConnectionHandler; 073 /** Indicate that the disconnect process is started. */ 074 private boolean disconnectStarted; 075 076 /** 077 * Creates a new Jmx client connection that will be authenticated as 078 * as the specified user. 079 * 080 * @param jmxConnectionHandler 081 * The connection handler on which we should be registered 082 * @param authInfo 083 * the User authentication info 084 */ 085 public JmxClientConnection(JmxConnectionHandler jmxConnectionHandler, 086 AuthenticationInfo authInfo) 087 { 088 super(); 089 090 nextMessageID = new AtomicInteger(1); 091 nextOperationID = new AtomicLong(0); 092 093 this.jmxConnectionHandler = jmxConnectionHandler; 094 jmxConnectionHandler.registerClientConnection(this); 095 096 setAuthenticationInfo(authInfo); 097 098 connectionID = DirectoryServer.newConnectionAccepted(this); 099 if (connectionID < 0) 100 { 101 disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, 102 ERR_CONNHANDLER_REJECTED_BY_SERVER.get()); 103 } 104 operationList = new LinkedList<>(); 105 106 // Register the Jmx Notification listener (this) 107 jmxConnectionHandler.getRMIConnector().jmxRmiConnectorNoClientCertificate 108 .addNotificationListener(this, null, null); 109 } 110 111 /** {@inheritDoc} */ 112 @Override 113 public void handleNotification(Notification notif, Object handback) 114 { 115 // We don't have the expected notification 116 if ( ! (notif instanceof JMXConnectionNotification)) 117 { 118 return ; 119 } 120 JMXConnectionNotification jcn = (JMXConnectionNotification) notif; 121 122 // The only handled notifications are CLOSED and FAILED 123 if (!JMXConnectionNotification.CLOSED.equals(jcn.getType()) 124 && !JMXConnectionNotification.FAILED.equals(jcn.getType())) 125 { 126 return; 127 } 128 129 // Check if the closed connection corresponds to the current connection 130 if (!jcn.getConnectionId().equals(jmxConnectionID)) 131 { 132 return; 133 } 134 135 // Ok, we can perform the unbind: call finalize 136 disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); 137 } 138 139 140 /** 141 * Retrieves the operation ID that should be used for the next Jmx 142 * operation. 143 * 144 * @return The operation ID that should be used for the next Jmx 145 * operation. 146 */ 147 public long nextOperationID() 148 { 149 long opID = nextOperationID.getAndIncrement(); 150 if (opID < 0) 151 { 152 synchronized (nextOperationID) 153 { 154 if (nextOperationID.get() < 0) 155 { 156 nextOperationID.set(1); 157 return 0; 158 } 159 else 160 { 161 return nextOperationID.getAndIncrement(); 162 } 163 } 164 } 165 166 return opID; 167 } 168 169 170 171 /** 172 * Retrieves the message ID that should be used for the next Jmx 173 * operation. 174 * 175 * @return The message ID that should be used for the next Jmx 176 * operation. 177 */ 178 public int nextMessageID() 179 { 180 int msgID = nextMessageID.getAndIncrement(); 181 if (msgID < 0) 182 { 183 synchronized (nextMessageID) 184 { 185 if (nextMessageID.get() < 0) 186 { 187 nextMessageID.set(2); 188 return 1; 189 } 190 else 191 { 192 return nextMessageID.getAndIncrement(); 193 } 194 } 195 } 196 197 return msgID; 198 } 199 200 201 202 /** 203 * Retrieves the unique identifier that has been assigned to this connection. 204 * 205 * @return The unique identifier that has been assigned to this connection. 206 */ 207 @Override 208 public long getConnectionID() 209 { 210 return connectionID; 211 } 212 213 /** 214 * Retrieves the connection handler that accepted this client connection. 215 * 216 * @return The connection handler that accepted this client connection. 217 */ 218 @Override 219 public ConnectionHandler<?> getConnectionHandler() 220 { 221 return jmxConnectionHandler; 222 } 223 224 /** 225 * Retrieves the protocol that the client is using to communicate with the 226 * Directory Server. 227 * 228 * @return The protocol that the client is using to communicate with the 229 * Directory Server. 230 */ 231 @Override 232 public String getProtocol() 233 { 234 return "jmx"; 235 } 236 237 238 239 /** 240 * Retrieves a string representation of the address of the client. 241 * 242 * @return A string representation of the address of the client. 243 */ 244 @Override 245 public String getClientAddress() 246 { 247 return "jmx"; 248 } 249 250 251 252 /** 253 * Retrieves the port number for this connection on the client system. 254 * 255 * @return The port number for this connection on the client system. 256 */ 257 @Override 258 public int getClientPort() 259 { 260 return -1; 261 } 262 263 264 265 /** 266 * Retrieves a string representation of the address on the server to which the 267 * client connected. 268 * 269 * @return A string representation of the address on the server to which the 270 * client connected. 271 */ 272 @Override 273 public String getServerAddress() 274 { 275 return "jmx"; 276 } 277 278 279 280 /** 281 * Retrieves the port number for this connection on the server 282 * system if available. 283 * 284 * @return The port number for this connection on the server system 285 * or -1 if there is no server port associated with this 286 * connection (e.g. internal client). 287 */ 288 @Override 289 public int getServerPort() 290 { 291 return -1; 292 } 293 294 295 296 /** 297 * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the remote 298 * client system. 299 * 300 * @return The <CODE>java.net.InetAddress</CODE> associated with the remote 301 * client system. It may be <CODE>null</CODE> if the client is not 302 * connected over an IP-based connection. 303 */ 304 @Override 305 public InetAddress getRemoteAddress() 306 { 307 return null; 308 } 309 310 311 312 /** 313 * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory Server 314 * system to which the client has established the connection. 315 * 316 * @return The <CODE>java.net.InetAddress</CODE> for the Directory Server 317 * system to which the client has established the connection. It may 318 * be <CODE>null</CODE> if the client is not connected over an 319 * IP-based connection. 320 */ 321 @Override 322 public InetAddress getLocalAddress() 323 { 324 return null; 325 } 326 327 /** {@inheritDoc} */ 328 @Override 329 public boolean isConnectionValid() 330 { 331 return !disconnectStarted; 332 } 333 334 /** 335 * Indicates whether this client connection is currently using a secure 336 * mechanism to communicate with the server. Note that this may change over 337 * time based on operations performed by the client or server (e.g., it may go 338 * from <CODE>false</CODE> to <CODE>true</CODE> if the client uses the 339 * StartTLS extended operation). 340 * 341 * @return <CODE>true</CODE> if the client connection is currently using a 342 * secure mechanism to communicate with the server, or 343 * <CODE>false</CODE> if not. 344 */ 345 @Override 346 public boolean isSecure() 347 { 348 return false; 349 } 350 351 352 /** 353 * Retrieves the human-readable name of the security mechanism that is used to 354 * protect communication with this client. 355 * 356 * @return The human-readable name of the security mechanism that is used to 357 * protect communication with this client, or <CODE>null</CODE> if no 358 * security is in place. 359 */ 360 public String getSecurityMechanism() 361 { 362 return "NULL"; 363 } 364 365 366 367 /** 368 * Sends a response to the client based on the information in the provided 369 * operation. 370 * 371 * @param operation The operation for which to send the response. 372 */ 373 @Override 374 public void sendResponse(Operation operation) 375 { 376 // There will not be any response sent by this method, since there is not an 377 // actual connection. 378 } 379 380 381 /** 382 * Processes an Jmx search operation with the provided information. 383 * 384 * @param request The search request. 385 * @return A reference to the internal search operation that was processed 386 * and contains information about the result of the processing as 387 * well as lists of the matching entries and search references. 388 */ 389 public InternalSearchOperation processSearch(SearchRequest request) 390 { 391 InternalSearchOperation searchOperation = 392 new InternalSearchOperation(this, nextOperationID(), nextMessageID(), request); 393 394 if (! hasPrivilege(Privilege.JMX_READ, null)) 395 { 396 LocalizableMessage message = ERR_JMX_SEARCH_INSUFFICIENT_PRIVILEGES.get(); 397 searchOperation.setErrorMessage(new LocalizableMessageBuilder(message)); 398 searchOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ; 399 } 400 else 401 { 402 searchOperation.run(); 403 } 404 return searchOperation; 405 } 406 407 /** 408 * Sends the provided search result entry to the client. 409 * 410 * @param searchOperation The search operation with which the entry is 411 * associated. 412 * @param searchEntry The search result entry to be sent to the client. 413 * 414 * @throws DirectoryException If a problem occurs while attempting to send 415 * the entry to the client and the search should 416 * be terminated. 417 */ 418 @Override 419 public void sendSearchEntry(SearchOperation searchOperation, 420 SearchResultEntry searchEntry) 421 throws DirectoryException 422 { 423 ((InternalSearchOperation) searchOperation).addSearchEntry(searchEntry); 424 } 425 426 427 428 /** 429 * Sends the provided search result reference to the client. 430 * 431 * @param searchOperation The search operation with which the reference is 432 * associated. 433 * @param searchReference The search result reference to be sent to the 434 * client. 435 * 436 * @return <CODE>true</CODE> if the client is able to accept referrals, or 437 * <CODE>false</CODE> if the client cannot handle referrals and no 438 * more attempts should be made to send them for the associated 439 * search operation. 440 * 441 * @throws DirectoryException If a problem occurs while attempting to send 442 * the reference to the client and the search 443 * should be terminated. 444 */ 445 @Override 446 public boolean sendSearchReference(SearchOperation searchOperation, 447 SearchResultReference searchReference) 448 throws DirectoryException 449 { 450 ((InternalSearchOperation) 451 searchOperation).addSearchReference(searchReference); 452 return true; 453 } 454 455 456 457 458 /** 459 * Sends the provided intermediate response message to the client. 460 * 461 * @param intermediateResponse The intermediate response message to be sent. 462 * 463 * @return <CODE>true</CODE> if processing on the associated operation should 464 * continue, or <CODE>false</CODE> if not. 465 */ 466 @Override 467 protected boolean sendIntermediateResponseMessage( 468 IntermediateResponse intermediateResponse) 469 { 470 // FIXME -- Do we need to support Jmx intermediate responses? If so, 471 // then implement this. 472 return false; 473 } 474 475 476 477 478 /** 479 * Closes the connection to the client, optionally sending it a message 480 * indicating the reason for the closure. Note that the ability to send a 481 * notice of disconnection may not be available for all protocols or under all 482 * circumstances. 483 * 484 * @param disconnectReason The disconnect reason that provides the generic 485 * cause for the disconnect. 486 * @param sendNotification Indicates whether to try to provide notification 487 * to the client that the connection will be closed. 488 * @param message The message to send to the client. It may be 489 * <CODE>null</CODE> if no notification is to be 490 * sent. 491 */ 492 @Override 493 public void disconnect(DisconnectReason disconnectReason, 494 boolean sendNotification, 495 LocalizableMessage message) 496 { 497 // we are already performing a disconnect 498 if (disconnectStarted) 499 { 500 return; 501 } 502 disconnectStarted = true ; 503 jmxConnectionHandler.unregisterClientConnection(this); 504 DirectoryServer.connectionClosed(this); 505 finalizeConnectionInternal(); 506 507 // unbind the underlying connection 508 try 509 { 510 UnbindOperationBasis unbindOp = new UnbindOperationBasis( 511 this, nextOperationID(), nextMessageID(), null); 512 unbindOp.run(); 513 } 514 catch (Exception e) 515 { 516 // TODO print a message ? 517 logger.traceException(e); 518 } 519 520 // Call postDisconnectPlugins 521 try 522 { 523 PluginConfigManager pluginManager = 524 DirectoryServer.getPluginConfigManager(); 525 pluginManager.invokePostDisconnectPlugins(this, disconnectReason, 526 message); 527 } 528 catch (Exception e) 529 { 530 logger.traceException(e); 531 } 532 } 533 534 535 536 /** 537 * Retrieves the set of operations in progress for this client connection. 538 * This list must not be altered by any caller. 539 * 540 * @return The set of operations in progress for this client connection. 541 */ 542 @Override 543 public Collection<Operation> getOperationsInProgress() 544 { 545 return operationList; 546 } 547 548 549 550 /** 551 * Retrieves the operation in progress with the specified message ID. 552 * 553 * @param messageID The message ID of the operation to retrieve. 554 * 555 * @return The operation in progress with the specified message ID, or 556 * <CODE>null</CODE> if no such operation could be found. 557 */ 558 @Override 559 public Operation getOperationInProgress(int messageID) 560 { 561 // Jmx operations will not be tracked. 562 return null; 563 } 564 565 566 567 /** 568 * Removes the provided operation from the set of operations in progress for 569 * this client connection. Note that this does not make any attempt to 570 * cancel any processing that may already be in progress for the operation. 571 * 572 * @param messageID The message ID of the operation to remove from the set 573 * of operations in progress. 574 * 575 * @return <CODE>true</CODE> if the operation was found and removed from the 576 * set of operations in progress, or <CODE>false</CODE> if not. 577 */ 578 @Override 579 public boolean removeOperationInProgress(int messageID) 580 { 581 // No implementation is required, since Jmx operations will not be 582 // tracked. 583 return false; 584 } 585 586 587 588 /** 589 * Attempts to cancel the specified operation. 590 * 591 * @param messageID The message ID of the operation to cancel. 592 * @param cancelRequest An object providing additional information about how 593 * the cancel should be processed. 594 * 595 * @return A cancel result that either indicates that the cancel was 596 * successful or provides a reason that it was not. 597 */ 598 @Override 599 public CancelResult cancelOperation(int messageID, 600 CancelRequest cancelRequest) 601 { 602 // Jmx operations cannot be cancelled. 603 // TODO: i18n 604 return new CancelResult(ResultCode.CANNOT_CANCEL, 605 LocalizableMessage.raw("Jmx operations cannot be cancelled")); 606 } 607 608 609 610 /** 611 * Attempts to cancel all operations in progress on this connection. 612 * 613 * @param cancelRequest An object providing additional information about how 614 * the cancel should be processed. 615 */ 616 @Override 617 public void cancelAllOperations(CancelRequest cancelRequest) 618 { 619 // No implementation is required since Jmx operations cannot be 620 // cancelled. 621 } 622 623 624 625 /** 626 * Attempts to cancel all operations in progress on this connection except the 627 * operation with the specified message ID. 628 * 629 * @param cancelRequest An object providing additional information about how 630 * the cancel should be processed. 631 * @param messageID The message ID of the operation that should not be 632 * canceled. 633 */ 634 @Override 635 public void cancelAllOperationsExcept(CancelRequest cancelRequest, 636 int messageID) 637 { 638 // No implementation is required since Jmx operations cannot be 639 // cancelled. 640 } 641 642 /** {@inheritDoc} */ 643 @Override 644 public String getMonitorSummary() 645 { 646 StringBuilder buffer = new StringBuilder(); 647 buffer.append("connID=\""); 648 buffer.append(connectionID); 649 buffer.append("\" connectTime=\""); 650 buffer.append(getConnectTimeString()); 651 buffer.append("\" jmxConnID=\""); 652 buffer.append(jmxConnectionID); 653 buffer.append("\" authDN=\""); 654 655 DN authDN = getAuthenticationInfo().getAuthenticationDN(); 656 if (authDN != null) 657 { 658 authDN.toString(buffer); 659 } 660 buffer.append("\""); 661 662 return buffer.toString(); 663 } 664 665 666 667 /** 668 * Appends a string representation of this client connection to the provided 669 * buffer. 670 * 671 * @param buffer The buffer to which the information should be appended. 672 */ 673 @Override 674 public void toString(StringBuilder buffer) 675 { 676 buffer.append("JmxClientConnection(connID="); 677 buffer.append(connectionID); 678 buffer.append(", authDN=\""); 679 buffer.append(getAuthenticationInfo().getAuthenticationDN()); 680 buffer.append("\")"); 681 } 682 683 /** 684 * Called by the Gc when the object is garbage collected 685 * Release the cursor in case the iterator was badly used and releaseCursor 686 * was never called. 687 */ 688 @Override 689 protected void finalize() 690 { 691 disconnect(DisconnectReason.OTHER, false, null); 692 } 693 694 /** 695 * To be implemented. 696 * 697 * @return number of operations performed on this connection 698 */ 699 @Override 700 public long getNumberOfOperations() { 701 // JMX connections will not be limited. 702 return 0; 703 } 704 705 /** {@inheritDoc} */ 706 @Override 707 public int getSSF() { 708 return 0; 709 } 710} 711