| /* |
| * 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); |
| } |
| } |
| } |