blob: 652ac76ab726a8b6d04862c5cb5cb5f329ca0a60 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.feed.jmx;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
import groovy.time.TimeDuration;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.entity.java.JmxSupport;
import org.apache.brooklyn.entity.java.UsesJmx;
import org.apache.brooklyn.util.core.crypto.SecureKeys;
import org.apache.brooklyn.util.crypto.SslTrustUtils;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class JmxHelper {
private static final Logger LOG = LoggerFactory.getLogger(JmxHelper.class);
public static final String JMX_URL_FORMAT = "service:jmx:rmi:///jndi/rmi://%s:%d/%s";
// first host:port may be ignored, so above is sufficient, but not sure
public static final String RMI_JMX_URL_FORMAT = "service:jmx:rmi://%s:%d/jndi/rmi://%s:%d/%s";
// jmxmp
public static final String JMXMP_URL_FORMAT = "service:jmx:jmxmp://%s:%d";
// Tracks the MBeans we have failed to find, with a set keyed off the url
private static final Map<String, Set<ObjectName>> notFoundMBeansByUrl = Collections.synchronizedMap(new WeakHashMap<String, Set<ObjectName>>());
public static final Map<String, String> CLASSES = ImmutableMap.<String,String>builder()
.put("Integer", Integer.TYPE.getName())
.put("Long", Long.TYPE.getName())
.put("Boolean", Boolean.TYPE.getName())
.put("Byte", Byte.TYPE.getName())
.put("Character", Character.TYPE.getName())
.put("Double", Double.TYPE.getName())
.put("Float", Float.TYPE.getName())
.put("GStringImpl", String.class.getName())
.put("LinkedHashMap", Map.class.getName())
.put("TreeMap", Map.class.getName())
.put("HashMap", Map.class.getName())
.put("ConcurrentHashMap", Map.class.getName())
.put("TabularDataSupport", TabularData.class.getName())
.put("CompositeDataSupport", CompositeData.class.getName())
.build();
/** constructs a JMX URL suitable for connecting to the given entity, being smart about JMX/RMI vs JMXMP */
public static String toJmxUrl(EntityLocal entity) {
String url = entity.getAttribute(UsesJmx.JMX_URL);
if (url != null) {
return url;
} else {
new JmxSupport(entity, null).setJmxUrl();
url = entity.getAttribute(UsesJmx.JMX_URL);
return Preconditions.checkNotNull(url, "Could not find URL for "+entity);
}
}
/** constructs an RMI/JMX URL with the given inputs
* (where the RMI Registry Port should be non-null, and at least one must be non-null) */
public static String toRmiJmxUrl(String host, Integer jmxRmiServerPort, Integer rmiRegistryPort, String context) {
if (rmiRegistryPort != null && rmiRegistryPort > 0) {
if (jmxRmiServerPort!=null && jmxRmiServerPort > 0 && jmxRmiServerPort!=rmiRegistryPort) {
// we have an explicit known JMX RMI server port (e.g. because we are using the agent),
// distinct from the RMI registry port
// (if the ports are the same, it is a short-hand, and don't use this syntax!)
return String.format(RMI_JMX_URL_FORMAT, host, jmxRmiServerPort, host, rmiRegistryPort, context);
}
return String.format(JMX_URL_FORMAT, host, rmiRegistryPort, context);
} else if (jmxRmiServerPort!=null && jmxRmiServerPort > 0) {
LOG.warn("No RMI registry port set for "+host+"; attempting to use JMX port for RMI lookup");
return String.format(JMX_URL_FORMAT, host, jmxRmiServerPort, context);
} else {
LOG.warn("No RMI/JMX details set for "+host+"; returning null");
return null;
}
}
/** constructs a JMXMP URL for connecting to the given host and port */
public static String toJmxmpUrl(String host, Integer jmxmpPort) {
return "service:jmx:jmxmp://"+host+(jmxmpPort!=null ? ":"+jmxmpPort : "");
}
final EntityLocal entity;
final String url;
final String user;
final String password;
private volatile transient JMXConnector connector;
private volatile transient MBeanServerConnection connection;
private transient boolean triedConnecting;
private transient boolean failedReconnecting;
private transient long failedReconnectingTime;
private int minTimeBetweenReconnectAttempts = 1000;
private final AtomicBoolean terminated = new AtomicBoolean();
// Tracks the MBeans we have failed to find for this JmsHelper's connection URL (so can log just once for each)
private final Set<ObjectName> notFoundMBeans;
public JmxHelper(EntityLocal entity) {
this(toJmxUrl(entity), entity, entity.getAttribute(UsesJmx.JMX_USER), entity.getAttribute(UsesJmx.JMX_PASSWORD));
if (entity.getAttribute(UsesJmx.JMX_URL) == null) {
entity.setAttribute(UsesJmx.JMX_URL, url);
}
}
// TODO split this in to two classes, one for entities, and one entity-neutral
// (simplifying set of constructors below)
public JmxHelper(String url) {
this(url, null, null);
}
public JmxHelper(String url, String user, String password) {
this(url, null, user, password);
}
public JmxHelper(String url, EntityLocal entity, String user, String password) {
this.url = url;
this.entity = entity;
this.user = user;
this.password = password;
synchronized (notFoundMBeansByUrl) {
Set<ObjectName> set = notFoundMBeansByUrl.get(url);
if (set == null) {
set = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<ObjectName, Boolean>()));
notFoundMBeansByUrl.put(url, set);
}
notFoundMBeans = set;
}
}
public void setMinTimeBetweenReconnectAttempts(int val) {
minTimeBetweenReconnectAttempts = val;
}
public String getUrl(){
return url;
}
// ============== connection related calls =======================
//for tesing purposes
protected MBeanServerConnection getConnection() {
return connection;
}
/**
* Checks if the JmxHelper is connected. Returned value could be stale as soon
* as it is received.
*
* This method is thread safe.
*
* @return true if connected, false otherwise.
*/
public boolean isConnected() {
return connection!=null;
}
/**
* Reconnects. If it already is connected, it disconnects first.
*
* @throws IOException
*/
public synchronized void reconnectWithRetryDampened() throws IOException {
// If we've already tried reconnecting very recently, don't try again immediately
if (failedReconnecting) {
long timeSince = (System.currentTimeMillis() - failedReconnectingTime);
if (timeSince < minTimeBetweenReconnectAttempts) {
String msg = "Not reconnecting to JMX at "+url+" because attempt failed "+Time.makeTimeStringRounded(timeSince)+" ago";
throw new IllegalStateException(msg);
}
}
reconnect();
}
public synchronized void reconnect() throws IOException {
disconnect();
try {
connect();
failedReconnecting = false;
} catch (Exception e) {
if (failedReconnecting) {
if (LOG.isDebugEnabled()) LOG.debug("unable to re-connect to JMX url (repeated failure): {}: {}", url, e);
} else {
LOG.debug("unable to re-connect to JMX url {} (rethrowing): {}", url, e);
failedReconnecting = true;
}
failedReconnectingTime = System.currentTimeMillis();
throw Throwables.propagate(e);
}
}
/** attempts to connect immediately */
@SuppressWarnings({ "rawtypes", "unchecked" })
public synchronized void connect() throws IOException {
if (terminated.get()) throw new IllegalStateException("JMX Helper "+this+" already terminated");
if (connection != null) return;
triedConnecting = true;
if (connector != null) connector.close();
JMXServiceURL serviceUrl = new JMXServiceURL(url);
Map env = getConnectionEnvVars();
try {
connector = JMXConnectorFactory.connect(serviceUrl, env);
} catch (NullPointerException npe) {
//some software -- eg WSO2 -- will throw an NPE exception if the JMX connection can't be created, instead of an IOException.
//this is a break of contract with the JMXConnectorFactory.connect method, so this code verifies if the NPE is
//thrown by a known offender (wso2) and if so replaces the bad exception by a new IOException.
//ideally WSO2 will fix this bug and we can remove this code.
boolean thrownByWso2 = npe.getStackTrace()[0].toString().contains("org.wso2.carbon.core.security.CarbonJMXAuthenticator.authenticate");
if (thrownByWso2) {
throw new IOException("Failed to connect to url "+url+". NullPointerException is thrown, but replaced by an IOException to fix a WSO2 JMX problem", npe);
} else {
throw npe;
}
} catch (IOException e) {
Exceptions.propagateIfFatal(e);
if (terminated.get()) {
throw new IllegalStateException("JMX Helper "+this+" already terminated", e);
} else {
throw e;
}
}
connection = connector.getMBeanServerConnection();
if (terminated.get()) {
disconnectNow();
throw new IllegalStateException("JMX Helper "+this+" already terminated");
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map getConnectionEnvVars() {
Map env = new LinkedHashMap();
if (groovyTruth(user) && groovyTruth(password)) {
String[] creds = new String[] {user, password};
env.put(JMXConnector.CREDENTIALS, creds);
}
if (entity!=null && groovyTruth(entity.getConfig(UsesJmx.JMX_SSL_ENABLED))) {
env.put("jmx.remote.profiles", JmxmpAgent.TLS_JMX_REMOTE_PROFILES);
PrivateKey key = entity.getConfig(UsesJmx.JMX_SSL_ACCESS_KEY);
Certificate cert = entity.getConfig(UsesJmx.JMX_SSL_ACCESS_CERT);
KeyStore ks = SecureKeys.newKeyStore();
try {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (key!=null) {
ks.setKeyEntry("brooklyn-jmx-access", key, "".toCharArray(), new Certificate[] { cert });
}
kmf.init(ks, "".toCharArray());
TrustManager tms =
// TODO use root cert for trusting server
//trustStore!=null ? SecureKeys.getTrustManager(trustStore) :
SslTrustUtils.TRUST_ALL;
SSLContext ctx = SSLContext.getInstance("TLSv1");
ctx.init(kmf.getKeyManagers(), new TrustManager[] { tms }, null);
SSLSocketFactory ssf = ctx.getSocketFactory();
env.put(JmxmpAgent.TLS_SOCKET_FACTORY_PROPERTY, ssf);
} catch (Exception e) {
LOG.warn("Error setting key "+key+" for "+entity+": "+e, e);
}
}
return env;
}
/**
* Continuously attempts to connect for at least the indicated amount of time; or indefinitely if -1. This method
* is useful when you are not sure if the system you are trying to connect to already is up and running.
*
* This method doesn't throw an Exception, but returns true on success, false otherwise.
*
* TODO: What happens if already connected?
*
* @param timeoutMs
* @return
*/
public boolean connect(long timeoutMs) {
if (LOG.isDebugEnabled()) LOG.debug("Connecting to JMX URL: {} ({})", url, ((timeoutMs == -1) ? "indefinitely" : timeoutMs+"ms timeout"));
long startMs = System.currentTimeMillis();
long endMs = (timeoutMs == -1) ? Long.MAX_VALUE : (startMs + timeoutMs);
long currentTime = startMs;
Throwable lastError = null;
int attempt = 0;
while (currentTime <= endMs) {
currentTime = System.currentTimeMillis();
if (attempt != 0) sleep(100); //sleep 100 to prevent thrashing and facilitate interruption
if (LOG.isTraceEnabled()) LOG.trace("trying connection to {} at time {}", url, currentTime);
try {
connect();
return true;
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
if (!terminated.get() && shouldRetryOn(e)) {
if (LOG.isDebugEnabled()) LOG.debug("Attempt {} failed connecting to {} ({})", new Object[] {attempt + 1, url, e.getMessage()});
lastError = e;
} else {
throw Exceptions.propagate(e);
}
}
attempt++;
}
LOG.warn("unable to connect to JMX url: "+url, lastError);
return false;
}
private boolean shouldRetryOn(Exception e) {
// Expect SecurityException, IOException, etc.
// But can also see things like javax.naming.ServiceUnavailableException with WSO2 app-servers.
// So let's not try to second guess strange behaviours that future entities will exhibit.
//
// However, if it was our request that was invalid then not worth retrying.
if (e instanceof AttributeNotFoundException) return false;
if (e instanceof InstanceAlreadyExistsException) return false;
if (e instanceof InstanceNotFoundException) return false;
if (e instanceof InvalidAttributeValueException) return false;
if (e instanceof ListenerNotFoundException) return false;
if (e instanceof MalformedObjectNameException) return false;
if (e instanceof NotCompliantMBeanException) return false;
if (e instanceof InterruptedException) return false;
if (e instanceof RuntimeInterruptedException) return false;
return true;
}
/**
* A thread-safe version of {@link #disconnectNow()}.
*
* This method is threadsafe.
*/
public synchronized void disconnect() {
disconnectNow();
}
/**
* Disconnects, preventing subsequent connections to be made. Method doesn't throw an exception.
*
* Can safely be called if already disconnected.
*
* This method is not threadsafe, but will thus not block if
* another thread is taking a long time for connections to timeout.
*
* Any concurrent requests will likely get an IOException - see
* {@linkplain http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXConnector.html#close()}.
*
*/
public void terminate() {
terminated.set(true);
disconnectNow();
}
protected void disconnectNow() {
triedConnecting = false;
if (connector != null) {
if (LOG.isDebugEnabled()) LOG.debug("Disconnecting from JMX URL {}", url);
try {
connector.close();
} catch (Exception e) {
// close attempts to connect to close cleanly; and if it can't, it throws;
// often we disconnect as part of shutdown, even if the other side has already stopped --
// so swallow exceptions (no situations known where we need a clean closure on the remote side)
if (LOG.isDebugEnabled()) LOG.debug("Caught exception disconnecting from JMX at {} ({})", url, e.getMessage());
if (LOG.isTraceEnabled()) LOG.trace("Details for exception disconnecting JMX", e);
} finally {
connector = null;
connection = null;
}
}
}
/**
* Gets a usable MBeanServerConnection.
*
* Method is threadsafe.
*
* @returns the MBeanServerConnection
* @throws IllegalStateException if not connected.
*/
private synchronized MBeanServerConnection getConnectionOrFail() {
if (isConnected())
return getConnection();
if (triedConnecting) {
throw new IllegalStateException("Failed to connect to JMX at "+url);
} else {
String msg = "Not connected (and not attempted to connect) to JMX at "+url+
(failedReconnecting ? (" (last reconnect failure at "+ Time.makeDateString(failedReconnectingTime) + ")") : "");
throw new IllegalStateException(msg);
}
}
private <T> T invokeWithReconnect(Callable<T> task) {
try {
return task.call();
} catch (Exception e) {
if (shouldRetryOn(e)) {
try {
reconnectWithRetryDampened();
return task.call();
} catch (Exception e2) {
throw Throwables.propagate(e2);
}
} else {
throw Throwables.propagate(e);
}
}
}
// ====================== query related calls =======================================
/**
* Converts from an object name pattern to a real object name, by querying with findMBean;
* if no matching MBean can be found (or if more than one match found) then returns null.
* If the supplied object name is not a pattern then just returns that. If the
*/
public ObjectName toLiteralObjectName(ObjectName objectName) {
if (checkNotNull(objectName, "objectName").isPattern()) {
ObjectInstance bean = findMBean(objectName);
return (bean != null) ? bean.getObjectName() : null;
} else {
return objectName;
}
}
public Set<ObjectInstance> findMBeans(final ObjectName objectName) {
return invokeWithReconnect(new Callable<Set<ObjectInstance>>() {
public Set<ObjectInstance> call() throws Exception {
return getConnectionOrFail().queryMBeans(objectName, null);
}});
}
public ObjectInstance findMBean(ObjectName objectName) {
Set<ObjectInstance> beans = findMBeans(objectName);
if (beans.size() == 1) {
notFoundMBeans.remove(objectName);
return Iterables.getOnlyElement(beans);
} else {
boolean changed = notFoundMBeans.add(objectName);
if (beans.size() > 1) {
if (changed) {
LOG.warn("JMX object name query returned {} values for {} at {}; ignoring all",
new Object[] {beans.size(), objectName.getCanonicalName(), url});
} else {
if (LOG.isDebugEnabled()) LOG.debug("JMX object name query returned {} values for {} at {} (repeating); ignoring all",
new Object[] {beans.size(), objectName.getCanonicalName(), url});
}
} else {
if (changed) {
LOG.warn("JMX object {} not found at {}", objectName.getCanonicalName(), url);
} else {
if (LOG.isDebugEnabled()) LOG.debug("JMX object {} not found at {} (repeating)", objectName.getCanonicalName(), url);
}
}
return null;
}
}
public Set<ObjectInstance> doesMBeanExistsEventually(final ObjectName objectName, Duration timeout) {
return doesMBeanExistsEventually(objectName, timeout.toMilliseconds(), TimeUnit.MILLISECONDS);
}
public Set<ObjectInstance> doesMBeanExistsEventually(final ObjectName objectName, TimeDuration timeout) {
return doesMBeanExistsEventually(objectName, timeout.toMilliseconds(), TimeUnit.MILLISECONDS);
}
public Set<ObjectInstance> doesMBeanExistsEventually(final ObjectName objectName, long timeoutMillis) {
return doesMBeanExistsEventually(objectName, timeoutMillis, TimeUnit.MILLISECONDS);
}
public Set<ObjectInstance> doesMBeanExistsEventually(String objectName, Duration timeout) {
return doesMBeanExistsEventually(createObjectName(objectName), timeout);
}
public Set<ObjectInstance> doesMBeanExistsEventually(String objectName, TimeDuration timeout) {
return doesMBeanExistsEventually(createObjectName(objectName), timeout);
}
public Set<ObjectInstance> doesMBeanExistsEventually(String objectName, long timeout, TimeUnit timeUnit) {
return doesMBeanExistsEventually(createObjectName(objectName), timeout, timeUnit);
}
/** returns set of beans found, with retry, empty set if none after timeout */
public Set<ObjectInstance> doesMBeanExistsEventually(final ObjectName objectName, long timeout, TimeUnit timeUnit) {
final long timeoutMillis = timeUnit.toMillis(timeout);
final AtomicReference<Set<ObjectInstance>> beans = new AtomicReference<Set<ObjectInstance>>(ImmutableSet.<ObjectInstance>of());
try {
Repeater.create("Wait for "+objectName)
.limitTimeTo(timeout, timeUnit)
.every(500, TimeUnit.MILLISECONDS)
.until(new Callable<Boolean>() {
public Boolean call() {
connect(timeoutMillis);
beans.set(findMBeans(objectName));
return !beans.get().isEmpty();
}})
.rethrowException()
.run();
return beans.get();
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
public void assertMBeanExistsEventually(ObjectName objectName, Duration timeout) {
assertMBeanExistsEventually(objectName, timeout.toMilliseconds(), TimeUnit.MILLISECONDS);
}
public void assertMBeanExistsEventually(ObjectName objectName, TimeDuration timeout) {
assertMBeanExistsEventually(objectName, timeout.toMilliseconds(), TimeUnit.MILLISECONDS);
}
public void assertMBeanExistsEventually(ObjectName objectName, long timeoutMillis) {
assertMBeanExistsEventually(objectName, timeoutMillis, TimeUnit.MILLISECONDS);
}
public void assertMBeanExistsEventually(ObjectName objectName, long timeout, TimeUnit timeUnit) {
Set<ObjectInstance> beans = doesMBeanExistsEventually(objectName, timeout, timeUnit);
if (beans.size() != 1) {
throw new IllegalStateException("MBean "+objectName+" not found within "+timeout+
(beans.size() > 1 ? "; found multiple matches: "+beans : ""));
}
}
/**
* Returns a specific attribute for a JMX {@link ObjectName}.
*/
public Object getAttribute(ObjectName objectName, final String attribute) {
final ObjectName realObjectName = toLiteralObjectName(objectName);
if (realObjectName != null) {
Object result = invokeWithReconnect(new Callable<Object>() {
public Object call() throws Exception {
return getConnectionOrFail().getAttribute(realObjectName, attribute);
}});
if (LOG.isTraceEnabled()) LOG.trace("From {}, for jmx attribute {}.{}, got value {}", new Object[] {url, objectName.getCanonicalName(), attribute, result});
return result;
} else {
return null;
}
}
public void setAttribute(String objectName, String attribute, Object val) {
setAttribute(createObjectName(objectName), attribute, val);
}
public void setAttribute(ObjectName objectName, final String attribute, final Object val) {
final ObjectName realObjectName = toLiteralObjectName(objectName);
if (realObjectName != null) {
invokeWithReconnect(new Callable<Void>() {
public Void call() throws Exception {
getConnectionOrFail().setAttribute(realObjectName, new javax.management.Attribute(attribute, val));
return null;
}});
if (LOG.isTraceEnabled()) LOG.trace("From {}, for jmx attribute {}.{}, set value {}", new Object[] {url, objectName.getCanonicalName(), attribute, val});
} else {
if (LOG.isDebugEnabled()) LOG.debug("From {}, cannot set attribute {}.{}, because mbean not found", new Object[] {url, objectName.getCanonicalName(), attribute});
}
}
/** @see #operation(ObjectName, String, Object ...) */
public Object operation(String objectName, String method, Object... arguments) {
return operation(createObjectName(objectName), method, arguments);
}
/**
* Executes an operation on a JMX {@link ObjectName}.
*/
public Object operation(ObjectName objectName, final String method, final Object... arguments) {
final ObjectName realObjectName = toLiteralObjectName(objectName);
final String[] signature = new String[arguments.length];
for (int i = 0; i < arguments.length; i++) {
Class<?> clazz = arguments[i].getClass();
signature[i] = (CLASSES.containsKey(clazz.getSimpleName()) ? CLASSES.get(clazz.getSimpleName()) : clazz.getName());
}
Object result = invokeWithReconnect(new Callable<Object>() {
public Object call() throws Exception {
return getConnectionOrFail().invoke(realObjectName, method, arguments, signature);
}});
if (LOG.isTraceEnabled()) LOG.trace("From {}, for jmx operation {}.{}({}), got value {}", new Object[] {url, realObjectName.getCanonicalName(), method, Arrays.asList(arguments),
result});
return result;
}
public void addNotificationListener(String objectName, NotificationListener listener) {
addNotificationListener(createObjectName(objectName), listener, null);
}
public void addNotificationListener(String objectName, NotificationListener listener, NotificationFilter filter) {
addNotificationListener(createObjectName(objectName), listener, filter);
}
public void addNotificationListener(ObjectName objectName, NotificationListener listener) {
addNotificationListener(objectName, listener, null);
}
public void addNotificationListener(final ObjectName objectName, final NotificationListener listener, final NotificationFilter filter) {
invokeWithReconnect(new Callable<Void>() {
public Void call() throws Exception {
getConnectionOrFail().addNotificationListener(objectName, listener, filter, null);
return null;
}});
}
public void removeNotificationListener(String objectName, NotificationListener listener) {
removeNotificationListener(createObjectName(objectName), listener);
}
public void removeNotificationListener(final ObjectName objectName, final NotificationListener listener) {
removeNotificationListener(objectName, listener, null);
}
public void removeNotificationListener(final ObjectName objectName, final NotificationListener listener, final NotificationFilter filter) {
if (isConnected()) invokeWithReconnect(new Callable<Void>() {
public Void call() throws Exception {
getConnectionOrFail().removeNotificationListener(objectName, listener, filter, null);
return null;
}});
}
public <M> M getProxyObject(String objectName, Class<M> mbeanInterface) {
return getProxyObject(createObjectName(objectName), mbeanInterface);
}
public <M> M getProxyObject(ObjectName objectName, Class<M> mbeanInterface) {
MBeanServerConnection connection = getConnectionOrFail();
return JMX.newMBeanProxy(connection, objectName, mbeanInterface, false);
}
public static ObjectName createObjectName(String name) {
try {
return new ObjectName(name);
} catch (MalformedObjectNameException e) {
throw Throwables.propagate(e);
}
}
private static void sleep(long sleepTimeMillis) {
try {
Thread.sleep(sleepTimeMillis);
} catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
}