io.hawt.jmx.RBACRegistry Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat 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 io.hawt.jmx;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.jolokia.server.core.util.EscapeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Generally we do enhanced Jolokia list
operation, but if OSGi env is found we decorate the returned
* objects with RBAC information.
*/
public class RBACRegistry implements RBACRegistryMBean {
public static final Logger LOG = LoggerFactory.getLogger(RBACRegistry.class);
private ObjectName rbacDecorator = null;
private ObjectName objectName;
private MBeanServer mBeanServer;
public void init() throws Exception {
if (objectName == null) {
objectName = new ObjectName("hawtio:type=security,name=RBACRegistry");
}
if (mBeanServer == null) {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
if (mBeanServer != null) {
rbacDecorator = new ObjectName("hawtio:type=security,area=jolokia,name=RBACDecorator");
try {
mBeanServer.registerMBean(this, objectName);
} catch (InstanceAlreadyExistsException iaee) {
// Try to remove and re-register
mBeanServer.unregisterMBean(objectName);
mBeanServer.registerMBean(this, objectName);
}
}
}
public void destroy() throws Exception {
if (objectName != null && mBeanServer != null) {
try {
mBeanServer.unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOG.debug("Error unregistering mbean " + objectName + ". This exception is ignored.", e);
}
}
}
@Override
public Map list() throws Exception {
return list(null);
}
@Override
public Map list(String path) throws Exception {
Map result = new HashMap<>();
// domain -> [mbean, mbean, ...], where mbean is either inline jsonified MBeanInfo or a key to shared
// jsonified MBeanInfo
Map> domains = new HashMap<>();
// if MBean is found to be "special", we can cache JSONified MBeanInfo (an object with "op", "attr" and "desc"
// properties)
// key -> [mbeaninfo, mbeaninfo, ...]
Map> cache = new HashMap<>();
result.put("cache", cache);
result.put("domains", domains);
if (mBeanServer == null) {
return result;
}
Set visited = new HashSet<>();
// see: org.jolokia.server.core.util.jmx.DefaultMBeanServerAccess#each(ObjectName, MBeanEachCallback)
ObjectName pathQuery = objectNameFromPath(path);
for (ObjectName nameObject : mBeanServer.queryNames(pathQuery, null)) {
addMBeanInfo(cache, domains, visited, nameObject);
}
tryAddRBACInfo(result);
return result;
}
/**
* @see org.jolokia.service.jmx.handler.ListHandler#objectNameFromPath(Stack)
*/
@SuppressWarnings("JavadocReference")
private ObjectName objectNameFromPath(String path) throws MalformedObjectNameException {
if (path == null) {
return null;
}
Deque pathStack = EscapeUtil.extractElementsFromPath(path);
String domain = pathStack.pop();
if (pathStack.isEmpty()) {
return new ObjectName(domain + ":*");
}
String props = pathStack.pop();
ObjectName mbean = new ObjectName(domain + ":" + props);
if (mbean.isPattern()) {
throw new IllegalArgumentException("Cannot use an MBean pattern as path (given MBean: " + mbean + ")");
}
return mbean;
}
private void addMBeanInfo(Map> cache, Map> domains, Set visited,
ObjectName nameObject) throws IntrospectionException, ReflectionException {
// Don't add if already visited previously
if (visited.contains(nameObject)) {
return;
}
Map jsonifiedMBeanInfo;
// Let's try to avoid invoking getMBeanInfo. simply domain+type attr is not enough, but we may
// detect special cases
String mbeanInfoKey = isSpecialMBean(nameObject);
if (mbeanInfoKey != null && cache.containsKey(mbeanInfoKey)) {
jsonifiedMBeanInfo = cache.get(mbeanInfoKey);
} else {
// we may have to assemble the info on the fly
try {
MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(nameObject);
// 2nd level of special cases - a bit slower (we had to getMBeanInfo(), but we may try
// cache by MBean's domain and class)
if (mbeanInfoKey == null) {
mbeanInfoKey = isSpecialClass(nameObject, mBeanInfo);
}
if (mbeanInfoKey != null && cache.containsKey(mbeanInfoKey)) {
jsonifiedMBeanInfo = cache.get(mbeanInfoKey);
} else {
// hard work here
jsonifiedMBeanInfo = jsonifyMBeanInfo(mBeanInfo);
}
if (mbeanInfoKey != null) {
cache.put(mbeanInfoKey, jsonifiedMBeanInfo);
}
} catch (InstanceNotFoundException e) {
// Log failure and continue so that we can still send a response back
LOG.debug("Failed to get MBean info for {}. Due to InstanceNotFoundException.", nameObject);
return;
}
}
Map domain = domains.computeIfAbsent(nameObject.getDomain(), key -> new HashMap<>());
// jsonifiedMBeanInfo should not be null here and *may* be cached
domain.put(nameObject.getKeyPropertyListString(), Objects.requireNonNullElse(mbeanInfoKey, jsonifiedMBeanInfo));
visited.add(nameObject);
}
/**
* This method duplicates what Jolokia does in List Handler in order to convert {@link MBeanInfo} to JSON.
*/
private Map jsonifyMBeanInfo(MBeanInfo mBeanInfo) {
Map result = new LinkedHashMap<>();
// desc
result.put("desc", mBeanInfo.getDescription());
// attr
Map attrMap = new LinkedHashMap<>();
result.put("attr", attrMap);
for (MBeanAttributeInfo attrInfo : mBeanInfo.getAttributes()) {
if (attrInfo == null) {
continue;
}
Map attr = new HashMap<>();
attr.put("type", attrInfo.getType());
attr.put("desc", attrInfo.getDescription());
attr.put("rw", attrInfo.isWritable() && attrInfo.isReadable());
attrMap.put(attrInfo.getName(), attr);
}
// op
Map opMap = new LinkedHashMap<>();
result.put("op", opMap);
for (MBeanOperationInfo opInfo : mBeanInfo.getOperations()) {
Map map = toMap(opInfo);
Object ops = opMap.get(opInfo.getName());
if (ops != null) {
if (ops instanceof List) {
// If it is already a list, simply add it to the end
//noinspection unchecked,rawtypes
((List) ops).add(map);
} else if (ops instanceof Map) {
// If it is a map, add a list with two elements
// (the old one and the new one)
List