/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.jmx;
import java.io.ObjectInputStream;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import javax.management.loading.ClassLoaderRepository;
import org.jboss.as.jmx.logging.JmxLogger;
/**
* MBeanServer wrapper that does <strong>not</strong> forward calls
* to add/remove a notification listener for domains exposing WildFly model controllers
*
* @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2014 Red Hat inc.
*/
public class BlockingNotificationMBeanServer implements MBeanServer {
private MBeanServer mbs;
private final String resolvedDomain;
private final String expressionsDomain;
/**
* Keep an association between the incoming <name,listener,filter,handback> tuple and a BlockingNotificationFilter
* that prevents leaking out WildFly notifications.
* The PlatformMBeanServer is checking the object identity when it is removing a notification listener. This association makes
* sure we will pass the same {@code BlockingNotificationFilter} to the removeNotificationListener() method that the one passed in the
* addNotificationListener().
*/
private final Map<Association, BlockingNotificationFilter> associations = new ConcurrentHashMap<>();
/**
*
* @param mbs the JMX MBeanServer (can not be {@code null}
* @param resolvedDomain JMX domain name for the 'resolved' model controller (can be {@code null} if the model controller is not exposed)
* @param expressionsDomain JMX domain name for the 'expression' model controller (can be {@code null} if the model controller is not exposed)
*/
public BlockingNotificationMBeanServer(MBeanServer mbs, String resolvedDomain, String expressionsDomain) {
this.mbs = mbs;
this.resolvedDomain = resolvedDomain;
this.expressionsDomain = expressionsDomain;
}
@Override
public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException, InstanceAlreadyExistsException, MBeanException, NotCompliantMBeanException {
return mbs.createMBean(className, name);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName) throws ReflectionException, InstanceAlreadyExistsException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException {
return mbs.createMBean(className, name, loaderName);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, Object[] params, String[] signature) throws ReflectionException, InstanceAlreadyExistsException, MBeanException, NotCompliantMBeanException {
return mbs.createMBean(className, name, params, signature);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object[] params, String[] signature) throws ReflectionException, InstanceAlreadyExistsException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException {
return mbs.createMBean(className, name, loaderName, params, signature);
}
@Override
public ObjectInstance registerMBean(Object object, ObjectName name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
return mbs.registerMBean(object, name);
}
@Override
public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException {
mbs.unregisterMBean(name);
}
@Override
public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException {
return mbs.getObjectInstance(name);
}
@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
return mbs.queryMBeans(name, query);
}
@Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
return mbs.queryNames(name, query);
}
@Override
public boolean isRegistered(ObjectName name) {
return mbs.isRegistered(name);
}
@Override
public Integer getMBeanCount() {
return mbs.getMBeanCount();
}
@Override
public Object getAttribute(ObjectName name, String attribute) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException {
return mbs.getAttribute(name, attribute);
}
@Override
public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, ReflectionException {
return mbs.getAttributes(name, attributes);
}
@Override
public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
mbs.setAttribute(name, attribute);
}
@Override
public AttributeList setAttributes(ObjectName name, AttributeList attributes) throws InstanceNotFoundException, ReflectionException {
return mbs.setAttributes(name, attributes);
}
@Override
public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, ReflectionException {
return mbs.invoke(name, operationName, params, signature);
}
@Override
public String getDefaultDomain() {
return mbs.getDefaultDomain();
}
@Override
public String[] getDomains() {
return mbs.getDomains();
}
@Override
public void addNotificationListener(ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException {
boolean inExposedModelControllerDomains = isInExposedModelControllerDomains(name);
if (inExposedModelControllerDomains) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.addNotificationListenerNotAllowed(name));
}
BlockingNotificationFilter blockingFilter = new BlockingNotificationFilter(filter);
associations.put(new Association(name, listener, filter, handback), blockingFilter);
mbs.addNotificationListener(name, listener, blockingFilter, handback);
}
@Override
public void addNotificationListener(ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException {
boolean inExposedModelControllerDomains = isInExposedModelControllerDomains(name);
if (inExposedModelControllerDomains) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.addNotificationListenerNotAllowed(name));
}
BlockingNotificationFilter blockingFilter = new BlockingNotificationFilter(filter);
associations.put(new Association(name, listener, filter, handback), blockingFilter);
mbs.addNotificationListener(name, listener, new BlockingNotificationFilter(filter), handback);
}
@Override
public void removeNotificationListener(ObjectName name, ObjectName listener) throws InstanceNotFoundException, ListenerNotFoundException {
if (isInExposedModelControllerDomains(name)) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListenerNotAllowed(name));
}
mbs.removeNotificationListener(name, listener);
}
@Override
public void removeNotificationListener(ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException {
if (isInExposedModelControllerDomains(name)) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListenerNotAllowed(name));
}
BlockingNotificationFilter blockingFilter = associations.get(new Association(name, listener, filter, handback));
if (blockingFilter != null) {
mbs.removeNotificationListener(name, listener, blockingFilter, handback);
} else {
mbs.removeNotificationListener(name, listener, filter, handback);
}
}
@Override
public void removeNotificationListener(ObjectName name, NotificationListener listener) throws InstanceNotFoundException, ListenerNotFoundException {
if (isInExposedModelControllerDomains(name)) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListenerNotAllowed(name));
}
mbs.removeNotificationListener(name, listener);
}
@Override
public void removeNotificationListener(ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException {
if (isInExposedModelControllerDomains(name)) {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListenerNotAllowed(name));
}
BlockingNotificationFilter blockingFilter = associations.get(new Association(name, listener, filter, handback));
if (blockingFilter != null) {
mbs.removeNotificationListener(name, listener, blockingFilter, handback);
} else {
mbs.removeNotificationListener(name, listener, filter, handback);
}
}
@Override
public MBeanInfo getMBeanInfo(ObjectName name) throws InstanceNotFoundException, IntrospectionException, ReflectionException {
return mbs.getMBeanInfo(name);
}
@Override
public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException {
return mbs.isInstanceOf(name, className);
}
@Override
public Object instantiate(String className) throws ReflectionException, MBeanException {
return mbs.instantiate(className);
}
@Override
public Object instantiate(String className, ObjectName loaderName) throws ReflectionException, MBeanException, InstanceNotFoundException {
return mbs.instantiate(className, loaderName);
}
@Override
public Object instantiate(String className, Object[] params, String[] signature) throws ReflectionException, MBeanException {
return mbs.instantiate(className, params, signature);
}
@Override
public Object instantiate(String className, ObjectName loaderName, Object[] params, String[] signature) throws ReflectionException, MBeanException, InstanceNotFoundException {
return mbs.instantiate(className, loaderName, params, signature);
}
@Override
public ObjectInputStream deserialize(ObjectName name, byte[] data) throws OperationsException {
return mbs.deserialize(name, data);
}
@Override
public ObjectInputStream deserialize(String className, byte[] data) throws OperationsException, ReflectionException {
return mbs.deserialize(className, data);
}
@Override
public ObjectInputStream deserialize(String className, ObjectName loaderName, byte[] data) throws OperationsException, ReflectionException {
return mbs.deserialize(className, loaderName, data);
}
@Override
public ClassLoader getClassLoaderFor(ObjectName mbeanName) throws InstanceNotFoundException {
return mbs.getClassLoaderFor(mbeanName);
}
@Override
public ClassLoader getClassLoader(ObjectName loaderName) throws InstanceNotFoundException {
return mbs.getClassLoaderFor(loaderName);
}
@Override
public ClassLoaderRepository getClassLoaderRepository() {
return mbs.getClassLoaderRepository();
}
private boolean isInExposedModelControllerDomains(ObjectName name) {
String domain = name.getDomain();
if (!name.isDomainPattern()) {
if (domain.equals(resolvedDomain) || domain.equals(expressionsDomain)) {
return true;
}
}
Pattern p = Pattern.compile(name.getDomain().replace("*", ".*"));
if ((resolvedDomain != null && p.matcher(resolvedDomain).matches())
|| (expressionsDomain != null && p.matcher(expressionsDomain).matches())) {
return true;
}
return false;
}
/**
* Block MBeanServerNotification corresponding to WildFly MBeans.
*/
private class BlockingNotificationFilter implements NotificationFilter {
private final NotificationFilter filter;
private BlockingNotificationFilter(NotificationFilter filter) {
this.filter = filter;
}
@Override
public boolean isNotificationEnabled(Notification notification) {
if (notification instanceof MBeanServerNotification) {
MBeanServerNotification notif = (MBeanServerNotification) notification;
if (notif.getMBeanName().getDomain().equals(resolvedDomain) ||
notif.getMBeanName().getDomain().equals(expressionsDomain)) {
return false;
}
}
if (filter == null) {
return true;
} else {
return filter.isNotificationEnabled(notification);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BlockingNotificationFilter that = (BlockingNotificationFilter) o;
if (filter != null ? !filter.equals(that.filter) : that.filter != null) return false;
return true;
}
@Override
public int hashCode() {
return filter != null ? filter.hashCode() : 0;
}
}
private static final class Association {
private final ObjectName name;
private final Object listener;
private final NotificationFilter filter;
private final Object handback;
/**
* @param name must not be {@code null}
* @param listener must not be {@code null}
* @param filter can be {@code null}
* @param handback can be {@code null}
*/
private Association(ObjectName name, Object listener, NotificationFilter filter, Object handback) {
this.name = name;
this.filter = filter;
this.listener = listener;
this.handback = handback;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Association that = (Association) o;
if (filter != null ? !filter.equals(that.filter) : that.filter != null) return false;
if (handback != null ? !handback.equals(that.handback) : that.handback != null) return false;
if (!listener.equals(that.listener)) return false;
if (!name.equals(that.name)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + listener.hashCode();
result = 31 * result + (filter != null ? filter.hashCode() : 0);
result = 31 * result + (handback != null ? handback.hashCode() : 0);
return result;
}
}
}