/* * 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 javax.management.JMX.DEFAULT_VALUE_FIELD; import static javax.management.JMX.LEGAL_VALUES_FIELD; import static javax.management.JMX.MAX_VALUE_FIELD; import static javax.management.JMX.MIN_VALUE_FIELD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOWED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEFAULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXPRESSIONS_ALLOWED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MAX; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MIN; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_RESOURCES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_TYPES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_OPERATION_DESCRIPTION_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_OPERATION_NAMES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REPLY_PROPERTIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REQUEST_PROPERTIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.AttributeChangeNotification; import javax.management.Descriptor; import javax.management.ImmutableDescriptor; import javax.management.InstanceNotFoundException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.Notification; import javax.management.ObjectName; import javax.management.openmbean.OpenMBeanAttributeInfo; import javax.management.openmbean.OpenMBeanAttributeInfoSupport; import javax.management.openmbean.OpenMBeanConstructorInfo; import javax.management.openmbean.OpenMBeanInfoSupport; import javax.management.openmbean.OpenMBeanOperationInfo; import javax.management.openmbean.OpenMBeanOperationInfoSupport; import javax.management.openmbean.OpenMBeanParameterInfo; import javax.management.openmbean.OpenMBeanParameterInfoSupport; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import org.jboss.as.controller.CompositeOperationHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.descriptions.DescriptionProvider; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.operations.common.ValidateAddressOperationHandler; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.AttributeAccess.AccessType; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.NotificationEntry; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.OperationEntry.Flag; import org.jboss.as.jmx.logging.JmxLogger; import org.jboss.as.jmx.model.ChildAddOperationFinder.ChildAddOperationEntry; import org.jboss.as.server.deployment.DeploymentUploadStreamAttachmentHandler; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ public class MBeanInfoFactory { private static final String DESC_MBEAN_EXPR = "mbean.expression.support"; private static final String DESC_MBEAN_EXPR_DESCR = "mbean.expression.support.description"; private static final String DESC_ALTERNATE_MBEAN = "alternate.mbean"; private static final String DESC_ALTERNATE_MBEAN_DESCR = "alternate.mbean.description"; private static final String DESC_EXPRESSIONS_ALLOWED = "expressions.allowed"; private static final String DESC_EXPRESSIONS_ALLOWED_DESC = "expressions.allowed.description"; private static final OpenMBeanParameterInfo[] EMPTY_PARAMETERS = new OpenMBeanParameterInfo[0]; private final ObjectName name; private final TypeConverters converters; private final ConfiguredDomains configuredDomains; private final MutabilityChecker mutabilityChecker; private final ImmutableManagementResourceRegistration resourceRegistration; private final ModelNode providedDescription; private final PathAddress pathAddress; private final boolean legacy; private MBeanInfoFactory(final ObjectName name, final TypeConverters converters, final ConfiguredDomains configuredDomains, final MutabilityChecker mutabilityChecker, final PathAddress address, final ImmutableManagementResourceRegistration resourceRegistration) { this.name = name; this.converters = converters; this.configuredDomains = configuredDomains; this.mutabilityChecker = mutabilityChecker; this.legacy = configuredDomains.isLegacyDomain(name); this.resourceRegistration = resourceRegistration; DescriptionProvider provider = resourceRegistration.getModelDescription(PathAddress.EMPTY_ADDRESS); providedDescription = provider != null ? provider.getModelDescription(null) : new ModelNode(); this.pathAddress = address; } static MBeanInfo createMBeanInfo(final ObjectName name, final TypeConverters converters, final ConfiguredDomains configuredDomains, final MutabilityChecker mutabilityChecker, final PathAddress address, final ImmutableManagementResourceRegistration resourceRegistration) throws InstanceNotFoundException{ return new MBeanInfoFactory(name, converters, configuredDomains, mutabilityChecker, address, resourceRegistration).createMBeanInfo(); } private MBeanInfo createMBeanInfo() { return new OpenMBeanInfoSupport(ModelControllerMBeanHelper.CLASS_NAME, getDescription(providedDescription), getAttributes(), getConstructors(), getOperations(), getNotifications(), createMBeanDescriptor()); } private String getDescription(ModelNode node) { if (!node.hasDefined(DESCRIPTION)) { return "-"; } String description = node.get(DESCRIPTION).asString(); if (description.trim().length() == 0) { return "-"; } return description; } private OpenMBeanAttributeInfo[] getAttributes() { List<OpenMBeanAttributeInfo> infos = new LinkedList<OpenMBeanAttributeInfo>(); if (providedDescription.hasDefined(ATTRIBUTES)) { for (final String name : providedDescription.require(ATTRIBUTES).keys()) { OpenMBeanAttributeInfo attributeInfo = getAttribute(name); if (attributeInfo != null) { infos.add(getAttribute(name)); } } } return infos.toArray(new OpenMBeanAttributeInfo[infos.size()]); } private OpenMBeanAttributeInfo getAttribute(String name) { final String escapedName = NameConverter.convertToCamelCase(name); ModelNode attribute = providedDescription.require(ATTRIBUTES).require(name); AttributeAccess access = resourceRegistration.getAttributeAccess(PathAddress.EMPTY_ADDRESS, name); if (access == null) { // Check for a bogus attribute in the description that's really a child Set<String> childTypes = resourceRegistration.getChildNames(PathAddress.EMPTY_ADDRESS); if (childTypes.contains(name)) { return null; } } final boolean writable = mutabilityChecker.mutable(pathAddress) && (access != null && access.getAccessType() == AccessType.READ_WRITE); return new OpenMBeanAttributeInfoSupport( escapedName, getDescription(attribute), converters.convertToMBeanType(attribute), true, writable, false, createAttributeDescriptor(attribute)); } private OpenMBeanConstructorInfo[] getConstructors() { //This can be left empty return null; } private OpenMBeanOperationInfo[] getOperations() { final boolean root = pathAddress.size() == 0; //TODO include inherited/global operations? List<OpenMBeanOperationInfo> ops = new ArrayList<OpenMBeanOperationInfo>(); for (Map.Entry<String, OperationEntry> entry : resourceRegistration.getOperationDescriptions(PathAddress.EMPTY_ADDRESS, false).entrySet()) { final String opName = entry.getKey(); if (opName.equals(ADD) || opName.equals(DESCRIBE)) { continue; } if (root) { if (opName.equals(READ_RESOURCE_OPERATION) || opName.equals(READ_ATTRIBUTE_OPERATION) || opName.equals(READ_RESOURCE_DESCRIPTION_OPERATION) || opName.equals(READ_CHILDREN_NAMES_OPERATION) || opName.equals(READ_CHILDREN_TYPES_OPERATION) || opName.equals(READ_CHILDREN_RESOURCES_OPERATION) || opName.equals(READ_OPERATION_NAMES_OPERATION) || opName.equals(READ_OPERATION_DESCRIPTION_OPERATION) || opName.equals(READ_RESOURCE_OPERATION) || opName.equals(READ_RESOURCE_OPERATION) || opName.equals(WRITE_ATTRIBUTE_OPERATION) || opName.equals(ValidateAddressOperationHandler.OPERATION_NAME) || opName.equals(CompositeOperationHandler.NAME) || opName.equals(DeploymentUploadStreamAttachmentHandler.OPERATION_NAME)) { //Ignore some of the global operations which probably don't make much sense here continue; } } final OperationEntry opEntry = entry.getValue(); if (mutabilityChecker.mutable(pathAddress) || opEntry.getFlags().contains(Flag.READ_ONLY) || opEntry.getFlags().contains(Flag.RUNTIME_ONLY)) { ops.add(getOperation(NameConverter.convertToCamelCase(entry.getKey()), null, opEntry)); } } addChildAddOperations(ops, resourceRegistration); return ops.toArray(new OpenMBeanOperationInfo[ops.size()]); } private void addChildAddOperations(List<OpenMBeanOperationInfo> ops, ImmutableManagementResourceRegistration resourceRegistration) { for (Map.Entry<PathElement, ChildAddOperationEntry> entry : ChildAddOperationFinder.findAddChildOperations(pathAddress, mutabilityChecker, resourceRegistration).entrySet()) { OpenMBeanParameterInfo addWildcardChildName = null; if (entry.getValue().getElement().isWildcard()) { addWildcardChildName = new OpenMBeanParameterInfoSupport("name", "The name of the " + entry.getValue().getElement().getKey() + " to add.", SimpleType.STRING); } ops.add(getOperation(NameConverter.createValidAddOperationName(entry.getKey()), addWildcardChildName, entry.getValue().getOperationEntry())); } } private OpenMBeanOperationInfo getOperation(String name, OpenMBeanParameterInfo addWildcardChildName, OperationEntry entry) { ModelNode opNode = entry.getDescriptionProvider().getModelDescription(null); OpenMBeanParameterInfo[] params = getParameterInfos(opNode); if (addWildcardChildName != null) { OpenMBeanParameterInfo[] newParams = new OpenMBeanParameterInfo[params.length + 1]; newParams[0] = addWildcardChildName; System.arraycopy(params, 0, newParams, 1, params.length); params = newParams; } return new OpenMBeanOperationInfoSupport( name, getDescription(opNode), params, getReturnType(opNode), entry.getFlags().contains(Flag.READ_ONLY) ? MBeanOperationInfo.INFO : MBeanOperationInfo.UNKNOWN, createOperationDescriptor()); } private OpenMBeanParameterInfo[] getParameterInfos(ModelNode opNode) { if (!opNode.hasDefined(REQUEST_PROPERTIES)) { return EMPTY_PARAMETERS; } List<Property> propertyList = opNode.get(REQUEST_PROPERTIES).asPropertyList(); List<OpenMBeanParameterInfo> params = new ArrayList<OpenMBeanParameterInfo>(propertyList.size()); for (Property prop : propertyList) { ModelNode value = prop.getValue(); String paramName = NameConverter.convertToCamelCase(prop.getName()); Map<String, Object> descriptions = new HashMap<String, Object>(4); boolean expressionsAllowed = prop.getValue().hasDefined(EXPRESSIONS_ALLOWED) && prop.getValue().get(EXPRESSIONS_ALLOWED).asBoolean(); descriptions.put(DESC_EXPRESSIONS_ALLOWED, String.valueOf(expressionsAllowed)); if (!expressionsAllowed) { Object defaultValue = getIfExists(value, DEFAULT); descriptions.put(DEFAULT_VALUE_FIELD, defaultValue); if (value.has(ALLOWED)) { if (value.get(TYPE).asType()!=ModelType.LIST){ List<ModelNode> allowed = value.get(ALLOWED).asList(); descriptions.put(LEGAL_VALUES_FIELD, fromModelNodes(allowed)); } } else { if (value.has(MIN)) { descriptions.put(MIN_VALUE_FIELD, getIfExistsAsComparable(value, MIN)); } if (value.has(MAX)) { descriptions.put(MAX_VALUE_FIELD, getIfExistsAsComparable(value, MAX)); } } } params.add( new OpenMBeanParameterInfoSupport( paramName, getDescription(prop.getValue()), converters.convertToMBeanType(value), new ImmutableDescriptor(descriptions))); } return params.toArray(new OpenMBeanParameterInfo[params.size()]); } private Set<?> fromModelNodes(final List<ModelNode> nodes) { Set<Object> values = new HashSet<Object>(nodes.size()); for (ModelNode node : nodes) { values.add(converters.getConverter(ModelType.STRING,null).fromModelNode(node)); } return values; } private Object getIfExists(final ModelNode parentNode, final String name) { if (parentNode.has(name)) { ModelNode defaultNode = parentNode.get(name); return converters.fromModelNode(parentNode, defaultNode); } else { return null; } } private Comparable<?> getIfExistsAsComparable(final ModelNode parentNode, final String name) { if (parentNode.has(name)) { ModelNode defaultNode = parentNode.get(name); Object value = converters.fromModelNode(parentNode, defaultNode); if (value instanceof Comparable) { return (Comparable<?>) value; } } return null; } private OpenType<?> getReturnType(ModelNode opNode) { if (!opNode.hasDefined(REPLY_PROPERTIES)) { return SimpleType.VOID; } if (opNode.get(REPLY_PROPERTIES).asList().size() == 0) { return SimpleType.VOID; } //TODO might have more than one REPLY_PROPERTIES? ModelNode reply = opNode.get(REPLY_PROPERTIES); return converters.convertToMBeanType(reply); } private MBeanNotificationInfo[] getNotifications() { List<MBeanNotificationInfo> notifications = new ArrayList<>(); for (Map.Entry<String, NotificationEntry> entry : resourceRegistration.getNotificationDescriptions(PathAddress.EMPTY_ADDRESS, true).entrySet()) { ModelNode descriptionModel = entry.getValue().getDescriptionProvider().getModelDescription(null); String description = descriptionModel.get(DESCRIPTION).asString(); String notificationType = entry.getKey(); MBeanNotificationInfo info = null; if (notificationType.equals(ModelDescriptionConstants.ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION)) { info = new MBeanNotificationInfo(new String[]{AttributeChangeNotification.ATTRIBUTE_CHANGE}, AttributeChangeNotification.class.getName(), description); } else if (notificationType.equals(ModelDescriptionConstants.RESOURCE_ADDED_NOTIFICATION) || notificationType.equals(ModelDescriptionConstants.RESOURCE_REMOVED_NOTIFICATION)) { // do not expose these notifications as they are emitted by the JMImplementation:type=MBeanServerDelegate MBean. } else { info = new MBeanNotificationInfo(new String[]{notificationType}, Notification.class.getName(), description); } if (info != null) { notifications.add(info); } } return notifications.toArray(new MBeanNotificationInfo[notifications.size()]); } private Descriptor createMBeanDescriptor() { Map<String, String> descriptions = new HashMap<String, String>(); addMBeanExpressionSupport(descriptions); return new ImmutableDescriptor(descriptions); } private Descriptor createAttributeDescriptor(ModelNode attribute) { Map<String, String> descriptions = new HashMap<String, String>(); addMBeanExpressionSupport(descriptions); Boolean allowExpressions = attribute.hasDefined(EXPRESSIONS_ALLOWED) && attribute.get(EXPRESSIONS_ALLOWED).asBoolean(); descriptions.put(DESC_EXPRESSIONS_ALLOWED, allowExpressions.toString()); descriptions.put(DESC_EXPRESSIONS_ALLOWED_DESC, allowExpressions ? JmxLogger.ROOT_LOGGER.descriptorAttributeExpressionsAllowedTrue() : JmxLogger.ROOT_LOGGER.descriptorAttributeExpressionsAllowedFalse()); return new ImmutableDescriptor(descriptions); } private Descriptor createOperationDescriptor() { Map<String, String> descriptions = new HashMap<String, String>(); addMBeanExpressionSupport(descriptions); return new ImmutableDescriptor(descriptions); } private void addMBeanExpressionSupport(Map<String, String> descriptions) { if (legacy) { descriptions.put(DESC_MBEAN_EXPR, "true"); descriptions.put(DESC_MBEAN_EXPR_DESCR, JmxLogger.ROOT_LOGGER.descriptorMBeanExpressionSupportFalse()); if (configuredDomains.getExprDomain() != null) { ObjectName alternate = configuredDomains.getMirroredObjectName(name); descriptions.put(DESC_ALTERNATE_MBEAN, alternate.toString()); descriptions.put(DESC_ALTERNATE_MBEAN_DESCR, JmxLogger.ROOT_LOGGER.descriptorAlternateMBeanExpressions(alternate)); } } else { descriptions.put(DESC_MBEAN_EXPR, "false"); descriptions.put(DESC_MBEAN_EXPR_DESCR, JmxLogger.ROOT_LOGGER.descriptorMBeanExpressionSupportTrue()); if (configuredDomains.getLegacyDomain() != null) { ObjectName alternate = configuredDomains.getMirroredObjectName(name); descriptions.put(DESC_ALTERNATE_MBEAN, alternate.toString()); descriptions.put(DESC_ALTERNATE_MBEAN_DESCR, JmxLogger.ROOT_LOGGER.descriptorAlternateMBeanLegacy(alternate)); } } } }