001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jmx.export.assembler; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021import java.util.Arrays; 022import java.util.Enumeration; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Properties; 026 027import org.springframework.beans.factory.BeanClassLoaderAware; 028import org.springframework.beans.factory.InitializingBean; 029import org.springframework.util.ClassUtils; 030import org.springframework.util.StringUtils; 031 032/** 033 * Subclass of {@code AbstractReflectiveMBeanInfoAssembler} that allows for 034 * the management interface of a bean to be defined using arbitrary interfaces. 035 * Any methods or properties that are defined in those interfaces are exposed 036 * as MBean operations and attributes. 037 * 038 * <p>By default, this class votes on the inclusion of each operation or attribute 039 * based on the interfaces implemented by the bean class. However, you can supply an 040 * array of interfaces via the {@code managedInterfaces} property that will be 041 * used instead. If you have multiple beans and you wish each bean to use a different 042 * set of interfaces, then you can map bean keys (that is the name used to pass the 043 * bean to the {@code MBeanExporter}) to a list of interface names using the 044 * {@code interfaceMappings} property. 045 * 046 * <p>If you specify values for both {@code interfaceMappings} and 047 * {@code managedInterfaces}, Spring will attempt to find interfaces in the 048 * mappings first. If no interfaces for the bean are found, it will use the 049 * interfaces defined by {@code managedInterfaces}. 050 * 051 * @author Rob Harrop 052 * @author Juergen Hoeller 053 * @since 1.2 054 * @see #setManagedInterfaces 055 * @see #setInterfaceMappings 056 * @see MethodNameBasedMBeanInfoAssembler 057 * @see SimpleReflectiveMBeanInfoAssembler 058 * @see org.springframework.jmx.export.MBeanExporter 059 */ 060public class InterfaceBasedMBeanInfoAssembler extends AbstractConfigurableMBeanInfoAssembler 061 implements BeanClassLoaderAware, InitializingBean { 062 063 private Class<?>[] managedInterfaces; 064 065 /** Mappings of bean keys to an array of classes */ 066 private Properties interfaceMappings; 067 068 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 069 070 /** Mappings of bean keys to an array of classes */ 071 private Map<String, Class<?>[]> resolvedInterfaceMappings; 072 073 074 /** 075 * Set the array of interfaces to use for creating the management info. 076 * These interfaces will be used for a bean if no entry corresponding to 077 * that bean is found in the {@code interfaceMappings} property. 078 * @param managedInterfaces an array of classes indicating the interfaces to use. 079 * Each entry <strong>MUST</strong> be an interface. 080 * @see #setInterfaceMappings 081 */ 082 public void setManagedInterfaces(Class<?>... managedInterfaces) { 083 if (managedInterfaces != null) { 084 for (Class<?> ifc : managedInterfaces) { 085 if (!ifc.isInterface()) { 086 throw new IllegalArgumentException( 087 "Management interface [" + ifc.getName() + "] is not an interface"); 088 } 089 } 090 } 091 this.managedInterfaces = managedInterfaces; 092 } 093 094 /** 095 * Set the mappings of bean keys to a comma-separated list of interface names. 096 * <p>The property key should match the bean key and the property value should match 097 * the list of interface names. When searching for interfaces for a bean, Spring 098 * will check these mappings first. 099 * @param mappings the mappings of bean keys to interface names 100 */ 101 public void setInterfaceMappings(Properties mappings) { 102 this.interfaceMappings = mappings; 103 } 104 105 @Override 106 public void setBeanClassLoader(ClassLoader beanClassLoader) { 107 this.beanClassLoader = beanClassLoader; 108 } 109 110 111 @Override 112 public void afterPropertiesSet() { 113 if (this.interfaceMappings != null) { 114 this.resolvedInterfaceMappings = resolveInterfaceMappings(this.interfaceMappings); 115 } 116 } 117 118 /** 119 * Resolve the given interface mappings, turning class names into Class objects. 120 * @param mappings the specified interface mappings 121 * @return the resolved interface mappings (with Class objects as values) 122 */ 123 private Map<String, Class<?>[]> resolveInterfaceMappings(Properties mappings) { 124 Map<String, Class<?>[]> resolvedMappings = new HashMap<String, Class<?>[]>(mappings.size()); 125 for (Enumeration<?> en = mappings.propertyNames(); en.hasMoreElements();) { 126 String beanKey = (String) en.nextElement(); 127 String[] classNames = StringUtils.commaDelimitedListToStringArray(mappings.getProperty(beanKey)); 128 Class<?>[] classes = resolveClassNames(classNames, beanKey); 129 resolvedMappings.put(beanKey, classes); 130 } 131 return resolvedMappings; 132 } 133 134 /** 135 * Resolve the given class names into Class objects. 136 * @param classNames the class names to resolve 137 * @param beanKey the bean key that the class names are associated with 138 * @return the resolved Class 139 */ 140 private Class<?>[] resolveClassNames(String[] classNames, String beanKey) { 141 Class<?>[] classes = new Class<?>[classNames.length]; 142 for (int x = 0; x < classes.length; x++) { 143 Class<?> cls = ClassUtils.resolveClassName(classNames[x].trim(), this.beanClassLoader); 144 if (!cls.isInterface()) { 145 throw new IllegalArgumentException( 146 "Class [" + classNames[x] + "] mapped to bean key [" + beanKey + "] is no interface"); 147 } 148 classes[x] = cls; 149 } 150 return classes; 151 } 152 153 154 /** 155 * Check to see if the {@code Method} is declared in 156 * one of the configured interfaces and that it is public. 157 * @param method the accessor {@code Method}. 158 * @param beanKey the key associated with the MBean in the 159 * {@code beans} {@code Map}. 160 * @return {@code true} if the {@code Method} is declared in one of the 161 * configured interfaces, otherwise {@code false}. 162 */ 163 @Override 164 protected boolean includeReadAttribute(Method method, String beanKey) { 165 return isPublicInInterface(method, beanKey); 166 } 167 168 /** 169 * Check to see if the {@code Method} is declared in 170 * one of the configured interfaces and that it is public. 171 * @param method the mutator {@code Method}. 172 * @param beanKey the key associated with the MBean in the 173 * {@code beans} {@code Map}. 174 * @return {@code true} if the {@code Method} is declared in one of the 175 * configured interfaces, otherwise {@code false}. 176 */ 177 @Override 178 protected boolean includeWriteAttribute(Method method, String beanKey) { 179 return isPublicInInterface(method, beanKey); 180 } 181 182 /** 183 * Check to see if the {@code Method} is declared in 184 * one of the configured interfaces and that it is public. 185 * @param method the operation {@code Method}. 186 * @param beanKey the key associated with the MBean in the 187 * {@code beans} {@code Map}. 188 * @return {@code true} if the {@code Method} is declared in one of the 189 * configured interfaces, otherwise {@code false}. 190 */ 191 @Override 192 protected boolean includeOperation(Method method, String beanKey) { 193 return isPublicInInterface(method, beanKey); 194 } 195 196 /** 197 * Check to see if the {@code Method} is both public and declared in 198 * one of the configured interfaces. 199 * @param method the {@code Method} to check. 200 * @param beanKey the key associated with the MBean in the beans map 201 * @return {@code true} if the {@code Method} is declared in one of the 202 * configured interfaces and is public, otherwise {@code false}. 203 */ 204 private boolean isPublicInInterface(Method method, String beanKey) { 205 return Modifier.isPublic(method.getModifiers()) && isDeclaredInInterface(method, beanKey); 206 } 207 208 /** 209 * Checks to see if the given method is declared in a managed 210 * interface for the given bean. 211 */ 212 private boolean isDeclaredInInterface(Method method, String beanKey) { 213 Class<?>[] ifaces = null; 214 215 if (this.resolvedInterfaceMappings != null) { 216 ifaces = this.resolvedInterfaceMappings.get(beanKey); 217 } 218 219 if (ifaces == null) { 220 ifaces = this.managedInterfaces; 221 if (ifaces == null) { 222 ifaces = ClassUtils.getAllInterfacesForClass(method.getDeclaringClass()); 223 } 224 } 225 226 for (Class<?> ifc : ifaces) { 227 for (Method ifcMethod : ifc.getMethods()) { 228 if (ifcMethod.getName().equals(method.getName()) && 229 Arrays.equals(ifcMethod.getParameterTypes(), method.getParameterTypes())) { 230 return true; 231 } 232 } 233 } 234 235 return false; 236 } 237 238}