/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat Middleware LLC, 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.model;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESOURCE_ADDED_NOTIFICATION;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import javax.management.Attribute;
import javax.management.AttributeChangeNotification;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
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.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import org.jboss.as.controller.ModelController;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.notification.Notification;
import org.jboss.as.controller.notification.NotificationFilter;
import org.jboss.as.controller.notification.NotificationHandler;
import org.jboss.as.controller.operations.global.GlobalNotifications;
import org.jboss.as.controller.registry.NotificationHandlerRegistration;
import org.jboss.as.jmx.BaseMBeanServerPlugin;
import org.jboss.as.jmx.logging.JmxLogger;
import org.jboss.dmr.ModelNode;
/**
* An MBeanServer wrapper that exposes the ModelController via JMX.
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public class ModelControllerMBeanServerPlugin extends BaseMBeanServerPlugin {
private final MBeanServer mbeanServer;
private final ConfiguredDomains configuredDomains;
private final ModelControllerMBeanHelper legacyHelper;
private final ModelControllerMBeanHelper exprHelper;
private final NotificationHandlerRegistration notificationRegistry;
private final AtomicLong notificationSequenceNumber = new AtomicLong(0);
public ModelControllerMBeanServerPlugin(final MBeanServer mbeanServer,
final ConfiguredDomains configuredDomains, ModelController controller, final MBeanServerDelegate delegate,
boolean legacyWithProperPropertyFormat, ProcessType processType,
ManagementModelIntegration.ManagementModelProvider managementModelProvider, boolean isMasterHc) {
assert configuredDomains != null;
this.mbeanServer = mbeanServer;
this.configuredDomains = configuredDomains;
this.notificationRegistry = controller.getNotificationRegistry();
MutabilityChecker mutabilityChecker = MutabilityChecker.create(processType, isMasterHc);
legacyHelper = configuredDomains.getLegacyDomain() != null ?
new ModelControllerMBeanHelper(TypeConverters.createLegacyTypeConverters(legacyWithProperPropertyFormat),
configuredDomains, configuredDomains.getLegacyDomain(), controller, mutabilityChecker, managementModelProvider) : null;
exprHelper = configuredDomains.getExprDomain() != null ?
new ModelControllerMBeanHelper(TypeConverters.createExpressionTypeConverters(), configuredDomains,
configuredDomains.getExprDomain(), controller, mutabilityChecker, managementModelProvider) : null;
// JMX notifications for MBean registration/unregistration are emitted by the MBeanServerDelegate and not by the
// MBeans itself. If we have a reference on the delegate, we add a listener for any WildFly resource address
// that converts the resource-added and resource-removed notifications to MBeanServerNotification and send them
// through the delegate.
if (delegate != null) {
for (String domain : configuredDomains.getDomains()) {
ResourceRegistrationNotificationHandler handler = new ResourceRegistrationNotificationHandler(delegate, domain);
notificationRegistry.registerNotificationHandler(NotificationHandlerRegistration.ANY_ADDRESS, handler, handler);
}
}
}
@Override
public boolean accepts(ObjectName objectName) {
String domain = objectName.getDomain();
if (!objectName.isDomainPattern()) {
return domain.equals(configuredDomains.getLegacyDomain()) || domain.equals(configuredDomains.getExprDomain());
}
Pattern p = Pattern.compile(objectName.getDomain().replace("*", ".*"));
return p.matcher(configuredDomains.getLegacyDomain()).matches() || p.matcher(configuredDomains.getExprDomain()).matches();
}
@Override
public boolean shouldAuditLog() {
return false;
}
@Override
public boolean shouldAuthorize() {
return false;
}
public Object getAttribute(ObjectName name, String attribute) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException,
ReflectionException {
return getHelper(name).getAttribute(name, attribute);
}
public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, ReflectionException {
return getHelper(name).getAttributes(name, attributes);
}
public ClassLoader getClassLoader(ObjectName loaderName) throws InstanceNotFoundException {
if (getHelper(loaderName).resolvePathAddress(loaderName) != null) {
return SecurityActions.getClassLoader(this.getClass());
}
throw JmxLogger.ROOT_LOGGER.mbeanNotFound(loaderName);
}
public ClassLoader getClassLoaderFor(ObjectName mbeanName) throws InstanceNotFoundException {
if (getHelper(mbeanName).toPathAddress(mbeanName) != null) {
return SecurityActions.getClassLoader(this.getClass());
}
throw JmxLogger.ROOT_LOGGER.mbeanNotFound(mbeanName);
}
public String[] getDomains() {
return configuredDomains.getDomains();
}
public Integer getMBeanCount() {
int count = 0;
if (legacyHelper != null) {
count += legacyHelper.getMBeanCount();
}
if (exprHelper != null) {
count += exprHelper.getMBeanCount();
}
return count;
}
public MBeanInfo getMBeanInfo(ObjectName name) throws InstanceNotFoundException, IntrospectionException, ReflectionException {
return getHelper(name).getMBeanInfo(name);
}
public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException {
return getHelper(name).getObjectInstance(name);
}
public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws InstanceNotFoundException,
MBeanException, ReflectionException {
return getHelper(name).invoke(name, operationName, params, signature);
}
public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException {
// return true for NotificationBroadcaster so that client connected to the MBeanServer know
// that it can broadcast notifications.
if (NotificationBroadcaster.class.getName().equals(className)) {
return true;
}
return false;
}
public boolean isRegistered(ObjectName name) {
return getHelper(name).resolvePathAddress(name) != null;
}
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
if (name != null && !name.isDomainPattern()) {
return getHelper(name).queryMBeans(mbeanServer, name, query);
} else {
Set<ObjectInstance> instances = new HashSet<ObjectInstance>();
if (legacyHelper != null) {
instances.addAll(legacyHelper.queryMBeans(mbeanServer, name, query));
}
if (exprHelper != null) {
instances.addAll(exprHelper.queryMBeans(mbeanServer, name, query));
}
return instances;
}
}
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
if (name != null && !name.isDomainPattern()) {
return getHelper(name).queryNames(mbeanServer, name, query);
} else {
Set<ObjectName> instances = new HashSet<ObjectName>();
if (legacyHelper != null) {
instances.addAll(legacyHelper.queryNames(mbeanServer, name, query));
}
if (exprHelper != null) {
instances.addAll(exprHelper.queryNames(mbeanServer, name, query));
}
return instances;
}
}
public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException,
InvalidAttributeValueException, MBeanException, ReflectionException {
getHelper(name).setAttribute(name, attribute);
}
public AttributeList setAttributes(ObjectName name, AttributeList attributes) throws InstanceNotFoundException, ReflectionException {
return getHelper(name).setAttributes(name, attributes);
}
public void addNotificationListener(final ObjectName name, final NotificationListener listener, final javax.management.NotificationFilter filter, final Object handback)
throws InstanceNotFoundException {
PathAddress pathAddress = getHelper(name).toPathAddress(name);
JMXNotificationHandler handler = new JMXNotificationHandler(getHelper(name).getDomain(), name, listener, filter, handback);
notificationRegistry.registerNotificationHandler(pathAddress, handler, handler);
}
public void addNotificationListener(ObjectName name, ObjectName listener, javax.management.NotificationFilter filter, Object handback)
throws InstanceNotFoundException {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.addNotificationListerWithObjectNameNotSupported(listener));
}
public void removeNotificationListener(ObjectName name, NotificationListener listener, javax.management.NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
PathAddress pathAddress = getHelper(name).toPathAddress(name);
JMXNotificationHandler handler = new JMXNotificationHandler(getHelper(name).getDomain(), name, listener, filter, handback);
notificationRegistry.unregisterNotificationHandler(pathAddress, handler, handler);
}
public void removeNotificationListener(ObjectName name, NotificationListener listener) throws InstanceNotFoundException,
ListenerNotFoundException {
removeNotificationListener(name, listener, null, null);
}
public void removeNotificationListener(ObjectName name, ObjectName listener, javax.management.NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListerWithObjectNameNotSupported(listener));
}
public void removeNotificationListener(ObjectName name, ObjectName listener) throws InstanceNotFoundException, ListenerNotFoundException {
throw new RuntimeOperationsException(JmxLogger.ROOT_LOGGER.removeNotificationListerWithObjectNameNotSupported(listener));
}
private ModelControllerMBeanHelper getHelper(ObjectName name) {
String domain = name.getDomain();
if (domain.equals(configuredDomains.getLegacyDomain()) || name.isDomainPattern()) {
return legacyHelper;
}
if (domain.equals(configuredDomains.getExprDomain())) {
return exprHelper;
}
//This should not happen
throw JmxLogger.ROOT_LOGGER.unknownDomain(domain);
}
/**
* Handle WildFly notifications, convert them to JMX notifications and forward them to the JMX listener.
*/
private class JMXNotificationHandler implements NotificationHandler, NotificationFilter {
private final String domain;
private final ObjectName name;
private final NotificationListener listener;
private final javax.management.NotificationFilter filter;
private final Object handback;
private JMXNotificationHandler(String domain, ObjectName name, NotificationListener listener, javax.management.NotificationFilter filter, Object handback) {
this.domain = domain;
this.name = name;
this.listener = listener;
this.filter = filter;
this.handback = handback;
}
@Override
public boolean isNotificationEnabled(Notification notification) {
if (isResourceAddedOrRemovedNotification(notification)) {
// filter outs resource-added and resource-removed notifications that are handled by the
// MBeanServerDelegate
return false;
}
ObjectName sourceName = ObjectNameAddressUtil.createObjectName(domain, notification.getSource());
if (!name.equals(sourceName)) {
return false;
}
if (filter == null) {
return true;
} else {
javax.management.Notification jmxNotification = convert(notification);
return filter.isNotificationEnabled(jmxNotification);
}
}
@Override
public void handleNotification(Notification notification) {
javax.management.Notification jmxNotification = convert(notification);
if (jmxNotification != null) {
listener.handleNotification(jmxNotification, handback);
}
}
private javax.management.Notification convert(Notification notification) {
long sequenceNumber = notificationSequenceNumber.incrementAndGet();
ObjectName source = ObjectNameAddressUtil.createObjectName(domain, notification.getSource());
String message = notification.getMessage();
long timestamp = notification.getTimestamp();
String notificationType = notification.getType();
javax.management.Notification jmxNotification;
if (notificationType.equals(ModelDescriptionConstants.ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION)) {
ModelNode data = notification.getData();
String attributeName = data.get(ModelDescriptionConstants.NAME).asString();
String jmxAttributeName = NameConverter.convertToCamelCase(attributeName);
try {
ModelNode modelDescription = getHelper(source).getMBeanRegistration(source).getModelDescription(PathAddress.EMPTY_ADDRESS).getModelDescription(null);
ModelNode attributeDescription = modelDescription.get(ATTRIBUTES, attributeName);
TypeConverters converters = getHelper(source).getConverters();
Object oldValue = converters.fromModelNode(attributeDescription, data.get(GlobalNotifications.OLD_VALUE));
Object newValue = converters.fromModelNode(attributeDescription, data.get(GlobalNotifications.NEW_VALUE));
String attributeType = converters.convertToMBeanType(attributeDescription).getTypeName();
jmxNotification = new AttributeChangeNotification(source, sequenceNumber, timestamp, message, jmxAttributeName, attributeType, oldValue, newValue);
} catch (InstanceNotFoundException e) {
// fallback to a generic notification
jmxNotification = new javax.management.Notification(notification.getType(), source, sequenceNumber, timestamp, message);
}
} else if (notificationType.equals(ModelDescriptionConstants.RESOURCE_ADDED_NOTIFICATION) ||
notificationType.equals(ModelDescriptionConstants.RESOURCE_REMOVED_NOTIFICATION)) {
// do not convert resource-added and resource-removed notifications: for JMX, they are not emitted by the MBean itself
// but by the MBeanServerDelegate (see ModelControllerMBeanServerPlugin constructor)
jmxNotification = null;
} else {
jmxNotification = new javax.management.Notification(notificationType, source, sequenceNumber, timestamp, message);
jmxNotification.setUserData(notification.getData());
}
return jmxNotification;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JMXNotificationHandler that = (JMXNotificationHandler) 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;
}
}
/**
* Handle resource-added and resource-removed notifications that are converted to MBeanServerNotifcations
* emitted by the MBeanServerDelegate
*/
private static class ResourceRegistrationNotificationHandler implements NotificationHandler, NotificationFilter {
private final MBeanServerDelegate delegate;
private final String domain;
private AtomicLong sequence = new AtomicLong(0);
private ResourceRegistrationNotificationHandler(MBeanServerDelegate delegate, String domain) {
this.delegate = delegate;
this.domain = domain;
}
@Override
public void handleNotification(Notification notification) {
String jmxType = notification.getType().equals(RESOURCE_ADDED_NOTIFICATION) ? MBeanServerNotification.REGISTRATION_NOTIFICATION : MBeanServerNotification.UNREGISTRATION_NOTIFICATION;
ObjectName mbeanName = ObjectNameAddressUtil.createObjectName(domain, notification.getSource());
javax.management.Notification jmxNotification = new MBeanServerNotification(jmxType, MBeanServerDelegate.DELEGATE_NAME, sequence.incrementAndGet(), mbeanName);
delegate.sendNotification(jmxNotification);
}
@Override
public boolean isNotificationEnabled(Notification notification) {
return isResourceAddedOrRemovedNotification(notification);
}
}
private static boolean isResourceAddedOrRemovedNotification(Notification notification) {
return notification.getType().equals(RESOURCE_ADDED_NOTIFICATION) ||
notification.getType().equals(ModelDescriptionConstants.RESOURCE_REMOVED_NOTIFICATION);
}
}