All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jolokia.client.jmxadapter.RemoteJmxAdapter Maven / Gradle / Ivy

package org.jolokia.client.jmxadapter;

import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryEval;
import javax.management.QueryExp;
import javax.management.RuntimeMBeanException;
import javax.management.openmbean.InvalidOpenTypeException;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;

import org.jolokia.client.J4pClient;
import org.jolokia.client.J4pClientBuilder;
import org.jolokia.client.exception.J4pBulkRemoteException;
import org.jolokia.client.exception.J4pException;
import org.jolokia.client.exception.J4pRemoteException;
import org.jolokia.client.exception.UncheckedJmxAdapterException;
import org.jolokia.client.request.J4pExecRequest;
import org.jolokia.client.request.J4pExecResponse;
import org.jolokia.client.request.J4pListRequest;
import org.jolokia.client.request.J4pListResponse;
import org.jolokia.client.request.J4pQueryParameter;
import org.jolokia.client.request.J4pReadRequest;
import org.jolokia.client.request.J4pReadResponse;
import org.jolokia.client.request.J4pRequest;
import org.jolokia.client.request.J4pResponse;
import org.jolokia.client.request.J4pSearchRequest;
import org.jolokia.client.request.J4pSearchResponse;
import org.jolokia.client.request.J4pVersionRequest;
import org.jolokia.client.request.J4pVersionResponse;
import org.jolokia.client.request.J4pWriteRequest;
import org.jolokia.server.core.util.ClassUtil;
import org.jolokia.service.serializer.JolokiaSerializer;
import org.jolokia.json.JSONObject;

/**
 * I emulate a subset of the functionality of a native MBeanServerConnector but over a Jolokia
 * connection to the VM , the response types and thrown exceptions attempts to mimic as close as
 * possible the ones from a native Java MBeanServerConnection
 * 

* Operations that are not supported will throw an #UnsupportedOperationException */ public class RemoteJmxAdapter implements MBeanServerConnection { private final J4pClient connector; private String agentId; private HashMap defaultProcessingOptions; protected final Map mbeanInfoCache = new HashMap<>(); String agentVersion; String protocolVersion; public RemoteJmxAdapter(final J4pClient connector) throws IOException { this.connector = connector; try { J4pVersionResponse response = this.unwrapExecute(new J4pVersionRequest()); this.agentVersion = response.getAgentVersion(); this.protocolVersion = response.getProtocolVersion(); JSONObject value = response.getValue(); JSONObject config = (JSONObject) value.get("config"); this.agentId = String.valueOf(config.get("agentId")); } catch (InstanceNotFoundException ignore) { } } public int hashCode() { return this.connector.getUri().hashCode(); } public boolean equals(Object o) { //as long as we refer to the same agent, we may be seen as equivalent return o instanceof RemoteJmxAdapter && this.connector.getUri() .equals(((RemoteJmxAdapter) o).connector.getUri()); } @SuppressWarnings("unused") public RemoteJmxAdapter(final String url) throws IOException { this(new J4pClientBuilder().url(url).build()); } @Override public ObjectInstance createMBean(String className, ObjectName name) { throw new UnsupportedOperationException("createMBean not supported by Jolokia"); } @Override public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName) { throw new UnsupportedOperationException("createMBean not supported over Jolokia"); } @Override public ObjectInstance createMBean( String className, ObjectName name, Object[] params, String[] signature) { throw new UnsupportedOperationException("createMBean not supported over Jolokia"); } @Override public ObjectInstance createMBean( String className, ObjectName name, ObjectName loaderName, Object[] params, String[] signature) { throw new UnsupportedOperationException("createMBean not supported over Jolokia"); } @Override public void unregisterMBean(ObjectName name) { throw new UnsupportedOperationException("unregisterMBean not supported over Jolokia"); } @Override public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException, IOException { J4pListResponse listResponse = this.unwrapExecute(new J4pListRequest(name)); return new ObjectInstance(name, listResponse.getClassName()); } private List listObjectInstances(ObjectName name) throws IOException { J4pListRequest listRequest = new J4pListRequest((String) null); if (name != null) { listRequest = new J4pListRequest(name); } try { final J4pListResponse listResponse = this.unwrapExecute(listRequest); return listResponse.getObjectInstances(name); } catch (MalformedObjectNameException e) { throw new UncheckedJmxAdapterException(e); } catch (InstanceNotFoundException e) { return Collections.emptyList(); } } @Override public Set queryMBeans(ObjectName name, QueryExp query) throws IOException { final HashSet result = new HashSet<>(); for (ObjectInstance instance : this.listObjectInstances(name)) { if (query == null || applyQuery(query, instance.getObjectName())) { result.add(instance); } } return result; } @Override public Set queryNames(ObjectName name, QueryExp query) throws IOException { final HashSet result = new HashSet<>(); // if name is null, use list instead of search if (name == null) { try { name = getObjectName(""); } catch (UncheckedJmxAdapterException ignore) { } } final J4pSearchRequest j4pSearchRequest = new J4pSearchRequest(name); J4pSearchResponse response; try { response = unwrapExecute(j4pSearchRequest); } catch (InstanceNotFoundException e) { return Collections.emptySet(); } final List names = response.getObjectNames(); for (ObjectName objectName : names) { if (query == null || applyQuery(query, objectName)) { result.add(objectName); } } return result; } private boolean applyQuery(QueryExp query, ObjectName objectName) { if (QueryEval.getMBeanServer() == null) { query.setMBeanServer(this.createStandInMbeanServerProxyForQueryEvaluation()); } try { return query.apply(objectName); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new UncheckedJmxAdapterException(e); } } /** * Query evaluation requires an MBeanServer but only to figure out objectInstance * * @return a dynamic proxy dispatching getObjectInstance back to myself */ private MBeanServer createStandInMbeanServerProxyForQueryEvaluation() { return (MBeanServer) Proxy.newProxyInstance( this.getClass().getClassLoader(), new Class[]{MBeanServer.class}, (proxy, method, args) -> { if (method.getName().contains("getObjectInstance") && args.length == 1) { return getObjectInstance((ObjectName) args[0]); } else { throw new UnsupportedOperationException( "This MBeanServer proxy does not support " + method); } }); } private , REQ extends J4pRequest> RESP unwrapExecute(REQ pRequest) throws IOException, InstanceNotFoundException { try { pRequest.setPreferredHttpMethod("POST"); return this.connector.execute(pRequest, defaultProcessingOptions()); } catch (J4pException e) { //noinspection unchecked return (RESP) unwrapException(e); } } private Map defaultProcessingOptions() { if (this.defaultProcessingOptions == null) { this.defaultProcessingOptions = new HashMap<>(); defaultProcessingOptions.put(J4pQueryParameter.MAX_DEPTH, "10"); // in case of stack overflow defaultProcessingOptions.put(J4pQueryParameter.SERIALIZE_EXCEPTION, "true"); } return defaultProcessingOptions; } private static final Set UNCHECKED_REMOTE_EXCEPTIONS = Collections.singleton("java.lang.UnsupportedOperationException"); @SuppressWarnings("rawtypes") protected J4pResponse unwrapException(J4pException e) throws IOException, InstanceNotFoundException { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } else if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } else if (e.getCause() instanceof Error) { throw (Error) e.getCause(); } else if (e.getMessage() .matches("Error: java.lang.IllegalArgumentException : No MBean '.+' found")) { throw new InstanceNotFoundException(); } else if (e instanceof J4pRemoteException && UNCHECKED_REMOTE_EXCEPTIONS.contains(((J4pRemoteException) e).getErrorType())) { throw new RuntimeMBeanException( ClassUtil.newInstance(((J4pRemoteException) e).getErrorType(), e.getMessage())); } else { throw new UncheckedJmxAdapterException(e); } } @Override public boolean isRegistered(ObjectName name) throws IOException { return !queryNames(name, null).isEmpty(); } @Override public Integer getMBeanCount() throws IOException { return this.queryNames(null, null).size(); } @Override public Object getAttribute(ObjectName name, String attribute) throws AttributeNotFoundException, InstanceNotFoundException, IOException { try { final Object rawValue = unwrapExecute(new J4pReadRequest(name, attribute)).getValue(); return adaptJsonToOptimalResponseValue(name, attribute, rawValue, getAttributeTypeFromMBeanInfo(name, attribute)); } catch (UncheckedJmxAdapterException e) { if (e.getCause() instanceof J4pRemoteException && "javax.management.AttributeNotFoundException" .equals(((J4pRemoteException) e.getCause()).getErrorType())) { throw new AttributeNotFoundException((e.getCause().getMessage())); } else { throw e; } } catch (UnsupportedOperationException e) { //JConsole does not seem to like unsupported operation while looking up attributes throw new AttributeNotFoundException(); } } private Object adaptJsonToOptimalResponseValue( ObjectName name, String attribute, Object rawValue, String typeFromMBeanInfo) throws IOException, InstanceNotFoundException { final String qualifiedName = name + "." + attribute; //cache MBeanInfo (and thereby attribute types) if it is not yet cached getMBeanInfo(name); //adjust numeric types, to avoid ClassCastException e.g. in JConsole proxies if (this.isPrimitive(rawValue)) { OpenType attributeType = null; try { attributeType = ToOpenTypeConverter.cachedType(qualifiedName); } catch (OpenDataException ignore) { } if (rawValue instanceof Number && attributeType != null) { return new JolokiaSerializer() .deserializeOpenType(attributeType, rawValue); } else { return rawValue; } } // special case, if the attribute is ObjectName if (rawValue instanceof JSONObject && ((JSONObject) rawValue).size() == 1 && ((JSONObject) rawValue).containsKey("objectName")) { return getObjectName("" + ((JSONObject) rawValue).get("objectName")); } try { return ToOpenTypeConverter.returnOpenTypedValue(qualifiedName, rawValue, typeFromMBeanInfo); } catch (OpenDataException e) { return rawValue; } } private boolean isPrimitive(Object rawValue) { return rawValue == null || rawValue instanceof Number || rawValue instanceof Boolean || rawValue instanceof String || rawValue instanceof Character; } static ObjectName getObjectName(String objectName) { try { return ObjectName.getInstance(objectName); } catch (MalformedObjectNameException e) { throw new UncheckedJmxAdapterException(e); } } @Override public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, IOException { AttributeList result = new AttributeList(); List requests = new ArrayList<>(attributes.length); for (String attribute : attributes) { requests.add(new J4pReadRequest(name, attribute)); } List responses = Collections.emptyList(); try { responses = this.connector.execute(requests, this.defaultProcessingOptions()); } catch (J4pBulkRemoteException e) { responses = e.getResults(); } catch (J4pException ignore) { //will result in empty return } for (Object item : responses) { if (item instanceof J4pReadResponse) { J4pReadResponse value = (J4pReadResponse) item; final String attribute = value.getRequest().getAttribute(); result.add(new Attribute(attribute, adaptJsonToOptimalResponseValue(name, attribute, value.getValue(), getAttributeTypeFromMBeanInfo(name, attribute)))); } } return result; } @Override public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, IOException { final J4pWriteRequest request = new J4pWriteRequest(name, attribute.getName(), attribute.getValue()); try { this.unwrapExecute(request); } catch (UncheckedJmxAdapterException e) { if (e.getCause() instanceof J4pRemoteException) { J4pRemoteException remote = (J4pRemoteException) e.getCause(); if ("javax.management.AttributeNotFoundException".equals(remote.getErrorType())) { throw new AttributeNotFoundException((e.getCause().getMessage())); } if ("java.lang.IllegalArgumentException".equals(remote.getErrorType()) && remote .getMessage() .matches( "Error: java.lang.IllegalArgumentException : Invalid value .+ for attribute .+")) { throw new InvalidAttributeValueException(remote.getMessage()); } } throw e; } } @Override public AttributeList setAttributes(ObjectName name, AttributeList attributes) throws InstanceNotFoundException, IOException { List attributeWrites = new ArrayList<>(attributes.size()); for (Attribute attribute : attributes.asList()) { attributeWrites.add(new J4pWriteRequest(name, attribute.getName(), attribute.getValue())); } try { this.connector.execute(attributeWrites); } catch (J4pException e) { unwrapException(e); } return attributes; } @Override public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, IOException { //jvisualvm may send null for no parameters if (params == null && (signature == null || signature.length == 0)) { params = new Object[0]; } try { final J4pExecResponse response = unwrapExecute( new J4pExecRequest(name, operationName + makeSignature(signature), params)); return adaptJsonToOptimalResponseValue(name, operationName, response.getValue(), getOperationTypeFromMBeanInfo(name, operationName, signature)); } catch (UncheckedJmxAdapterException e) { if (e.getCause() instanceof J4pRemoteException) { throw new MBeanException((Exception) e.getCause()); } throw e; } } private String getOperationTypeFromMBeanInfo(ObjectName name, String operationName, String[] signature) throws IOException, InstanceNotFoundException { final MBeanInfo mBeanInfo = getMBeanInfo(name); if (signature == null) { signature = new String[0]; } for (final MBeanOperationInfo operation : mBeanInfo.getOperations()) { if (operationName.equals(operation.getName()) && Arrays.equals( Arrays.stream(operation.getSignature()).map(MBeanParameterInfo::getType).toArray(String[]::new), signature)) { return operation.getReturnType(); } } return null; } private String makeSignature(String[] signature) { StringBuilder builder = new StringBuilder("("); if (signature != null) { for (int i = 0; i < signature.length; i++) { if (i > 0) { builder.append(','); } builder.append(signature[i]); } } builder.append(')'); return builder.toString(); } @Override public String getDefaultDomain() { return "DefaultDomain"; } @Override public String[] getDomains() throws IOException { Set domains = new HashSet<>(); for (final ObjectName name : this.queryNames(null, null)) { domains.add(name.getDomain()); } return domains.toArray(new String[0]); } @Override public void addNotificationListener( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) { if (!isRunningInJConsole() && !isRunningInJVisualVm() && !isRunningInJmc()) {//just ignore in JConsole/JvisualVM as it wrecks the MBean page throw new UnsupportedOperationException("addNotificationListener not supported for Jolokia"); } } private boolean isRunningInJVisualVm() { final String version = System.getProperty("netbeans.productversion"); return version != null && version.contains("VisualVM"); } private boolean isRunningInJConsole() { return System.getProperty("jconsole.showOutputViewer") != null; } private boolean isRunningInJmc() { return System.getProperty("running.in.jmc") != null; } @Override public void addNotificationListener( ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) { throw new UnsupportedOperationException("addNotificationListener not supported for Jolokia"); } @Override public void removeNotificationListener(ObjectName name, ObjectName listener) { throw new UnsupportedOperationException("removeNotificationListener not supported for Jolokia"); } @Override public void removeNotificationListener( ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) { throw new UnsupportedOperationException("removeNotificationListener not supported by Jolokia"); } @Override public void removeNotificationListener(ObjectName name, NotificationListener listener) { if (!isRunningInJmc() && !isRunningInJConsole() && !isRunningInJVisualVm()) { throw new UnsupportedOperationException("removeNotificationListener not supported by Jolokia"); } } @Override public void removeNotificationListener( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) { throw new UnsupportedOperationException("removeNotificationListener not supported by Jolokia"); } @Override public MBeanInfo getMBeanInfo(ObjectName name) throws InstanceNotFoundException, IOException { MBeanInfo result = this.mbeanInfoCache.get(name); //cache in case client queries a lot for MBean info if (result == null) { final J4pListResponse response = this.unwrapExecute(new J4pListRequest(name)); result = response.getMbeanInfo(); this.mbeanInfoCache.put(name, result); for (MBeanAttributeInfo attr : result.getAttributes()) { final String qualifiedName = name + "." + attr.getName(); try { if (ToOpenTypeConverter.cachedType(qualifiedName) == null) { ToOpenTypeConverter .cacheType(ToOpenTypeConverter.typeFor(attr.getType()), qualifiedName); } } catch (OpenDataException | InvalidOpenTypeException ignore) { } } } return result; } private String getAttributeTypeFromMBeanInfo(final ObjectName name, final String attributeName) throws IOException, InstanceNotFoundException { final MBeanInfo mBeanInfo = getMBeanInfo(name); for (final MBeanAttributeInfo attribute : mBeanInfo.getAttributes()) { if (attributeName.equals(attribute.getName())) { return attribute.getType(); } } return null; } @Override public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException, IOException { final ObjectInstance objectInstance = getObjectInstance(name); try { //try to use classes available in this VM to check compatibility return Class.forName(className) .isAssignableFrom(Class.forName(objectInstance.getClassName())); } catch (ClassNotFoundException e) { if (className.equals(objectInstance.getClassName())) { return true; } else { try { if (ToOpenTypeConverter.cachedType(name.toString()) != null) {//the proprietary Oracle VMs used to have classnames not available in newer openjdk, check for overrides return ToOpenTypeConverter.cachedType(name.toString()).getTypeName().equals(className); } } catch (OpenDataException ignore) { } } return false; } } String getId() { return this.agentId; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy