com.tangosol.net.management.MBeanHelper Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.net.management;
import com.oracle.coherence.common.net.TcpSocketProvider;
import com.tangosol.internal.net.management.DefaultGatewayDependencies;
import com.tangosol.internal.net.management.GatewayDependencies;
import com.tangosol.internal.net.management.LegacyXmlGatewayHelper;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.CacheService;
import com.tangosol.net.Cluster;
import com.tangosol.net.InetAddressHelper;
import com.tangosol.net.Member;
import com.tangosol.net.NamedCache;
import com.tangosol.net.management.annotation.Description;
import com.tangosol.net.management.annotation.Notification;
import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.Filter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.rmi.registry.LocateRegistry;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.management.DynamicMBean;
import javax.management.MBeanNotificationInfo;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.openmbean.SimpleType;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;
/**
* Helper class providing various functionality related to aggregation of
* attributes and methods exposed by Coherence JMX framework MBeans.
*
* @since Coherence 3.3
* @author gg 2007/01/02
*/
public abstract class MBeanHelper
extends Base
{
/**
* Return the the default domain name as configured in the Coherence
* operational configuration descriptor ("default-domain-name" element).
*
* @return the default domain name
*/
public static String getDefaultDomain()
{
GatewayDependencies deps = LegacyXmlGatewayHelper.fromXml(CacheFactory.getManagementConfig(),
new DefaultGatewayDependencies());
return deps.getDefaultDomain();
}
/**
* Find an MBeanServer that Coherence MBeans are registered with.
*
* @return an existing or a new MBeanServer with the default
* domain name as configured in the Coherence operational
* configuration descriptor
*/
public static MBeanServer findMBeanServer()
{
return findMBeanServer(null);
}
/**
* Find an MBeanServer that has the specified default domain name. If the
* domain name is not specified, any existing MBeanServer is chosen.
*
* @param sDefaultDomain the default domain name
*
* @return an existing or a new MBeanServer with the specified default
* domain name
*/
public static MBeanServer findMBeanServer(String sDefaultDomain)
{
GatewayDependencies deps = LegacyXmlGatewayHelper.fromXml(CacheFactory.getManagementConfig(),
new DefaultGatewayDependencies());
return findMBeanServer(sDefaultDomain, deps);
}
/**
* Find an MBeanServer that has the specified default domain name. If the
* domain name is not specified, any existing MBeanServer is chosen.
*
* @param sDefaultDomain the default domain name
* @param deps the {@link GatewayDependencies} containing the
* management configuration to use
*
* @return an existing or a new MBeanServer with the specified default
* domain name
*/
public static MBeanServer findMBeanServer(String sDefaultDomain, GatewayDependencies deps)
{
try
{
if (sDefaultDomain == null || sDefaultDomain.isEmpty())
{
sDefaultDomain = deps.getDefaultDomain();
}
// allow custom
MBeanServerFinder finder = deps.getMBeanServerFinder();
if (finder != null)
{
MBeanServer server = finder.findMBeanServer(sDefaultDomain);
if (server != null)
{
return server;
}
}
// check among existing MBeanServers
for (MBeanServer server : MBeanServerFactory.findMBeanServer(null))
{
if (sDefaultDomain == null || sDefaultDomain.length() == 0 ||
server.getDefaultDomain().equals(sDefaultDomain))
{
return server;
}
}
// try the PlatformMBeanServer (JDK 1.5 specific)
try
{
Class clzFactory = Class.forName("java.lang.management.ManagementFactory");
MBeanServer server = (MBeanServer) ClassHelper.invokeStatic(clzFactory,
"getPlatformMBeanServer", ClassHelper.VOID);
if (sDefaultDomain == null || sDefaultDomain.length() == 0 ||
server.getDefaultDomain().equals(sDefaultDomain))
{
return server;
}
}
catch (Exception eIgnore) {}
return MBeanServerFactory.createMBeanServer(sDefaultDomain);
}
catch (Exception e)
{
throw Base.ensureRuntimeException(e, "Failed to locate the server");
}
}
/**
* Find the JMXServiceURL for the MBeanConnector used by the
* Coherence JMX framework.
*
* @param sDefaultDomain the default domain name
* @param deps the {@link GatewayDependencies} containing the
* management configuration to use
*
* @return JMXServiceUrl for the MBeanConnector or null if no Connector is
* running.
*/
public static JMXServiceURL findJMXServiceUrl(String sDefaultDomain, GatewayDependencies deps)
{
if (sDefaultDomain == null || sDefaultDomain.isEmpty())
{
sDefaultDomain = deps.getDefaultDomain();
}
MBeanServerFinder finder = deps.getMBeanServerFinder();
if (finder != null)
{
return finder.findJMXServiceUrl(sDefaultDomain);
}
return null;
}
/**
* Find all MBeans matching to the specified query at a local MBeanServer and
* register them with the specified Registry.
*
* Note: the MBeanServer that the query runs against is not necessarily the
* one used by the Registry.
*
* @param sMBeanServerDomain the default domain of the MBeanServer where the
* query should be executed. If this value is empty
* or null the Coherence default domain is used
* @param sQuery a JMX query string that will be used to find
* the MBeans
* @param sPrefix a target location to prepend to converted MBean names
* @param registry a Registry to register the JMX query results with
*/
public static void registerQueryMBeans(String sMBeanServerDomain,
String sQuery, String sPrefix, Registry registry)
{
if (sPrefix.length() > 0)
{
sPrefix += ',';
}
try
{
ObjectName onameQuery = new ObjectName(sQuery);
MBeanServer mbs = findMBeanServer(sMBeanServerDomain);
Set setNames = mbs.queryNames(onameQuery, null);
for (ObjectName oname : setNames)
{
String sSourceDomain = oname.getDomain();
String sSourceName = oname.getKeyPropertyListString();
String sTargetName = sPrefix + "Domain=" + sSourceDomain + ','
+ sSourceName.replaceFirst("type=", "subType=");
registry.register(registry.ensureGlobalName(sTargetName),
new MBeanReference(oname, mbs));
}
}
catch (MalformedObjectNameException e)
{
// the exception provides all necessary information about invalid config
// and [we assume] makes it quite clear that the definition is ignored
err("Ignoring failed registration request: " + e +
"; " + getStackTrace(e));
}
}
/**
* Register the specified NamedCache with the cluster registry.
*
* @param cache the NamedCache object to register
* @param sContext the cache context (tier)
*/
public static void registerCacheMBean(NamedCache cache, String sContext)
{
registerCacheMBean(cache.getCacheService(), cache.getCacheName(), sContext, cache);
}
/**
* Register the specified map with the cluster registry.
*
* @param service the CacheService that the cache belongs to
* @param sCacheName the cache name
* @param sContext the cache context (tier)
* @param map the map object to register
*/
public static void registerCacheMBean(CacheService service, String sCacheName,
String sContext, Map map)
{
try
{
Cluster cluster = service.getCluster();
Registry registry = cluster.getManagement();
if (registry != null)
{
String sName = Registry.CACHE_TYPE +
"," + Registry.KEY_SERVICE + service.getInfo().getServiceName() +
",name=" + sCacheName;
sName = registry.ensureGlobalName(sName);
sName = sName + (sContext == null ? "" : "," + sContext);
registry.register(sName, map);
}
}
catch (Throwable e)
{
CacheFactory.log("Failed to register cache \"" +
sCacheName + "\"; " + e, CacheFactory.LOG_WARN);
}
}
/**
* Unregister all managed objects that are related to the specified cache
* from the registry.
*
* @param service the CacheService that the cache belongs to
* @param sCacheName the cache name
*/
public static void unregisterCacheMBean(CacheService service, String sCacheName)
{
unregisterCacheMBean(service, sCacheName, null);
}
/**
* Unregister all managed objects that are related to the specified cache
* from the registry.
*
* @param sServiceName the CacheService name
* @param sCacheName the cache name
* @param sContext the cache context (tier)
*/
public static void unregisterCacheMBean(String sServiceName, String sCacheName, String sContext)
{
unregisterCacheMBean(CacheFactory.getCluster(), sServiceName, sCacheName, sContext);
}
/**
* Unregister all managed objects that are related to the specified cache
* from the registry.
*
* @param service the CacheService that the cache belongs to
* @param sCacheName the cache name
* @param sContext the cache context (tier)
*/
public static void unregisterCacheMBean(CacheService service, String sCacheName, String sContext)
{
unregisterCacheMBean(service.getCluster(), service.getInfo().getServiceName(), sCacheName, sContext);
}
/**
* Unregister all managed objects that are related to the specified cache
* from the registry.
*
* @param cluster the Cluster object
* @param sServiceName the CacheService that the cache belongs to
* @param sCacheName the cache name
* @param sContext the cache context (tier)
*/
public static void unregisterCacheMBean(Cluster cluster, String sServiceName, String sCacheName, String sContext)
{
try
{
Registry registry = cluster.getManagement();
if (registry != null)
{
String sName = Registry.CACHE_TYPE + "," + Registry.KEY_SERVICE + sServiceName +
",name=" + sCacheName;
sName = registry.ensureGlobalName(sName);
sName = sName + (sContext == null ? "" : "," + sContext);
registry.unregister(sName);
}
}
catch (Throwable e) {}
}
/**
* Unregister all managed objects related to the given cache name and
* context from the cluster registry.
*
* @param sCacheName the cache name
* @param sContext the cache context (tier)
*/
public static void unregisterCacheMBean(String sCacheName, String sContext)
{
try
{
Cluster cluster = CacheFactory.getCluster();
Registry registry = cluster.getManagement();
if (registry != null)
{
Member member = cluster.getLocalMember();
// use "*" in lieu of service name
String sPattern = Registry.CACHE_TYPE +
",name=" + sCacheName +
(member == null ? "" : ",nodeId=" + member.getId()) +
(sContext == null ? "" : (',' + sContext));
registry.unregister(sPattern);
}
}
catch (Throwable e) {}
}
/**
* Unregister all managed objects related to the given cache name and
* context from the cluster registry.
*
* @param cache the cache
* @param sContext the cache context (tier)
*/
public static void unregisterCacheMBean(NamedCache cache, String sContext)
{
unregisterCacheMBean(cache.getCacheService(), cache.getCacheName(), sContext);
}
/**
* Ensure that there is an instance of a local MBean of the specified type
* registered with the default MBeansServer.
*
* @param sName an MBean name
*
* @return the MBeanReference for the registered MBean
*/
public static MBeanReference ensureSingletonMBean(String sName)
{
try
{
MBeanServer server = findMBeanServer();
ObjectName name = new ObjectName("Coherence:type=" + sName);
if (!server.isRegistered(name))
{
Object oBean = Class.forName(MANAGEABLE + sName + "MBean").
newInstance();
server.registerMBean(oBean, name);
}
return new MBeanReference(name, server);
}
catch (Throwable e)
{
throw Base.ensureRuntimeException(e);
}
}
/**
* Create a DynamicMBean driven by maps containing attribute descriptions and
* values.
*
* @param mapDescr Map<String, String> keyed by the attribute names with
* values being attribute descriptions
* @param mapValue Map<String, Object> keyed by the attribute names with
* values being attribute values
*
* @return a DynamicMBean
*/
public static DynamicMBean createMapAdapterMbean(
Map mapDescr, Map mapValue)
{
try
{
DynamicMBean bean = (DynamicMBean)
Class.forName(MANAGEABLE + "MapAdapter").newInstance();
ClassHelper.invoke(bean, "_initialize", new Object[] {mapDescr, mapValue});
return bean;
}
catch (Throwable e)
{
throw Base.ensureRuntimeException(e);
}
}
/**
* Start a {@link JMXConnectorServer}. This method is used to expose the
* specified MBeanServer to external agents (such as JConsole) using RMI.
*
* This method also allows for the RMI functionality to emulate the
* JConsole remote connection security.
*
* For the list of relevant system properties please see:
*
* Java 1.6 Agent Documentation.
*
* @param sAddr host to bind to
* @param nRegPort port used for the JMX RMI registry
* @param nConPort port used for the JMX RMI connection
* @param mbs {@link MBeanServer} that contains Coherence MBeans
* @param mapEnv a set of attributes to control the new connector server's
* behavior
*
* @return a JMXConnectorServer that has been started
*
* @see JMXConnectorServerFactory
*/
public static JMXConnectorServer startRmiConnector(String sAddr, int nRegPort,
int nConPort, MBeanServer mbs, Map mapEnv)
{
String sPriorRmiHostname = System.getProperty("java.rmi.server.hostname");
try
{
String sHost = sAddr == null ? null : InetAddressHelper.getLocalAddress(sAddr).getHostAddress(); // translate CIDR format
// get the System Properties to copy to the JMX Connector
// NOTE: atn and ssl default to false unless any atn/access/reg are setup in which case they default to true
String sAuthFile = System.getProperty(
"com.sun.management.jmxremote.password.file");
String sAccessFile = System.getProperty(
"com.sun.management.jmxremote.access.file");
String sAuth = System.getProperty(
"com.sun.management.jmxremote.authenticate",
(sAuthFile != null || sAccessFile != null || nRegPort != 0 ||
(mapEnv != null && "true".equals(mapEnv.get("com.oracle.coherence.tcmp.ssl"))))
? "true" : "false");
String sSSL = System.getProperty(
"com.sun.management.jmxremote.ssl", sAuth);
if (sAuth.equalsIgnoreCase("true"))
{
String sJavaHome = System.getProperty("java.home");
if (sAuthFile == null)
{
Path pathPreJava11 = Paths.get(sJavaHome, "lib", "management", "jmxremote.password");
sAuthFile = pathPreJava11.toFile().exists()
? pathPreJava11.toString()
: Paths.get(sJavaHome, "conf", "management", "jmxremote.password").toString(); // Java 11
}
if (sAccessFile == null)
{
Path pathPreJava11 = Paths.get(sJavaHome, "lib", "management", "jmxremote.access");
sAccessFile = pathPreJava11.toFile().exists()
? pathPreJava11.toString()
: Paths.get(sJavaHome, "conf", "management", "jmxremote.access").toString(); // Java 11
}
}
if (mapEnv == null)
{
mapEnv = new HashMap();
}
mapEnv.put("jmx.remote.x.daemon", "true"); // compensation for COH-14091
if (sAuthFile != null
&& mapEnv.get("jmx.remote.x.password.file") == null)
{
mapEnv.put("jmx.remote.x.password.file", sAuthFile);
}
if (sAccessFile != null
&& mapEnv.get("jmx.remote.x.access.file") == null)
{
mapEnv.put("jmx.remote.x.access.file", sAccessFile);
}
if (sSSL.equalsIgnoreCase("true"))
{
try
{
if (mapEnv.get("jmx.remote.rmi.client.socket.factory") == null)
{
mapEnv.put("jmx.remote.rmi.client.socket.factory",
Class.forName("javax.rmi.ssl.SslRMIClientSocketFactory")
.newInstance());
}
if (mapEnv.get("jmx.remote.rmi.server.socket.factory") == null)
{
mapEnv.put("jmx.remote.rmi.server.socket.factory",
Class.forName("javax.rmi.ssl.SslRMIServerSocketFactory")
.newInstance());
}
}
catch (ClassNotFoundException e)
{
String sMsg = "JMXConnectorServer not started. SSL security requires" +
" the Java Dynamic Management Kit or Java 1.5.";
throw ensureRuntimeException(e, sMsg);
}
}
else if (!InetAddressHelper.isAnyLocalAddress(sHost))
{
// RMI's socket factory API is only capable of creating servers which listen on wildcard
// we use a custom factory to force ourselves onto a different IP if needed
// Unfortunately we can't help on SSL
RMISocketFactory factory = new RMISocketFactory()
{
public ServerSocket createServerSocket(int nPort)
throws IOException
{
// TODO: register a protocol prefix for RMI once our multiplexing layer supports it, for now
// JMX is the only public protocol supported on our multiplexed sockets.
ServerSocket socket = TcpSocketProvider.DEMULTIPLEXED.openServerSocket();
socket.bind(sHost == null ? new InetSocketAddress(nPort) : new InetSocketAddress(sHost, nPort));
return socket;
}
public Socket createSocket(String sRemoteHost, int nPort)
throws IOException
{
return RMISocketFactory.getDefaultSocketFactory().createSocket(sRemoteHost, nPort);
}
};
mapEnv.put("jmx.remote.rmi.server.socket.factory", factory);
try
{
// if not set then non-wildcard binding works but nothing can connect as the RMI stub may contain
// the wrong address. We restore the original value in the finally block at the end of this method
System.setProperty("java.rmi.server.hostname", sHost);
}
catch (Throwable e)
{
}
}
for (int i = 0; ; ++i)
{
int nPortAttempt = nConPort;
// Bind and release on the configured port. This is a workaround for two JMX issues:
// - JMX doesn't complain if asked to use a port which is not available and appears
// to succeed but no client would be able to connect
// - JMX doesn't properly handle ephemeral ports, specifically if you bind to
// port 0 you will get an ephemeral binding but connector.getAddress() will
// simply return the same URL which was passed in and you won't know what
// you've bound to. To work around this we pre-compute an ephemeral port to
// try with
try (ServerSocket srv = new ServerSocket(nPortAttempt, 0))
{
nPortAttempt = srv.getLocalPort(); // in case it was ephemeral
}
JMXServiceURL url;
if (nRegPort == 0)
{
// dynamic url
url = new JMXServiceURL("rmi", sHost, nPortAttempt);
}
else
{
LocateRegistry.createRegistry(nRegPort);
// static url
url = new JMXServiceURL("service:jmx:rmi://" + sHost
+ ":" + nPortAttempt + "/jndi/rmi://" + sHost + ":"
+ nRegPort + "/server");
}
try
{
JMXConnectorServer connector = JMXConnectorServerFactory.newJMXConnectorServer(url, mapEnv, mbs);
connector.start();
return connector;
}
catch (IOException e)
{
if (nConPort != 0 || i > Short.MAX_VALUE)
{
throw e;
}
// else; the ephemeral port may have been concurrently taken; retry
}
}
}
catch (Exception e)
{
throw ensureRuntimeException(e, "Could not start JMXConnectorServer");
}
finally
{
try
{
if (sPriorRmiHostname == null)
{
System.getProperties().remove("java.rmi.server.hostname");
}
else
{
System.setProperty("java.rmi.server.hostname", sPriorRmiHostname);
}
}
catch (Throwable e) {}
}
}
/**
* Start a com.sun.jdmk.comm.HtmlAdaptorServer, which is a part of
* the Sun JMX reference implementation. It is being created via
* reflection to avoid a runtime dependency to this library.
*
* @param nPort port to bind the HTTP server to
* @param mbs MBeanServer that this HTTP server will expose
*
* @return an HtmlAdaptorServer that has been started
*/
public static Object startHttpConnector(int nPort, MBeanServer mbs)
{
try
{
// this is similar to HttpAdapter in TDE core-net
String sAdapter = "HttpAdapter:port=" + nPort;
ObjectName nameAdapter = new ObjectName(sAdapter);
Object adaptorServer = ClassHelper.newInstance(
Class.forName("com.sun.jdmk.comm.HtmlAdaptorServer"),
new Object[] {Integer.valueOf(nPort)});
mbs.registerMBean(adaptorServer, nameAdapter);
mbs.invoke(nameAdapter, "start", null, null);
return adaptorServer;
}
catch (Exception e)
{
throw ensureRuntimeException(e,
"Please ensure that JMX RI (jmxtools.jar, jmxri.jar) is in the classpath");
}
}
/**
* Create an escape-sequence string that allows for special characters to
* be included in a JMX ObjectName.
*
* @param s the string to be quoted
*
* @return the quoted string
*
* @since Coherence 3.4
*/
public static String quote(String s)
{
return quote(s, /*fKey*/ true);
}
/**
* Create an escape-sequence string that allows for special characters to
* be included in a JMX ObjectName.
*
* @param s the string to be quoted
* @param fKey true if a key in the ObjectName is being quoted
*
* @return the quoted string
*
* @since Coherence 19.1.0.0
*/
protected static String quote(String s, boolean fKey)
{
String sDelims = "\n\\\"" + (fKey ? "*?" : "");
StringTokenizer tokens = new StringTokenizer(s, sDelims, true);
StringBuilder sb = new StringBuilder("\"");
while (tokens.hasMoreTokens())
{
String sToken = tokens.nextToken();
if (sToken.length() == 1 && sDelims.contains(sToken))
{
sb.append('\\');
if (sToken.equals("\n"))
{
sb.append('n');
}
else
{
sb.append(sToken);
}
}
else
{
sb.append(sToken);
}
}
sb.append('"');
return sb.toString();
}
/**
* Unquote a string iff given string is quoted otherwise return original string.
*
* @param s the string
*
* @return the unquoted string
*
* @since Coherence 19.1.0.0.0
*/
public static String safeUnquote(String s)
{
if (s.startsWith("\"") && s.endsWith("\""))
{
return s.replaceAll("^\"|\"$", "");
}
return s;
}
/**
* Convert a string returned from the {@link #quote} method to the original
* string.
*
* @param s the string to be unquoted
*
* @return the unquoted string
*
* @throws IllegalArgumentException if the passed string could not have
* been returned by the {@link #quote} method; for instance
* if it does not begin and end with a quote (")
*
* @since Coherence 3.4
*/
public static String unquote(String s)
{
String sDelims = "\"\\";
StringTokenizer tokens = new StringTokenizer(s, sDelims, true);
StringBuilder sb = new StringBuilder("");
if (tokens.countTokens() == 1)
{
return s;
}
if (tokens.nextToken().equals("\""))
{
while (tokens.hasMoreTokens())
{
String sToken = tokens.nextToken();
if (sDelims.contains(sToken))
{
if (tokens.hasMoreTokens())
{
String sNextToken = tokens.nextToken();
if (sNextToken.startsWith("n"))
{
sb.append('\n');
sb.append(sNextToken.substring(1));
}
else
{
sb.append(sNextToken);
}
}
}
else
{
sb.append(sToken);
}
}
}
else
{
throw new IllegalArgumentException("Argument not quoted");
}
return sb.toString();
}
/**
* Determine if the string requires quotes.
*
* @param s the string to be quoted
*
* @return true iff the {@link #quote} method needs to be called
*
* @since Coherence 3.4
*/
public static boolean isQuoteRequired(String s)
{
return isQuoteRequired(s, /*fKey*/ true);
}
/**
* Determine if the string requires quotes.
*
* @param s the string to be quoted
* @param fKey true if a key in the ObjectName is being quoted
*
* @return true iff the {@link #quote} method needs to be called
*
* @since Coherence 3.4
*/
public static boolean isQuoteRequired(String s, boolean fKey)
{
return !isQuoted(s)
&& (s.indexOf(',') != -1
|| s.indexOf('=') != -1
|| s.indexOf(':') != -1
|| !quote(s, fKey).equals("\"" + s + "\""));
}
/**
* Return {@code true} if the specified string is already quoted.
*
* @param s the string to check
*
* @return {@code true} if the specified string is already quoted
*/
private static boolean isQuoted(String s)
{
int nLen = s.length();
return nLen >= 2
&& s.charAt(0) == '"'
&& s.charAt(nLen - 1) == '"';
}
/**
* Return a quoted {@link ObjectName#getKeyPropertyListString KeyPropertyString}
* or a quoted canonical name. Wildcard and AnyCharacter (* and ? respectively)
* are not escaped when present on the value of a key value pair.
*
* @param sCanonical a string to be quoted
*
* @return a quoted and escape-sequence string
*
* @throws MalformedObjectNameException if the name is invalid
*
* @since Coherence 3.4
*/
public static String quoteCanonical(String sCanonical)
throws MalformedObjectNameException
{
int ofDomain = sCanonical.indexOf(':');
int ofEquals = sCanonical.indexOf('=');
String sName = sCanonical;
StringBuilder sbCanonical = new StringBuilder();
// Note: a colon could be a part of a key-value pair
// (e.g. "domain:k1=v:1,k2=v2" or "k1=v:1,k2=v2")
if (ofDomain >= 0 && (ofEquals < 0 || ofDomain < ofEquals))
{
// strip the domain part of the name
sName = sCanonical.substring(ofDomain + 1);
sbCanonical.append(sCanonical.substring(0, ofDomain + 1));
}
String[] asPairs = Base.parseDelimitedString(sName , ',');
int cPairs = asPairs.length;
for (int i = 0; i < cPairs; i++)
{
StringBuilder sbValue = new StringBuilder();
String sPair = asPairs[i];
if (asPairs[i].equals("*"))
{
sbCanonical.append(asPairs[i]);
}
else
{
int ofValue = sPair.indexOf('=');
if (ofValue > -1)
{
String[] asPair = Base.parseDelimitedString(asPairs[i], '=');
String sKey = asPair[0];
if (isQuoteRequired(sKey))
{
sKey = quote(sKey);
}
sbCanonical.append(sKey).append('=');
sbValue.append(sPair.substring(ofValue + 1));
}
else
{
throw new MalformedObjectNameException("ObjectName \""
+ sCanonical + "\" is invalid.");
}
String sValue = sbValue.toString();
if (isQuoteRequired(sValue, /*fKey*/ false))
{
sValue = quote(sValue, /*fKey*/ false);
}
sbCanonical.append(sValue);
if (i + 1 < cPairs)
{
sbCanonical.append(',');
}
}
}
return sbCanonical.toString();
}
/**
* Return true if the Canonical name is prefixed with the domain name.
*
* @param sCanonical a canonical MBean name or key property list
*
* @return true iff the name contains the domain prefix
*
* @since Coherence 3.6
*/
public static boolean hasDomain(String sCanonical)
{
if (sCanonical == null)
{
return false;
}
int ofDomain = sCanonical.indexOf(':');
int ofEquals = sCanonical.indexOf('=');
// Note: a colon could be a part of a key-value pair
// (e.g. "domain:k1=v:1,k2=v2" or "k1=v:1,k2=v2")
return ofDomain >= 0 && (ofEquals < 0 || ofDomain < ofEquals);
}
/**
* Ensure the Canonical name is prefixed with the domain name.
*
* @param sCanonical a canonical MBean name or key property list
*
* @return a Canonical name that is prefixed with the domain name.
*
* @since Coherence 12.2.1.4
*/
public static String ensureDomain(String sCanonical)
{
if (sCanonical == null || hasDomain(sCanonical) && sCanonical.indexOf(':') > 0)
{
return sCanonical;
}
String sDomain = CacheFactory.getCluster().getManagement().getDomainName();
if (sDomain == null || sDomain.length() == 0)
{
sDomain = getDefaultDomain();
}
if (sDomain == null)
{
sDomain = "Coherence";
}
if (sCanonical.indexOf(':') == 0)
{
return sDomain + sCanonical;
}
else
{
return sDomain + ":" + sCanonical;
}
}
/**
* Remove the domain prefix from the canonical name if one exists.
*
* @param sCanonical a canonical MBean name or key property list
*
* @return the canonical name stripped of the domain prefix
*
* @since Coherence 3.6
*/
public static String stripDomain(String sCanonical)
{
return hasDomain(sCanonical)
? sCanonical.substring(sCanonical.indexOf(':') + 1)
: sCanonical;
}
/**
* Compare two {@link Registry#ensureGlobalName(String) global MBean names}
* forcing numeric comparison of the node ID while using string comparison
* on all other key properties. For example, the following order is enforced:
*
* Coherence:type=Node,nodeId=2 < Coherence:type=Node,nodeId=10
* Coherence:type=Cache,nodeId=20 < Coherence:type=Node,nodeId=1
*
* If the key sets are different the lexicographical comparison is used.
*
* @param oname1 the first ObjectName to be compared
* @param oname2 the second ObjectName to be compared
*
* @return a negative integer, zero, or a positive integer as the first name
* is less than, equal to, or greater than the second one
*
* @since Coherence 3.6
*/
public static int compareKeyList(ObjectName oname1, ObjectName oname2)
{
Map mapKeyList1 = oname1.getKeyPropertyList();
Map mapKeyList2 = oname2.getKeyPropertyList();
Set setKeys1 = mapKeyList1.keySet();
Set setKeys2 = mapKeyList2.keySet();
if (setKeys1.equals(setKeys2))
{
for (Object o : mapKeyList1.keySet())
{
String sKey = (String) o;
String sValue1 = (String) mapKeyList1.get(sKey);
String sValue2 = (String) mapKeyList2.get(sKey);
if ((sKey + '=').equals(Registry.KEY_NODE_ID))
{
int n1 = Integer.parseInt(sValue1);
int n2 = Integer.parseInt(sValue2);
if (n1 != n2)
{
return n1 - n2;
}
}
else
{
int iCompare = sValue1.compareTo(sValue2);
if (iCompare != 0)
{
return iCompare;
}
}
}
return 0;
}
// use the lexicographical comparison
return oname1.getCanonicalName().compareTo(oname2.getCanonicalName());
}
// ----- annotation-related helpers -------------------------------------
/**
* Retrieve the description for the MBean from the MBean interface annotation.
*
* @param clzMBeanIface the MBean interface
* @param info the {@link MBeanInfo} for the MBean
*
* @return the MBean description
*
* @since Coherence 12.1.2
*/
public static String getDescription(Class> clzMBeanIface, MBeanInfo info)
{
String sDesc = null;
if (clzMBeanIface != null)
{
Description desc = clzMBeanIface.getAnnotation(Description.class);
if (desc != null)
{
sDesc = desc.value();
}
}
return sDesc == null ? info.getDescription() : sDesc;
}
/**
* Retrieve a description for a particular attribute by finding a
* {@link Description} annotation on the getter method for the attribute.
* If a description is not found on the getter method, the setter will
* be checked.
*
* @param clzMBeanIface the MBean interface
* @param info the {@link MBeanAttributeInfo} for the attribute
*
* @return the description for an attribute
*
* @since Coherence 12.1.2
*/
public static String getDescription(Class clzMBeanIface, MBeanAttributeInfo info)
{
String sDesc = null;
String sMethName = info.getName();
if (info.isIs())
{
sMethName = "is" + sMethName;
}
else
{
sMethName = "get" + sMethName;
}
Method methGetter = findMethod(clzMBeanIface, sMethName);
if (methGetter != null)
{
Description descGetter = methGetter.getAnnotation(Description.class);
if (descGetter != null)
{
sDesc = descGetter.value();
}
else
{
// try the setter
sMethName = "set" + info.getName();
Method methSetter = findMethod(clzMBeanIface, sMethName);
if (methSetter != null)
{
Description descSetter = methSetter.getAnnotation(Description.class);
if (descSetter != null)
{
sDesc = descSetter.value();
}
}
}
}
return sDesc == null ? info.getDescription() : sDesc;
}
/**
* Retrieve a description for the particular {@link MBeanOperationInfo} by
* finding a {@link Description} annotation on the corresponding method.
*
*
* @param clzMBeanIface the MBean interface
* @param info the {@link MBeanOperationInfo}
*
* @return the description for an operation
*
* @since Coherence 12.1.2
*/
public static String getDescription(Class clzMBeanIface, MBeanOperationInfo info)
{
String sDesc = null;
Method meth = findMethod(clzMBeanIface, info);
if (meth != null)
{
Description desc = meth.getAnnotation(Description.class);
if (desc != null)
{
sDesc = desc.value();
}
}
return sDesc == null ? info.getDescription() : sDesc;
}
/**
* Retrieve the parameter name for the specified parameter by finding a
* {@link Description} annotation on the corresponding method.
*
* @param clzMBeanIface the MBean interface
* @param infoOp the {@link MBeanOperationInfo} for the op
* @param infoParam the {@link MBeanParameterInfo} for the parameter
* @param iParam zero-based sequence number of the parameter
*
* @return the name to use for the given parameter
*
* @since Coherence 12.1.2
*/
public static String getParameterName(Class clzMBeanIface,
MBeanOperationInfo infoOp, MBeanParameterInfo infoParam, int iParam)
{
String sName = infoParam.getName();
Method meth = findMethod(clzMBeanIface, infoOp);
if (meth != null)
{
Description desc = getParameterAnnotation(meth, iParam, Description.class);
if (desc != null)
{
sName = desc.value();
}
}
return sName;
}
/**
* Return an {@link MBeanNotificationInfo} if a {@link Notification}
* annotation is present on the provided MBean interface.
*
* @param clzMBeanIface the MBean interface
*
* @return an array of MBeanNotificationInfo of size 0 or 1 based on the
* presence of a Notification annotation.
*/
public static MBeanNotificationInfo[] getNotificationInfo(Class> clzMBeanIface)
{
Notification anno = clzMBeanIface.getAnnotation(Notification.class);
if (anno != null)
{
return new MBeanNotificationInfo[] {
new MBeanNotificationInfo(anno.types(), anno.className(), anno.description())};
}
return new MBeanNotificationInfo[0];
}
/**
* Retrieve an {@link Annotation} for a parameter to a method.
*
* @param the Annotation sub type
* @param meth the {@link Method}
* @param iParam zero-based index of the parameter
* @param clzAnno the Annotation {@link Class} to be retrieved
*
* @return the annotation or null if no {@link Annotation} is found
*/
protected static A getParameterAnnotation(
Method meth, int iParam, Class clzAnno)
{
for (Annotation a : meth.getParameterAnnotations()[iParam])
{
if (clzAnno.isInstance(a))
{
return clzAnno.cast(a);
}
}
return null;
}
/**
* Find a {@link Method} for the specified {@link MBeanOperationInfo} in
* the specified MBean class or interface.
*
* @param clzMBean the MBean class (interface)
* @param op the {@link MBeanOperationInfo}
*
* @return the {@link Method} or null if no method is found
*/
protected static Method findMethod(Class clzMBean, MBeanOperationInfo op)
{
MBeanParameterInfo[] aParam = op.getSignature();
int cParams = aParam.length;
String[] asParamTypes = new String[cParams];
for (int i = 0; i < cParams; i++)
{
asParamTypes[i] = aParam[i].getType();
}
return findMethod(clzMBean, op.getName(), asParamTypes);
}
/**
* Find a {@link Method} with the specified name and parameter types in
* the specified class.
*
* @param clz the class (interface)
* @param sName the {@link Method} name
* @param asParamTypes the array of Strings representing parameter types
*
* @return the {@link Method} or null if no method is found
*/
protected static Method findMethod(Class clz, String sName, String... asParamTypes)
{
ClassLoader loader = clz.getClassLoader();
int cParams = asParamTypes.length;
Class[] aclzParam = new Class[cParams];
try
{
for (int i = 0; i < cParams; i++)
{
aclzParam[i] = classForName(asParamTypes[i], loader);
}
return ClassHelper.findMethod(clz, sName, aclzParam, false);
}
catch (Exception e)
{
return null;
}
}
/**
* Find a {@link Class} for the type name. This method also handles primitive
* types.
*
* Note: for security related reasons this method has package private access.
*
* @param sName the name of the type
* @param loader the {@link ClassLoader} to use
*
* @return the {@link Class} for the type name
*
* @throws ClassNotFoundException if no such {@link Class} is found using the
* provided {@link ClassLoader}
*/
static Class classForName(String sName, ClassLoader loader)
throws ClassNotFoundException
{
Class clz = SCALAR_TYPES.get(sName);
if (clz == null)
{
clz = Class.forName(sName, false, loader);
}
return clz;
}
/**
* Helper class to expose a {@link Filter} object as a {@link QueryExp}.
*/
static public class QueryExpFilter
implements QueryExp
{
public QueryExpFilter(Filter filter)
{
f_filter = filter;
}
@Override
public boolean apply(ObjectName name)
{
return f_filter == null || f_filter.evaluate(name);
}
@Override
public void setMBeanServer(MBeanServer s)
{
}
// ----- data fields ----------------------------------------------
/**
* The underlying filter.
*/
private final Filter f_filter;
}
// ----- constants ------------------------------------------------------
/**
* A map of scalar types (classes) keyed by the corresponding JMX signatures.
*
* @see
* JMX 1.2 specification; Chapter 3, Basic Data Types
*/
public static final Map SCALAR_TYPES;
/**
* A map of scalar SimpleTypes (classes) keyed by the corresponding JMX signatures.
*/
public static final Map SCALAR_SIMPLETYPES;
static
{
Map mapScalar = new HashMap<>();
mapScalar.put("java.lang.Boolean", Boolean.TYPE);
mapScalar.put("boolean", Boolean.TYPE);
mapScalar.put("java.lang.Character", Character.TYPE);
mapScalar.put("char", Character.TYPE);
mapScalar.put("java.lang.Byte", Byte.TYPE);
mapScalar.put("byte", Byte.TYPE);
mapScalar.put("java.lang.Short", Short.TYPE);
mapScalar.put("short", Short.TYPE);
mapScalar.put("java.lang.Integer", Integer.TYPE);
mapScalar.put("int", Integer.TYPE);
mapScalar.put("java.lang.Long", Long.TYPE);
mapScalar.put("long", Long.TYPE);
mapScalar.put("java.lang.Float", Float.TYPE);
mapScalar.put("float", Float.TYPE);
mapScalar.put("java.lang.Double", Double.TYPE);
mapScalar.put("double", Double.TYPE);
SCALAR_TYPES = Collections.unmodifiableMap(mapScalar);
Map mapSimple = new HashMap<>();
mapSimple.put("java.lang.Boolean", SimpleType.BOOLEAN);
mapSimple.put("boolean", SimpleType.BOOLEAN);
mapSimple.put("java.lang.Character", SimpleType.CHARACTER);
mapSimple.put("character", SimpleType.CHARACTER);
mapSimple.put("java.lang.Byte", SimpleType.BYTE);
mapSimple.put("byte", SimpleType.BYTE);
mapSimple.put("java.lang.Short", SimpleType.SHORT);
mapSimple.put("short", SimpleType.SHORT);
mapSimple.put("java.lang.Integer", SimpleType.INTEGER);
mapSimple.put("int", SimpleType.INTEGER);
mapSimple.put("java.lang.Long", SimpleType.LONG);
mapSimple.put("long", SimpleType.LONG);
mapSimple.put("java.lang.Float", SimpleType.FLOAT);
mapSimple.put("float", SimpleType.FLOAT);
mapSimple.put("java.lang.Double", SimpleType.DOUBLE);
mapSimple.put("double", SimpleType.DOUBLE);
mapSimple.put("java.lang.String", SimpleType.STRING);
mapSimple.put("string", SimpleType.STRING);
SCALAR_SIMPLETYPES = Collections.unmodifiableMap(mapSimple);
}
/**
* Package name for TDE-based dynamic MBean objects.
*/
private static final String MANAGEABLE = "com.tangosol.coherence.component.manageable.";
}