/*
|
* The contents of this file are subject to the terms of the Common Development and
|
* Distribution License (the License). You may not use this file except in compliance with the
|
* License.
|
*
|
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
* specific language governing permission and limitations under the License.
|
*
|
* When distributing Covered Software, include this CDDL Header Notice in each file and include
|
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
* Header, with the fields enclosed by brackets [] replaced by your own identifying
|
* information: "Portions Copyright [year] [name of copyright owner]".
|
*
|
* Copyright 2006-2009 Sun Microsystems, Inc.
|
* Portions Copyright 2011-2016 ForgeRock AS.
|
*/
|
package org.opends.server.protocols.jmx;
|
|
import java.net.InetAddress;
|
import java.util.Collection;
|
import java.util.LinkedList;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import javax.management.Notification;
|
import javax.management.NotificationListener;
|
import javax.management.remote.JMXConnectionNotification;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.LocalizableMessageBuilder;
|
import org.forgerock.i18n.slf4j.LocalizedLogger;
|
import org.forgerock.opendj.ldap.DN;
|
import org.forgerock.opendj.ldap.ResultCode;
|
import org.opends.server.api.ClientConnection;
|
import org.opends.server.api.ConnectionHandler;
|
import org.opends.server.core.*;
|
import org.opends.server.protocols.internal.InternalSearchOperation;
|
import org.opends.server.protocols.internal.SearchRequest;
|
import org.opends.server.types.*;
|
|
import static org.opends.messages.ProtocolMessages.*;
|
|
/**
|
* This class defines the set of methods and structures that must be implemented
|
* by a Directory Server client connection.
|
*/
|
public class JmxClientConnection
|
extends ClientConnection implements NotificationListener
|
{
|
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
|
|
/** The message ID counter to use for jmx connections. */
|
private final AtomicInteger nextMessageID;
|
/** The operation ID counter to use for operations on this connection. */
|
private final AtomicLong nextOperationID;
|
/** The empty operation list for this connection. */
|
private final LinkedList<Operation> operationList;
|
/** The connection ID for this client connection. */
|
private final long connectionID;
|
/** The JMX connection ID for this client connection. */
|
protected String jmxConnectionID;
|
/** The reference to the connection handler that accepted this connection. */
|
private final JmxConnectionHandler jmxConnectionHandler;
|
/** Indicate that the disconnect process is started. */
|
private boolean disconnectStarted;
|
|
/**
|
* Creates a new Jmx client connection that will be authenticated as
|
* as the specified user.
|
*
|
* @param jmxConnectionHandler
|
* The connection handler on which we should be registered
|
* @param authInfo
|
* the User authentication info
|
*/
|
public JmxClientConnection(JmxConnectionHandler jmxConnectionHandler,
|
AuthenticationInfo authInfo)
|
{
|
super();
|
|
nextMessageID = new AtomicInteger(1);
|
nextOperationID = new AtomicLong(0);
|
|
this.jmxConnectionHandler = jmxConnectionHandler;
|
jmxConnectionHandler.registerClientConnection(this);
|
|
setAuthenticationInfo(authInfo);
|
|
connectionID = DirectoryServer.newConnectionAccepted(this);
|
if (connectionID < 0)
|
{
|
disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
|
ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
|
}
|
operationList = new LinkedList<>();
|
|
// Register the Jmx Notification listener (this)
|
jmxConnectionHandler.getRMIConnector().jmxRmiConnectorNoClientCertificate
|
.addNotificationListener(this, null, null);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void handleNotification(Notification notif, Object handback)
|
{
|
// We don't have the expected notification
|
if ( ! (notif instanceof JMXConnectionNotification))
|
{
|
return ;
|
}
|
JMXConnectionNotification jcn = (JMXConnectionNotification) notif;
|
|
// The only handled notifications are CLOSED and FAILED
|
if (!JMXConnectionNotification.CLOSED.equals(jcn.getType())
|
&& !JMXConnectionNotification.FAILED.equals(jcn.getType()))
|
{
|
return;
|
}
|
|
// Check if the closed connection corresponds to the current connection
|
if (!jcn.getConnectionId().equals(jmxConnectionID))
|
{
|
return;
|
}
|
|
// Ok, we can perform the unbind: call finalize
|
disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null);
|
}
|
|
|
/**
|
* Retrieves the operation ID that should be used for the next Jmx
|
* operation.
|
*
|
* @return The operation ID that should be used for the next Jmx
|
* operation.
|
*/
|
public long nextOperationID()
|
{
|
long opID = nextOperationID.getAndIncrement();
|
if (opID < 0)
|
{
|
synchronized (nextOperationID)
|
{
|
if (nextOperationID.get() < 0)
|
{
|
nextOperationID.set(1);
|
return 0;
|
}
|
else
|
{
|
return nextOperationID.getAndIncrement();
|
}
|
}
|
}
|
|
return opID;
|
}
|
|
|
|
/**
|
* Retrieves the message ID that should be used for the next Jmx
|
* operation.
|
*
|
* @return The message ID that should be used for the next Jmx
|
* operation.
|
*/
|
public int nextMessageID()
|
{
|
int msgID = nextMessageID.getAndIncrement();
|
if (msgID < 0)
|
{
|
synchronized (nextMessageID)
|
{
|
if (nextMessageID.get() < 0)
|
{
|
nextMessageID.set(2);
|
return 1;
|
}
|
else
|
{
|
return nextMessageID.getAndIncrement();
|
}
|
}
|
}
|
|
return msgID;
|
}
|
|
|
|
/**
|
* Retrieves the unique identifier that has been assigned to this connection.
|
*
|
* @return The unique identifier that has been assigned to this connection.
|
*/
|
@Override
|
public long getConnectionID()
|
{
|
return connectionID;
|
}
|
|
/**
|
* Retrieves the connection handler that accepted this client connection.
|
*
|
* @return The connection handler that accepted this client connection.
|
*/
|
@Override
|
public ConnectionHandler<?> getConnectionHandler()
|
{
|
return jmxConnectionHandler;
|
}
|
|
/**
|
* Retrieves the protocol that the client is using to communicate with the
|
* Directory Server.
|
*
|
* @return The protocol that the client is using to communicate with the
|
* Directory Server.
|
*/
|
@Override
|
public String getProtocol()
|
{
|
return "jmx";
|
}
|
|
|
|
/**
|
* Retrieves a string representation of the address of the client.
|
*
|
* @return A string representation of the address of the client.
|
*/
|
@Override
|
public String getClientAddress()
|
{
|
return "jmx";
|
}
|
|
|
|
/**
|
* Retrieves the port number for this connection on the client system.
|
*
|
* @return The port number for this connection on the client system.
|
*/
|
@Override
|
public int getClientPort()
|
{
|
return -1;
|
}
|
|
|
|
/**
|
* Retrieves a string representation of the address on the server to which the
|
* client connected.
|
*
|
* @return A string representation of the address on the server to which the
|
* client connected.
|
*/
|
@Override
|
public String getServerAddress()
|
{
|
return "jmx";
|
}
|
|
|
|
/**
|
* Retrieves the port number for this connection on the server
|
* system if available.
|
*
|
* @return The port number for this connection on the server system
|
* or -1 if there is no server port associated with this
|
* connection (e.g. internal client).
|
*/
|
@Override
|
public int getServerPort()
|
{
|
return -1;
|
}
|
|
|
|
/**
|
* Retrieves the <CODE>java.net.InetAddress</CODE> associated with the remote
|
* client system.
|
*
|
* @return The <CODE>java.net.InetAddress</CODE> associated with the remote
|
* client system. It may be <CODE>null</CODE> if the client is not
|
* connected over an IP-based connection.
|
*/
|
@Override
|
public InetAddress getRemoteAddress()
|
{
|
return null;
|
}
|
|
|
|
/**
|
* Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory Server
|
* system to which the client has established the connection.
|
*
|
* @return The <CODE>java.net.InetAddress</CODE> for the Directory Server
|
* system to which the client has established the connection. It may
|
* be <CODE>null</CODE> if the client is not connected over an
|
* IP-based connection.
|
*/
|
@Override
|
public InetAddress getLocalAddress()
|
{
|
return null;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public boolean isConnectionValid()
|
{
|
return !disconnectStarted;
|
}
|
|
/**
|
* Indicates whether this client connection is currently using a secure
|
* mechanism to communicate with the server. Note that this may change over
|
* time based on operations performed by the client or server (e.g., it may go
|
* from <CODE>false</CODE> to <CODE>true</CODE> if the client uses the
|
* StartTLS extended operation).
|
*
|
* @return <CODE>true</CODE> if the client connection is currently using a
|
* secure mechanism to communicate with the server, or
|
* <CODE>false</CODE> if not.
|
*/
|
@Override
|
public boolean isSecure()
|
{
|
return false;
|
}
|
|
|
/**
|
* Retrieves the human-readable name of the security mechanism that is used to
|
* protect communication with this client.
|
*
|
* @return The human-readable name of the security mechanism that is used to
|
* protect communication with this client, or <CODE>null</CODE> if no
|
* security is in place.
|
*/
|
public String getSecurityMechanism()
|
{
|
return "NULL";
|
}
|
|
|
|
/**
|
* Sends a response to the client based on the information in the provided
|
* operation.
|
*
|
* @param operation The operation for which to send the response.
|
*/
|
@Override
|
public void sendResponse(Operation operation)
|
{
|
// There will not be any response sent by this method, since there is not an
|
// actual connection.
|
}
|
|
|
/**
|
* Processes an Jmx search operation with the provided information.
|
*
|
* @param request The search request.
|
* @return A reference to the internal search operation that was processed
|
* and contains information about the result of the processing as
|
* well as lists of the matching entries and search references.
|
*/
|
public InternalSearchOperation processSearch(SearchRequest request)
|
{
|
InternalSearchOperation searchOperation =
|
new InternalSearchOperation(this, nextOperationID(), nextMessageID(), request);
|
|
if (! hasPrivilege(Privilege.JMX_READ, null))
|
{
|
LocalizableMessage message = ERR_JMX_SEARCH_INSUFFICIENT_PRIVILEGES.get();
|
searchOperation.setErrorMessage(new LocalizableMessageBuilder(message));
|
searchOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
|
}
|
else
|
{
|
searchOperation.run();
|
}
|
return searchOperation;
|
}
|
|
/**
|
* Sends the provided search result entry to the client.
|
*
|
* @param searchOperation The search operation with which the entry is
|
* associated.
|
* @param searchEntry The search result entry to be sent to the client.
|
*
|
* @throws DirectoryException If a problem occurs while attempting to send
|
* the entry to the client and the search should
|
* be terminated.
|
*/
|
@Override
|
public void sendSearchEntry(SearchOperation searchOperation,
|
SearchResultEntry searchEntry)
|
throws DirectoryException
|
{
|
((InternalSearchOperation) searchOperation).addSearchEntry(searchEntry);
|
}
|
|
|
|
/**
|
* Sends the provided search result reference to the client.
|
*
|
* @param searchOperation The search operation with which the reference is
|
* associated.
|
* @param searchReference The search result reference to be sent to the
|
* client.
|
*
|
* @return <CODE>true</CODE> if the client is able to accept referrals, or
|
* <CODE>false</CODE> if the client cannot handle referrals and no
|
* more attempts should be made to send them for the associated
|
* search operation.
|
*
|
* @throws DirectoryException If a problem occurs while attempting to send
|
* the reference to the client and the search
|
* should be terminated.
|
*/
|
@Override
|
public boolean sendSearchReference(SearchOperation searchOperation,
|
SearchResultReference searchReference)
|
throws DirectoryException
|
{
|
((InternalSearchOperation)
|
searchOperation).addSearchReference(searchReference);
|
return true;
|
}
|
|
|
|
|
/**
|
* Sends the provided intermediate response message to the client.
|
*
|
* @param intermediateResponse The intermediate response message to be sent.
|
*
|
* @return <CODE>true</CODE> if processing on the associated operation should
|
* continue, or <CODE>false</CODE> if not.
|
*/
|
@Override
|
protected boolean sendIntermediateResponseMessage(
|
IntermediateResponse intermediateResponse)
|
{
|
// FIXME -- Do we need to support Jmx intermediate responses? If so,
|
// then implement this.
|
return false;
|
}
|
|
|
|
|
/**
|
* Closes the connection to the client, optionally sending it a message
|
* indicating the reason for the closure. Note that the ability to send a
|
* notice of disconnection may not be available for all protocols or under all
|
* circumstances.
|
*
|
* @param disconnectReason The disconnect reason that provides the generic
|
* cause for the disconnect.
|
* @param sendNotification Indicates whether to try to provide notification
|
* to the client that the connection will be closed.
|
* @param message The message to send to the client. It may be
|
* <CODE>null</CODE> if no notification is to be
|
* sent.
|
*/
|
@Override
|
public void disconnect(DisconnectReason disconnectReason,
|
boolean sendNotification,
|
LocalizableMessage message)
|
{
|
// we are already performing a disconnect
|
if (disconnectStarted)
|
{
|
return;
|
}
|
disconnectStarted = true ;
|
jmxConnectionHandler.unregisterClientConnection(this);
|
DirectoryServer.connectionClosed(this);
|
finalizeConnectionInternal();
|
|
// unbind the underlying connection
|
try
|
{
|
UnbindOperationBasis unbindOp = new UnbindOperationBasis(
|
this, nextOperationID(), nextMessageID(), null);
|
unbindOp.run();
|
}
|
catch (Exception e)
|
{
|
// TODO print a message ?
|
logger.traceException(e);
|
}
|
|
// Call postDisconnectPlugins
|
try
|
{
|
PluginConfigManager pluginManager =
|
DirectoryServer.getPluginConfigManager();
|
pluginManager.invokePostDisconnectPlugins(this, disconnectReason,
|
message);
|
}
|
catch (Exception e)
|
{
|
logger.traceException(e);
|
}
|
}
|
|
|
|
/**
|
* Retrieves the set of operations in progress for this client connection.
|
* This list must not be altered by any caller.
|
*
|
* @return The set of operations in progress for this client connection.
|
*/
|
@Override
|
public Collection<Operation> getOperationsInProgress()
|
{
|
return operationList;
|
}
|
|
|
|
/**
|
* Retrieves the operation in progress with the specified message ID.
|
*
|
* @param messageID The message ID of the operation to retrieve.
|
*
|
* @return The operation in progress with the specified message ID, or
|
* <CODE>null</CODE> if no such operation could be found.
|
*/
|
@Override
|
public Operation getOperationInProgress(int messageID)
|
{
|
// Jmx operations will not be tracked.
|
return null;
|
}
|
|
|
|
/**
|
* Removes the provided operation from the set of operations in progress for
|
* this client connection. Note that this does not make any attempt to
|
* cancel any processing that may already be in progress for the operation.
|
*
|
* @param messageID The message ID of the operation to remove from the set
|
* of operations in progress.
|
*
|
* @return <CODE>true</CODE> if the operation was found and removed from the
|
* set of operations in progress, or <CODE>false</CODE> if not.
|
*/
|
@Override
|
public boolean removeOperationInProgress(int messageID)
|
{
|
// No implementation is required, since Jmx operations will not be
|
// tracked.
|
return false;
|
}
|
|
|
|
/**
|
* Attempts to cancel the specified operation.
|
*
|
* @param messageID The message ID of the operation to cancel.
|
* @param cancelRequest An object providing additional information about how
|
* the cancel should be processed.
|
*
|
* @return A cancel result that either indicates that the cancel was
|
* successful or provides a reason that it was not.
|
*/
|
@Override
|
public CancelResult cancelOperation(int messageID,
|
CancelRequest cancelRequest)
|
{
|
// Jmx operations cannot be cancelled.
|
// TODO: i18n
|
return new CancelResult(ResultCode.CANNOT_CANCEL,
|
LocalizableMessage.raw("Jmx operations cannot be cancelled"));
|
}
|
|
|
|
/**
|
* Attempts to cancel all operations in progress on this connection.
|
*
|
* @param cancelRequest An object providing additional information about how
|
* the cancel should be processed.
|
*/
|
@Override
|
public void cancelAllOperations(CancelRequest cancelRequest)
|
{
|
// No implementation is required since Jmx operations cannot be
|
// cancelled.
|
}
|
|
|
|
/**
|
* Attempts to cancel all operations in progress on this connection except the
|
* operation with the specified message ID.
|
*
|
* @param cancelRequest An object providing additional information about how
|
* the cancel should be processed.
|
* @param messageID The message ID of the operation that should not be
|
* canceled.
|
*/
|
@Override
|
public void cancelAllOperationsExcept(CancelRequest cancelRequest,
|
int messageID)
|
{
|
// No implementation is required since Jmx operations cannot be
|
// cancelled.
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public String getMonitorSummary()
|
{
|
StringBuilder buffer = new StringBuilder();
|
buffer.append("connID=\"");
|
buffer.append(connectionID);
|
buffer.append("\" connectTime=\"");
|
buffer.append(getConnectTimeString());
|
buffer.append("\" jmxConnID=\"");
|
buffer.append(jmxConnectionID);
|
buffer.append("\" authDN=\"");
|
|
DN authDN = getAuthenticationInfo().getAuthenticationDN();
|
if (authDN != null)
|
{
|
buffer.append(authDN);
|
}
|
buffer.append("\"");
|
|
return buffer.toString();
|
}
|
|
|
|
/**
|
* Appends a string representation of this client connection to the provided
|
* buffer.
|
*
|
* @param buffer The buffer to which the information should be appended.
|
*/
|
@Override
|
public void toString(StringBuilder buffer)
|
{
|
buffer.append("JmxClientConnection(connID=");
|
buffer.append(connectionID);
|
buffer.append(", authDN=\"");
|
buffer.append(getAuthenticationInfo().getAuthenticationDN());
|
buffer.append("\")");
|
}
|
|
/**
|
* Called by the Gc when the object is garbage collected
|
* Release the cursor in case the iterator was badly used and releaseCursor
|
* was never called.
|
*/
|
@Override
|
protected void finalize()
|
{
|
disconnect(DisconnectReason.OTHER, false, null);
|
}
|
|
/**
|
* To be implemented.
|
*
|
* @return number of operations performed on this connection
|
*/
|
@Override
|
public long getNumberOfOperations() {
|
// JMX connections will not be limited.
|
return 0;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public int getSSF() {
|
return 0;
|
}
|
}
|