001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.jmx.export.assembler;
018:
019: import java.lang.reflect.Method;
020: import java.lang.reflect.Modifier;
021: import java.util.Arrays;
022: import java.util.Enumeration;
023: import java.util.HashMap;
024: import java.util.Map;
025: import java.util.Properties;
026:
027: import org.springframework.beans.factory.BeanClassLoaderAware;
028: import org.springframework.beans.factory.InitializingBean;
029: import org.springframework.util.ClassUtils;
030: import org.springframework.util.StringUtils;
031:
032: /**
033: * Subclass of <code>AbstractReflectiveMBeanInfoAssembler</code> 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</code> 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</code>) to a list of interface names using the
044: * <code>interfaceMappings</code> property.
045: *
046: * <p>If you specify values for both <code>interfaceMappings</code> and
047: * <code>managedInterfaces</code>, 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</code>.
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: */
060: public class InterfaceBasedMBeanInfoAssembler extends
061: AbstractConfigurableMBeanInfoAssembler implements
062: BeanClassLoaderAware, InitializingBean {
063:
064: /**
065: * Stores the array of interfaces to use for creating the management interface.
066: */
067: private Class[] managedInterfaces;
068:
069: /**
070: * Stores the mappings of bean keys to an array of <code>Class</code>es.
071: */
072: private Properties interfaceMappings;
073:
074: private ClassLoader beanClassLoader = ClassUtils
075: .getDefaultClassLoader();
076:
077: /**
078: * Stores the mappings of bean keys to an array of <code>Class</code>es.
079: */
080: private Map resolvedInterfaceMappings;
081:
082: /**
083: * Set the array of interfaces to use for creating the management info.
084: * These interfaces will be used for a bean if no entry corresponding to
085: * that bean is found in the <code>interfaceMappings</code> property.
086: * @param managedInterfaces an array of classes indicating the interfaces to use.
087: * Each entry <strong>MUST</strong> be an interface.
088: * @see #setInterfaceMappings
089: */
090: public void setManagedInterfaces(Class[] managedInterfaces) {
091: if (managedInterfaces != null) {
092: for (int x = 0; x < managedInterfaces.length; x++) {
093: if (!managedInterfaces[x].isInterface()) {
094: throw new IllegalArgumentException(
095: "Management interface ["
096: + managedInterfaces[x].getName()
097: + "] is no interface");
098: }
099: }
100: }
101: this .managedInterfaces = managedInterfaces;
102: }
103:
104: /**
105: * Set the mappings of bean keys to a comma-separated list of interface names.
106: * The property key should match the bean key and the property value should match
107: * the list of interface names. When searching for interfaces for a bean, Spring
108: * will check these mappings first.
109: * @param mappings the mappins of bean keys to interface names
110: */
111: public void setInterfaceMappings(Properties mappings) {
112: this .interfaceMappings = mappings;
113: }
114:
115: public void setBeanClassLoader(ClassLoader beanClassLoader) {
116: this .beanClassLoader = beanClassLoader;
117: }
118:
119: public void afterPropertiesSet() {
120: if (this .interfaceMappings != null) {
121: this .resolvedInterfaceMappings = resolveInterfaceMappings(this .interfaceMappings);
122: }
123: }
124:
125: /**
126: * Resolve the given interface mappings, turning class names into Class objects.
127: * @param mappings the specified interface mappings
128: * @return the resolved interface mappings (with Class objects as values)
129: */
130: private Map resolveInterfaceMappings(Properties mappings) {
131: Map resolvedMappings = new HashMap(mappings.size());
132: for (Enumeration en = mappings.propertyNames(); en
133: .hasMoreElements();) {
134: String beanKey = (String) en.nextElement();
135: String[] classNames = StringUtils
136: .commaDelimitedListToStringArray(mappings
137: .getProperty(beanKey));
138: Class[] classes = resolveClassNames(classNames, beanKey);
139: resolvedMappings.put(beanKey, classes);
140: }
141: return resolvedMappings;
142: }
143:
144: /**
145: * Resolve the given class names into Class objects.
146: * @param classNames the class names to resolve
147: * @param beanKey the bean key that the class names are associated with
148: * @return the resolved Class
149: */
150: private Class[] resolveClassNames(String[] classNames,
151: String beanKey) {
152: Class[] classes = new Class[classNames.length];
153: for (int x = 0; x < classes.length; x++) {
154: Class cls = ClassUtils.resolveClassName(classNames[x]
155: .trim(), this .beanClassLoader);
156: if (!cls.isInterface()) {
157: throw new IllegalArgumentException("Class ["
158: + classNames[x] + "] mapped to bean key ["
159: + beanKey + "] is no interface");
160: }
161: classes[x] = cls;
162: }
163: return classes;
164: }
165:
166: /**
167: * Check to see if the <code>Method</code> is declared in
168: * one of the configured interfaces and that it is public.
169: * @param method the accessor <code>Method</code>.
170: * @param beanKey the key associated with the MBean in the
171: * <code>beans</code> <code>Map</code>.
172: * @return <code>true</code> if the <code>Method</code> is declared in one of the
173: * configured interfaces, otherwise <code>false</code>.
174: */
175: protected boolean includeReadAttribute(Method method, String beanKey) {
176: return isPublicInInterface(method, beanKey);
177: }
178:
179: /**
180: * Check to see if the <code>Method</code> is declared in
181: * one of the configured interfaces and that it is public.
182: * @param method the mutator <code>Method</code>.
183: * @param beanKey the key associated with the MBean in the
184: * <code>beans</code> <code>Map</code>.
185: * @return <code>true</code> if the <code>Method</code> is declared in one of the
186: * configured interfaces, otherwise <code>false</code>.
187: */
188: protected boolean includeWriteAttribute(Method method,
189: String beanKey) {
190: return isPublicInInterface(method, beanKey);
191: }
192:
193: /**
194: * Check to see if the <code>Method</code> is declared in
195: * one of the configured interfaces and that it is public.
196: * @param method the operation <code>Method</code>.
197: * @param beanKey the key associated with the MBean in the
198: * <code>beans</code> <code>Map</code>.
199: * @return <code>true</code> if the <code>Method</code> is declared in one of the
200: * configured interfaces, otherwise <code>false</code>.
201: */
202: protected boolean includeOperation(Method method, String beanKey) {
203: return isPublicInInterface(method, beanKey);
204: }
205:
206: /**
207: * Check to see if the <code>Method</code> is both public and declared in
208: * one of the configured interfaces.
209: * @param method the <code>Method</code> to check.
210: * @param beanKey the key associated with the MBean in the beans map
211: * @return <code>true</code> if the <code>Method</code> is declared in one of the
212: * configured interfaces and is public, otherwise <code>false</code>.
213: */
214: private boolean isPublicInInterface(Method method, String beanKey) {
215: return ((method.getModifiers() & Modifier.PUBLIC) > 0)
216: && isDeclaredInInterface(method, beanKey);
217: }
218:
219: /**
220: * Checks to see if the given method is declared in a managed
221: * interface for the given bean.
222: */
223: private boolean isDeclaredInInterface(Method method, String beanKey) {
224: Class[] ifaces = null;
225:
226: if (this .resolvedInterfaceMappings != null) {
227: ifaces = (Class[]) this .resolvedInterfaceMappings
228: .get(beanKey);
229: }
230:
231: if (ifaces == null) {
232: ifaces = this .managedInterfaces;
233: if (ifaces == null) {
234: ifaces = ClassUtils.getAllInterfacesForClass(method
235: .getDeclaringClass());
236: }
237: }
238:
239: if (ifaces != null) {
240: for (int i = 0; i < ifaces.length; i++) {
241: Method[] methods = ifaces[i].getMethods();
242: for (int j = 0; j < methods.length; j++) {
243: Method ifaceMethod = methods[j];
244: if (ifaceMethod.getName().equals(method.getName())
245: && Arrays.equals(ifaceMethod
246: .getParameterTypes(), method
247: .getParameterTypes())) {
248: return true;
249: }
250: }
251: }
252: }
253:
254: return false;
255: }
256:
257: }
|