/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.jmx.provider.ro;
import java.io.IOException;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.MBeanServer;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnectorServer;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
import org.objectweb.proactive.api.PARemoteObject;
import org.objectweb.proactive.core.ProActiveException;
import org.objectweb.proactive.core.remoteobject.RemoteObjectExposer;
import org.objectweb.proactive.core.util.URIBuilder;
import org.ow2.proactive.jmx.provider.JMXProviderUtils;
/**
* <p>
* A remote object representing a connector server. Remote clients can make
* connections using the {@link #newClient(Object)} method. This method returns
* a Remote Object representing the connection.
* </p>
*
* <p>
* User code does not usually reference this class directly. Remote Object
* connection servers are usually created with the class
* {@link ROConnectorServer}. Remote clients usually create connections either
* with {@link javax.management.remote.JMXConnectorFactory} or by instantiating
* {@link ROConnector}.
* </p>
*
* @author The ProActive Team
*/
public class ROServerImpl implements Serializable {
private static final Logger LOGGER = Logger.getLogger(ROServerImpl.class);
/** The current connection number */
private static int connectionNumber;
/** The reference on the MBeanServer */
private transient MBeanServer mbeanServer;
/** The environment containing the attributes */
private final Map<String, ?> env;
/** The access control context */
private final AccessControlContext context;
/** All known weakly referenced connection exposers identified by the connection id */
private final Map<String, WeakReference<RemoteObjectExposer<ROConnection>>> connections;
/** The remote object exposer of this server */
private final RemoteObjectExposer<ROServerImpl> roe;
/**
* Empty constructor without arguments.
*/
public ROServerImpl() {
this.mbeanServer = null;
this.env = null;
this.context = null;
this.connections = null;
this.roe = null;
}
/**
* Constructs a new <code>ROServerImpl</code>.
*
* @param mbs
* the MBean server
* @param env
* the environment containing attributes for the new
* <code>ROServerImpl</code>
*/
public ROServerImpl(final MBeanServer mbs, final Map<String, ?> env) {
this.mbeanServer = mbs;
this.env = env;
this.context = AccessController.getContext();
this.connections = new ConcurrentHashMap<>();
this.roe = new RemoteObjectExposer<>(ROServerImpl.class.getName(), this);
}
/**
* Returns a new ROConnection.
*
* @return a RO Connection that will enables remote calls onto the remote
* MBean Server
*/
public synchronized ROConnection newClient(final Object credentials) throws IOException {
if (this.mbeanServer == null) {
throw new IllegalStateException("Not attached to an MBean server");
}
// Authenticate to get the subject
Subject subject = null;
final JMXAuthenticator authenticator = (JMXAuthenticator) this.env.get(JMXConnectorServer.AUTHENTICATOR);
if (authenticator != null) {
try {
subject = authenticator.authenticate(credentials);
} catch (SecurityException e) {
LOGGER.warn("Authentication failed", e);
throw e;
}
}
final int num = ++ROServerImpl.connectionNumber;
// Create the id of the connection
final String connectionId = ROServerImpl.createConnectionID(subject, num);
try {
final ROConnection connection = new ROConnection(this.mbeanServer,
connectionId,
this,
subject,
this.context);
// Create a remote object exposer for this object
final RemoteObjectExposer<ROConnection> roe = new RemoteObjectExposer<>(ROConnection.class.getName(),
connection);
// Use a weak reference and put it in the hash map
this.connections.put(connectionId, new WeakReference<>(roe));
// Get the default base uri for all remote objects
final URI baseURI = JMXProviderUtils.getBaseURI();
// Generate the uri (default base uri + class simple name + connection number)
final URI uri = URIBuilder.buildURI(baseURI, ROConnection.class.getSimpleName() + num);
// Bind under the correct uri
return PARemoteObject.bind(roe, uri);
} catch (Exception e) {
final String message = "Unable to create the client connection " + connectionId;
LOGGER.error(message, e);
throw JMXProviderUtils.newIOException(message, e);
}
}
/**
* Sets the MBean server attached to this connector.
*
* @param mbs
* The MBean server bound to this connector
*/
public synchronized void setMBeanServer(final MBeanServer mbs) {
this.mbeanServer = mbs;
}
/**
* Returns the MBean server bound with the connector.
*
* @return the MBean server bound with the connector
*/
public synchronized MBeanServer getMBeanServer() {
return this.mbeanServer;
}
/**
* Closes this server.
*
* @throws IOException if the close operation failed
*/
public synchronized void close() throws IOException {
// First close the server
IOException serverException = null;
try {
this.internalCloseRemoteObject(this.roe);
} catch (ProActiveException e) {
serverException = JMXProviderUtils.newIOException("Unable to close the server " + this.roe.getURL(), e);
}
// Even if the server was not closed properly
// try to close all the connections
IOException connectionCloseException = null;
for (final Entry<String, WeakReference<RemoteObjectExposer<ROConnection>>> entry : this.connections.entrySet()) {
String connectionId = entry.getKey();
WeakReference<RemoteObjectExposer<ROConnection>> weakReference = entry.getValue();
RemoteObjectExposer<ROConnection> roe = weakReference.get();
if (roe == null) {
this.connections.remove(connectionId);
} else {
try {
this.internalCloseRemoteObject(roe);
} catch (ProActiveException e) {
if (connectionCloseException == null) {
connectionCloseException = JMXProviderUtils.newIOException("Unable to close the connection " +
connectionId, e);
}
}
}
}
// If there was an exception when closing the server re-throw it once all connections
// were closed (at least attempted)
if (serverException != null) {
throw serverException;
}
// If there was an exception when closing the connections re-throw it now (the first one)
if (connectionCloseException != null) {
throw connectionCloseException;
}
}
private void internalCloseRemoteObject(final RemoteObjectExposer<?> roe) throws ProActiveException {
// First try to unregisterAll
ProActiveException unregisterAllException = null;
try {
roe.unregisterAll();
} catch (ProActiveException e) {
// In case of exception keep it
unregisterAllException = e;
}
// Since the unexportAll method throws an exception it cannot be placed in a finally block
// so try it even if there was a exception on unregisterAll
ProActiveException unexportAllException = null;
try {
roe.unexportAll();
} catch (ProActiveException e) {
// In case of exception keep it
unexportAllException = e;
}
// If there was an exception on unregisterAll report it
if (unregisterAllException != null) {
throw unregisterAllException;
}
// If there was an exception on unexportAll report it
if (unexportAllException != null) {
throw unexportAllException;
}
}
// -------------------------------------------------------------------------
// PROTECTED METHODS
// -------------------------------------------------------------------------
protected void closeConnectionById(final String connectionId) throws IOException {
// This method is executed in case of a closed server
// or a closed connection. We must ensure the connection is in both cases removed from the list
// of connections.
final WeakReference<RemoteObjectExposer<ROConnection>> weak = this.connections.remove(connectionId);
if (weak == null) {
return;
}
final RemoteObjectExposer<ROConnection> roe = weak.get();
if (roe == null) {
return;
}
// Try to close the remote object
try {
this.internalCloseRemoteObject(roe);
} catch (ProActiveException e) {
throw JMXProviderUtils.newIOException("Unable to close the connection " + connectionId, e);
}
}
/**
* Returns the remote object exposer of this server.
*/
protected RemoteObjectExposer<ROServerImpl> getRemoteObjectExposer() {
return this.roe;
}
// -------------------------------------------------------------------------
// PRIVATE STATIC METHODS
// -------------------------------------------------------------------------
/**
* See JSR 160 specification at javax/management/remote/package-summary.html
* The formal grammar is: ConnectionId: Protocol : ClientAddressopt Space
* ClientIdopt Space ArbitraryText ClientAddress: // HostAddress
* ClientPortopt ClientPort : HostPort
*/
private static String createConnectionID(
// final String clientAddress,
// final int clientPort,
final Subject subject, final int num) {
// Start with Protocol
final StringBuilder strBuilder = new StringBuilder(JMXProviderUtils.RO_PROTOCOL);
strBuilder.append(':');
// ClientAddress
// if (clientAddress != null) {
// strBuilder.append("//").append(clientAddress);
// }
// ClientPort
// if (clientPort >= 0) {
// strBuilder.append(':').append(clientPort);
// }
strBuilder.append(' ');
// ClientId
if (subject != null) {
for (final Principal p : subject.getPrincipals()) {
String name = p.getName();
// Must not contain spaces
name = name.replace(' ', '_');
strBuilder.append(name);
}
}
strBuilder.append(' ');
// ArbitraryText
strBuilder.append(num);
return strBuilder.toString();
}
}