0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.jmx.export;
0018:
0019: import java.util.ArrayList;
0020: import java.util.Arrays;
0021: import java.util.HashMap;
0022: import java.util.HashSet;
0023: import java.util.Iterator;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.Set;
0027:
0028: import javax.management.DynamicMBean;
0029: import javax.management.InstanceNotFoundException;
0030: import javax.management.JMException;
0031: import javax.management.MBeanException;
0032: import javax.management.MBeanServer;
0033: import javax.management.MalformedObjectNameException;
0034: import javax.management.NotCompliantMBeanException;
0035: import javax.management.NotificationFilter;
0036: import javax.management.NotificationListener;
0037: import javax.management.ObjectName;
0038: import javax.management.StandardMBean;
0039: import javax.management.modelmbean.ModelMBean;
0040: import javax.management.modelmbean.ModelMBeanInfo;
0041: import javax.management.modelmbean.RequiredModelMBean;
0042:
0043: import org.springframework.aop.framework.ProxyFactory;
0044: import org.springframework.aop.support.AopUtils;
0045: import org.springframework.aop.target.LazyInitTargetSource;
0046: import org.springframework.beans.factory.BeanClassLoaderAware;
0047: import org.springframework.beans.factory.BeanFactory;
0048: import org.springframework.beans.factory.BeanFactoryAware;
0049: import org.springframework.beans.factory.DisposableBean;
0050: import org.springframework.beans.factory.InitializingBean;
0051: import org.springframework.beans.factory.ListableBeanFactory;
0052: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
0053: import org.springframework.beans.factory.config.BeanDefinition;
0054: import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
0055: import org.springframework.core.Constants;
0056: import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler;
0057: import org.springframework.jmx.export.assembler.MBeanInfoAssembler;
0058: import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler;
0059: import org.springframework.jmx.export.naming.KeyNamingStrategy;
0060: import org.springframework.jmx.export.naming.ObjectNamingStrategy;
0061: import org.springframework.jmx.export.naming.SelfNaming;
0062: import org.springframework.jmx.export.notification.ModelMBeanNotificationPublisher;
0063: import org.springframework.jmx.export.notification.NotificationPublisherAware;
0064: import org.springframework.jmx.support.JmxUtils;
0065: import org.springframework.jmx.support.MBeanRegistrationSupport;
0066: import org.springframework.jmx.support.ObjectNameManager;
0067: import org.springframework.util.Assert;
0068: import org.springframework.util.ClassUtils;
0069: import org.springframework.util.CollectionUtils;
0070: import org.springframework.util.ObjectUtils;
0071:
0072: /**
0073: * JMX exporter that allows for exposing any <i>Spring-managed bean</i>
0074: * to a JMX <code>MBeanServer</code>, without the need to define any
0075: * JMX-specific information in the bean classes.
0076: *
0077: * <p>If the bean implements one of the JMX management interfaces,
0078: * then MBeanExporter can simply register the MBean with the server
0079: * automatically, through its autodetection process.
0080: *
0081: * <p>If the bean does not implement one of the JMX management interfaces,
0082: * then MBeanExporter will create the management information using the
0083: * supplied {@link MBeanInfoAssembler} implementation.
0084: *
0085: * <p>A list of {@link MBeanExporterListener MBeanExporterListeners}
0086: * can be registered via the
0087: * {@link #setListeners(MBeanExporterListener[]) listeners} property,
0088: * allowing application code to be notified of MBean registration and
0089: * unregistration events.
0090: *
0091: * @author Rob Harrop
0092: * @author Juergen Hoeller
0093: * @author Rick Evans
0094: * @author Mark Fisher
0095: * @since 1.2
0096: * @see #setBeans
0097: * @see #setAutodetect
0098: * @see #setAssembler
0099: * @see #setListeners
0100: * @see org.springframework.jmx.export.assembler.MBeanInfoAssembler
0101: * @see MBeanExporterListener
0102: */
0103: public class MBeanExporter extends MBeanRegistrationSupport implements
0104: MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware,
0105: InitializingBean, DisposableBean {
0106:
0107: /**
0108: * Autodetection mode indicating that no autodetection should be used.
0109: */
0110: public static final int AUTODETECT_NONE = 0;
0111:
0112: /**
0113: * Autodetection mode indicating that only valid MBeans should be autodetected.
0114: */
0115: public static final int AUTODETECT_MBEAN = 1;
0116:
0117: /**
0118: * Autodetection mode indicating that only the {@link MBeanInfoAssembler} should be able
0119: * to autodetect beans.
0120: */
0121: public static final int AUTODETECT_ASSEMBLER = 2;
0122:
0123: /**
0124: * Autodetection mode indicating that all autodetection mechanisms should be used.
0125: */
0126: public static final int AUTODETECT_ALL = AUTODETECT_MBEAN
0127: | AUTODETECT_ASSEMBLER;
0128:
0129: /**
0130: * Wildcard used to map a {@link javax.management.NotificationListener}
0131: * to all MBeans registered by the <code>MBeanExporter</code>.
0132: */
0133: private static final String WILDCARD = "*";
0134:
0135: /** Constant for the JMX <code>mr_type</code> "ObjectReference" */
0136: private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference";
0137:
0138: /** Prefix for the autodetect constants defined in this class */
0139: private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_";
0140:
0141: /** Constants instance for this class */
0142: private static final Constants constants = new Constants(
0143: MBeanExporter.class);
0144:
0145: /** The beans to be exposed as JMX managed resources, with JMX names as keys */
0146: private Map beans;
0147:
0148: /** The autodetect mode to use for this MBeanExporter */
0149: private Integer autodetectMode;
0150:
0151: /** Indicates whether Spring should modify generated ObjectNames */
0152: private boolean ensureUniqueRuntimeObjectNames = true;
0153:
0154: /** Indicates whether Spring should expose the managed resource ClassLoader in the MBean */
0155: private boolean exposeManagedResourceClassLoader = false;
0156:
0157: /** A set of bean names that should be excluded from autodetection */
0158: private Set excludedBeans;
0159:
0160: /** The MBeanExporterListeners registered with this exporter. */
0161: private MBeanExporterListener[] listeners;
0162:
0163: /** The NotificationListeners to register for the MBeans registered by this exporter */
0164: private NotificationListenerBean[] notificationListeners = new NotificationListenerBean[0];
0165:
0166: /** Stores the MBeanInfoAssembler to use for this exporter */
0167: private MBeanInfoAssembler assembler = new SimpleReflectiveMBeanInfoAssembler();
0168:
0169: /** The strategy to use for creating ObjectNames for an object */
0170: private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy();
0171:
0172: /** Stores the ClassLoader to use for generating lazy-init proxies */
0173: private ClassLoader beanClassLoader = ClassUtils
0174: .getDefaultClassLoader();
0175:
0176: /** Stores the BeanFactory for use in autodetection process */
0177: private ListableBeanFactory beanFactory;
0178:
0179: /**
0180: * Supply a <code>Map</code> of beans to be registered with the JMX
0181: * <code>MBeanServer</code>.
0182: * <p>The String keys are the basis for the creation of JMX object names.
0183: * By default, a JMX <code>ObjectName</code> will be created straight
0184: * from the given key. This can be customized through specifying a
0185: * custom <code>NamingStrategy</code>.
0186: * <p>Both bean instances and bean names are allowed as values.
0187: * Bean instances are typically linked in through bean references.
0188: * Bean names will be resolved as beans in the current factory, respecting
0189: * lazy-init markers (that is, not triggering initialization of such beans).
0190: * @param beans Map with JMX names as keys and bean instances or bean names
0191: * as values
0192: * @see #setNamingStrategy
0193: * @see org.springframework.jmx.export.naming.KeyNamingStrategy
0194: * @see javax.management.ObjectName#ObjectName(String)
0195: */
0196: public void setBeans(Map beans) {
0197: this .beans = beans;
0198: }
0199:
0200: /**
0201: * Set whether to autodetect MBeans in the bean factory that this exporter
0202: * runs in. Will also ask an <code>AutodetectCapableMBeanInfoAssembler</code>
0203: * if available.
0204: * <p>This feature is turned off by default. Explicitly specify
0205: * <code>true</code> here to enable autodetection.
0206: * @see #setAssembler
0207: * @see AutodetectCapableMBeanInfoAssembler
0208: * @see #isMBean
0209: */
0210: public void setAutodetect(boolean autodetect) {
0211: this .autodetectMode = new Integer(autodetect ? AUTODETECT_ALL
0212: : AUTODETECT_NONE);
0213: }
0214:
0215: /**
0216: * Sets the autodetection mode to use.
0217: * @exception IllegalArgumentException if the supplied value is not
0218: * one of the <code>AUTODETECT_</code> constants
0219: * @see #setAutodetectModeName(String)
0220: * @see #AUTODETECT_ALL
0221: * @see #AUTODETECT_ASSEMBLER
0222: * @see #AUTODETECT_MBEAN
0223: * @see #AUTODETECT_NONE
0224: */
0225: public void setAutodetectMode(int autodetectMode) {
0226: if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(
0227: new Integer(autodetectMode))) {
0228: throw new IllegalArgumentException(
0229: "Only values of autodetect constants allowed");
0230: }
0231: this .autodetectMode = new Integer(autodetectMode);
0232: }
0233:
0234: /**
0235: * Sets the autodetection mode to use by name.
0236: * @exception IllegalArgumentException if the supplied value is not resolvable
0237: * to one of the <code>AUTODETECT_</code> constants or is <code>null</code>
0238: * @see #setAutodetectMode(int)
0239: * @see #AUTODETECT_ALL
0240: * @see #AUTODETECT_ASSEMBLER
0241: * @see #AUTODETECT_MBEAN
0242: * @see #AUTODETECT_NONE
0243: */
0244: public void setAutodetectModeName(String constantName) {
0245: if (constantName == null
0246: || !constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) {
0247: throw new IllegalArgumentException(
0248: "Only autodetect constants allowed");
0249: }
0250: this .autodetectMode = (Integer) constants
0251: .asNumber(constantName);
0252: }
0253:
0254: /**
0255: * Set the implementation of the <code>MBeanInfoAssembler</code> interface to use
0256: * for this exporter. Default is a <code>SimpleReflectiveMBeanInfoAssembler</code>.
0257: * <p>The passed-in assembler can optionally implement the
0258: * <code>AutodetectCapableMBeanInfoAssembler</code> interface, which enables it
0259: * to participate in the exporter's MBean autodetection process.
0260: * @see org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler
0261: * @see org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler
0262: * @see org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler
0263: * @see #setAutodetect
0264: */
0265: public void setAssembler(MBeanInfoAssembler assembler) {
0266: this .assembler = assembler;
0267: }
0268:
0269: /**
0270: * Set the implementation of the <code>ObjectNamingStrategy</code> interface
0271: * to use for this exporter. Default is a <code>KeyNamingStrategy</code>.
0272: * @see org.springframework.jmx.export.naming.KeyNamingStrategy
0273: * @see org.springframework.jmx.export.naming.MetadataNamingStrategy
0274: */
0275: public void setNamingStrategy(ObjectNamingStrategy namingStrategy) {
0276: this .namingStrategy = namingStrategy;
0277: }
0278:
0279: /**
0280: * Set the <code>MBeanExporterListener</code>s that should be notified
0281: * of MBean registration and unregistration events.
0282: * @see MBeanExporterListener
0283: */
0284: public void setListeners(MBeanExporterListener[] listeners) {
0285: this .listeners = listeners;
0286: }
0287:
0288: /**
0289: * Set the list of names for beans that should be excluded from autodetection.
0290: */
0291: public void setExcludedBeans(String[] excludedBeans) {
0292: this .excludedBeans = (excludedBeans != null ? new HashSet(
0293: Arrays.asList(excludedBeans)) : null);
0294: }
0295:
0296: /**
0297: * Indicates whether Spring should ensure that {@link ObjectName ObjectNames}
0298: * generated by the configured {@link ObjectNamingStrategy} for
0299: * runtime-registered MBeans should be modified to ensure uniqueness
0300: * for every instance of a managed <code>Class</code>.
0301: * <p>The default value is <code>true</code>.
0302: * @see JmxUtils#appendIdentityToObjectName(javax.management.ObjectName, Object)
0303: */
0304: public void setEnsureUniqueRuntimeObjectNames(
0305: boolean ensureUniqueRuntimeObjectNames) {
0306: this .ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames;
0307: }
0308:
0309: /**
0310: * Indicates whether or not the managed resource should be exposed on the
0311: * {@link Thread#getContextClassLoader() thread context ClassLoader} before
0312: * allowing any invocations on the MBean to occur.
0313: * <ü>The default value is <code>true</code>, exposing a {@link SpringModelMBean}
0314: * which performs thread context ClassLoader management. Switch this flag off to
0315: * expose a standard JMX {@link javax.management.modelmbean.RequiredModelMBean}.
0316: */
0317: public void setExposeManagedResourceClassLoader(
0318: boolean exposeManagedResourceClassLoader) {
0319: this .exposeManagedResourceClassLoader = exposeManagedResourceClassLoader;
0320: }
0321:
0322: /**
0323: * Set the {@link NotificationListenerBean NotificationListenerBeans}
0324: * containing the
0325: * {@link javax.management.NotificationListener NotificationListeners}
0326: * that will be registered with the {@link MBeanServer}.
0327: * @see #setNotificationListenerMappings(java.util.Map)
0328: * @see NotificationListenerBean
0329: */
0330: public void setNotificationListeners(
0331: NotificationListenerBean[] notificationListeners) {
0332: this .notificationListeners = notificationListeners;
0333: }
0334:
0335: /**
0336: * Set the {@link NotificationListener NotificationListeners} to register
0337: * with the {@link javax.management.MBeanServer}.
0338: * <P>The key of each entry in the <code>Map</code> is a {@link String}
0339: * representation of the {@link javax.management.ObjectName} or the bean
0340: * name of the MBean the listener should be registered for. Specifying an
0341: * asterisk (<code>*</code>) for a key will cause the listener to be
0342: * associated with all MBeans registered by this class at startup time.
0343: * <p>The value of each entry is the
0344: * {@link javax.management.NotificationListener} to register. For more
0345: * advanced options such as registering
0346: * {@link javax.management.NotificationFilter NotificationFilters} and
0347: * handback objects see {@link #setNotificationListeners(NotificationListenerBean[])}.
0348: * @throws IllegalArgumentException if the supplied <code>listeners</code> {@link Map} is <code>null</code>.
0349: */
0350: public void setNotificationListenerMappings(Map listeners) {
0351: Assert.notNull(listeners, "'listeners' must not be null");
0352: List notificationListeners = new ArrayList(listeners.size());
0353:
0354: for (Iterator iterator = listeners.entrySet().iterator(); iterator
0355: .hasNext();) {
0356: Map.Entry entry = (Map.Entry) iterator.next();
0357:
0358: // Get the listener from the map value.
0359: Object value = entry.getValue();
0360: if (!(value instanceof NotificationListener)) {
0361: throw new IllegalArgumentException("Map entry value ["
0362: + value + "] is not a NotificationListener");
0363: }
0364: NotificationListenerBean bean = new NotificationListenerBean(
0365: (NotificationListener) value);
0366:
0367: // Get the ObjectName from the map key.
0368: Object key = entry.getKey();
0369: if (key != null && !WILDCARD.equals(key)) {
0370: // This listener is mapped to a specific ObjectName.
0371: bean.setMappedObjectName(entry.getKey().toString());
0372: }
0373:
0374: notificationListeners.add(bean);
0375: }
0376:
0377: this .notificationListeners = (NotificationListenerBean[]) notificationListeners
0378: .toArray(new NotificationListenerBean[notificationListeners
0379: .size()]);
0380: }
0381:
0382: public void setBeanClassLoader(ClassLoader classLoader) {
0383: this .beanClassLoader = classLoader;
0384: }
0385:
0386: /**
0387: * This callback is only required for resolution of bean names in the
0388: * {@link #setBeans(java.util.Map) "beans"} {@link Map} and for
0389: * autodetection of MBeans (in the latter case, a
0390: * <code>ListableBeanFactory</code> is required).
0391: * @see #setBeans
0392: * @see #setAutodetect
0393: * @throws IllegalArgumentException if the supplied <code>beanFactory</code> is not a {@link ListableBeanFactory}.
0394: */
0395: public void setBeanFactory(BeanFactory beanFactory) {
0396: if (beanFactory instanceof ListableBeanFactory) {
0397: this .beanFactory = (ListableBeanFactory) beanFactory;
0398: } else {
0399: logger
0400: .info("MBeanExporter not running in a ListableBeanFactory: Autodetection of MBeans not available.");
0401: }
0402: }
0403:
0404: //---------------------------------------------------------------------
0405: // Lifecycle in bean factory: automatically register/unregister beans
0406: //---------------------------------------------------------------------
0407:
0408: /**
0409: * Start bean registration automatically when deployed in an
0410: * <code>ApplicationContext</code>.
0411: * @see #registerBeans()
0412: */
0413: public void afterPropertiesSet() {
0414: logger.info("Registering beans for JMX exposure on startup");
0415: registerBeans();
0416: }
0417:
0418: /**
0419: * Unregisters all beans that this exported has exposed via JMX
0420: * when the enclosing <code>ApplicationContext</code> is destroyed.
0421: */
0422: public void destroy() {
0423: logger.info("Unregistering JMX-exposed beans on shutdown");
0424: unregisterBeans();
0425: }
0426:
0427: //---------------------------------------------------------------------
0428: // Implementation of MBeanExportOperations interface
0429: //---------------------------------------------------------------------
0430:
0431: public ObjectName registerManagedResource(Object managedResource)
0432: throws MBeanExportException {
0433: Assert.notNull(managedResource,
0434: "Managed resource must not be null");
0435: ObjectName objectName = null;
0436: try {
0437: objectName = getObjectName(managedResource, null);
0438: if (this .ensureUniqueRuntimeObjectNames) {
0439: objectName = JmxUtils.appendIdentityToObjectName(
0440: objectName, managedResource);
0441: }
0442: } catch (Exception ex) {
0443: throw new MBeanExportException(
0444: "Unable to generate ObjectName for MBean ["
0445: + managedResource + "]", ex);
0446: }
0447: registerManagedResource(managedResource, objectName);
0448: return objectName;
0449: }
0450:
0451: public void registerManagedResource(Object managedResource,
0452: ObjectName objectName) throws MBeanExportException {
0453: Assert.notNull(managedResource,
0454: "Managed resource must not be null");
0455: Assert.notNull(objectName, "ObjectName must not be null");
0456: try {
0457: if (isMBean(managedResource.getClass())) {
0458: doRegister(managedResource, objectName);
0459: } else {
0460: ModelMBean mbean = createAndConfigureMBean(
0461: managedResource, managedResource.getClass()
0462: .getName());
0463: doRegister(mbean, objectName);
0464: injectNotificationPublisherIfNecessary(managedResource,
0465: mbean, objectName);
0466: }
0467: } catch (JMException ex) {
0468: throw new UnableToRegisterMBeanException(
0469: "Unable to register MBean [" + managedResource
0470: + "] with object name [" + objectName + "]",
0471: ex);
0472: }
0473: }
0474:
0475: //---------------------------------------------------------------------
0476: // Exporter implementation
0477: //---------------------------------------------------------------------
0478:
0479: /**
0480: * Registers the defined beans with the {@link MBeanServer}.
0481: * <p>Each bean is exposed to the <code>MBeanServer</code> via a
0482: * <code>ModelMBean</code>. The actual implemetation of the
0483: * <code>ModelMBean</code> interface used depends on the implementation of
0484: * the <code>ModelMBeanProvider</code> interface that is configured. By
0485: * default the <code>RequiredModelMBean</code> class that is supplied with
0486: * all JMX implementations is used.
0487: * <p>The management interface produced for each bean is dependent on the
0488: * <code>MBeanInfoAssembler</code> implementation being used. The
0489: * <code>ObjectName</code> given to each bean is dependent on the
0490: * implementation of the <code>ObjectNamingStrategy</code> interface being used.
0491: */
0492: protected void registerBeans() {
0493: // The beans property may be null, for example if we are relying solely on autodetection.
0494: if (this .beans == null) {
0495: this .beans = new HashMap();
0496: // Use AUTODETECT_ALL as default in no beans specified explicitly.
0497: if (this .autodetectMode == null) {
0498: this .autodetectMode = new Integer(AUTODETECT_ALL);
0499: }
0500: }
0501:
0502: // Perform autodetection, if desired.
0503: int mode = (this .autodetectMode != null ? this .autodetectMode
0504: .intValue() : AUTODETECT_NONE);
0505: if (mode != AUTODETECT_NONE) {
0506: if (this .beanFactory == null) {
0507: throw new MBeanExportException(
0508: "Cannot autodetect MBeans if not running in a BeanFactory");
0509: }
0510: if (mode == AUTODETECT_MBEAN || mode == AUTODETECT_ALL) {
0511: // Autodetect any beans that are already MBeans.
0512: this .logger
0513: .debug("Autodetecting user-defined JMX MBeans");
0514: autodetectMBeans();
0515: }
0516: // Allow the assembler a chance to vote for bean inclusion.
0517: if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL)
0518: && this .assembler instanceof AutodetectCapableMBeanInfoAssembler) {
0519: autodetectBeans((AutodetectCapableMBeanInfoAssembler) this .assembler);
0520: }
0521: }
0522:
0523: if (!this .beans.isEmpty()) {
0524: // If no server was provided then try to find one. This is useful in an environment
0525: // such as JDK 1.5, Tomcat or JBoss where there is already an MBeanServer loaded.
0526: if (this .server == null) {
0527: this .server = JmxUtils.locateMBeanServer();
0528: }
0529: try {
0530: for (Iterator it = this .beans.entrySet().iterator(); it
0531: .hasNext();) {
0532: Map.Entry entry = (Map.Entry) it.next();
0533: String beanKey = (String) entry.getKey();
0534: Object value = entry.getValue();
0535: registerBeanNameOrInstance(value, beanKey);
0536: }
0537:
0538: // All MBeans are now registered successfully - go ahead and register the notification listeners.
0539: registerNotificationListeners();
0540: } catch (RuntimeException ex) {
0541: // Unregister beans already registered by this exporter.
0542: unregisterBeans();
0543: throw ex;
0544: }
0545: }
0546: }
0547:
0548: /**
0549: * Return whether the specified bean definition should be considered as lazy-init.
0550: * @param beanFactory the bean factory that is supposed to contain the bean definition
0551: * @param beanName the name of the bean to check
0552: * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#getBeanDefinition
0553: * @see org.springframework.beans.factory.config.BeanDefinition#isLazyInit
0554: */
0555: protected boolean isBeanDefinitionLazyInit(
0556: ListableBeanFactory beanFactory, String beanName) {
0557: if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
0558: return false;
0559: }
0560: try {
0561: BeanDefinition bd = ((ConfigurableListableBeanFactory) beanFactory)
0562: .getBeanDefinition(beanName);
0563: return bd.isLazyInit();
0564: } catch (NoSuchBeanDefinitionException ex) {
0565: // Probably a directly registered singleton.
0566: return false;
0567: }
0568: }
0569:
0570: /**
0571: * Registers an individual bean with the {@link #setServer MBeanServer}.
0572: * <p>This method is responsible for deciding <strong>how</strong> a bean
0573: * should be exposed to the <code>MBeanServer</code>. Specifically, if the
0574: * supplied <code>mapValue</code> is the name of a bean that is configured
0575: * for lazy initialization, then a proxy to the resource is registered with
0576: * the <code>MBeanServer</code> so that the the lazy load behavior is
0577: * honored. If the bean is already an MBean then it will be registered
0578: * directly with the <code>MBeanServer</code> without any intervention. For
0579: * all other beans or bean names, the resource itself is registered with
0580: * the <code>MBeanServer</code> directly.
0581: * @param mapValue the value configured for this bean in the beans map;
0582: * may be either the <code>String</code> name of a bean, or the bean itself
0583: * @param beanKey the key associated with this bean in the beans map
0584: * @return the <code>ObjectName</code> under which the resource was registered
0585: * @throws MBeanExportException if the export failed
0586: * @see #setBeans
0587: * @see #registerBeanInstance
0588: * @see #registerLazyInit
0589: */
0590: protected ObjectName registerBeanNameOrInstance(Object mapValue,
0591: String beanKey) throws MBeanExportException {
0592: try {
0593: if (mapValue instanceof String) {
0594: // Bean name pointing to a potentially lazy-init bean in the factory.
0595: if (this .beanFactory == null) {
0596: throw new MBeanExportException(
0597: "Cannot resolve bean names if not running in a BeanFactory");
0598: }
0599: String beanName = (String) mapValue;
0600: if (isBeanDefinitionLazyInit(this .beanFactory, beanName)) {
0601: ObjectName objectName = registerLazyInit(beanName,
0602: beanKey);
0603: replaceNotificationListenerBeanNameMappingKeysIfNecessary(
0604: beanName, objectName);
0605: return objectName;
0606: } else {
0607: Object bean = this .beanFactory.getBean(beanName);
0608: ObjectName objectName = registerBeanInstance(bean,
0609: beanKey);
0610: replaceNotificationListenerBeanNameMappingKeysIfNecessary(
0611: beanName, objectName);
0612: return objectName;
0613: }
0614: } else {
0615: // Plain bean instance -> register it directly.
0616: if (this .beanFactory != null) {
0617: Map beansOfSameType = this .beanFactory
0618: .getBeansOfType(mapValue.getClass(), false,
0619: false);
0620: for (Iterator iterator = beansOfSameType.entrySet()
0621: .iterator(); iterator.hasNext();) {
0622: Map.Entry entry = (Map.Entry) iterator.next();
0623: if (entry.getValue() == mapValue) {
0624: String beanName = (String) entry.getKey();
0625: ObjectName objectName = registerBeanInstance(
0626: mapValue, beanKey);
0627: replaceNotificationListenerBeanNameMappingKeysIfNecessary(
0628: beanName, objectName);
0629: return objectName;
0630: }
0631: }
0632: }
0633: return registerBeanInstance(mapValue, beanKey);
0634: }
0635: } catch (Exception ex) {
0636: throw new UnableToRegisterMBeanException(
0637: "Unable to register MBean [" + mapValue
0638: + "] with key '" + beanKey + "'", ex);
0639: }
0640: }
0641:
0642: /**
0643: * Replaces any bean names used as keys in the <code>NotificationListener</code>
0644: * mappings with their corresponding <code>ObjectName</code> values.
0645: * @param beanName the name of the bean to be registered
0646: * @param objectName the <code>ObjectName</code> under which the bean will be registered
0647: * with the <code>MBeanServer</code>
0648: */
0649: private void replaceNotificationListenerBeanNameMappingKeysIfNecessary(
0650: String beanName, ObjectName objectName) {
0651: Set mappedListeners = new HashSet();
0652: String objectNameString = objectName.getCanonicalName();
0653: for (int i = 0; i < notificationListeners.length; i++) {
0654: NotificationListenerBean bean = notificationListeners[i];
0655: NotificationListener listener = bean
0656: .getNotificationListener();
0657: Set newMappedNames = new HashSet();
0658: String[] mappedNames = bean.getMappedObjectNames();
0659: for (int j = 0; j < mappedNames.length; j++) {
0660: String mappedName = mappedNames[j];
0661: if (mappedName.equals(beanName)) {
0662: mappedName = objectNameString;
0663: }
0664: if (mappedName.equals(objectNameString)) {
0665: // avoid duplicate mappings on beanName and objectName
0666: if (!mappedListeners.contains(listener)) {
0667: newMappedNames.add(mappedName);
0668: mappedListeners.add(listener);
0669: }
0670: } else {
0671: newMappedNames.add(mappedName);
0672: }
0673: }
0674: bean.setMappedObjectNames((String[]) newMappedNames
0675: .toArray(new String[newMappedNames.size()]));
0676: }
0677: }
0678:
0679: /**
0680: * Registers an existing MBean or an MBean adapter for a plain bean
0681: * with the <code>MBeanServer</code>.
0682: * @param bean the bean to register, either an MBean or a plain bean
0683: * @param beanKey the key associated with this bean in the beans map
0684: * @return the <code>ObjectName</code> under which the bean was registered
0685: * with the <code>MBeanServer</code>
0686: */
0687: private ObjectName registerBeanInstance(Object bean, String beanKey)
0688: throws JMException {
0689: ObjectName objectName = getObjectName(bean, beanKey);
0690: Object mbeanToExpose = null;
0691: if (isMBean(bean.getClass())) {
0692: mbeanToExpose = bean;
0693: } else {
0694: DynamicMBean adaptedBean = adaptMBeanIfPossible(bean);
0695: if (adaptedBean != null) {
0696: mbeanToExpose = adaptedBean;
0697: }
0698: }
0699: if (mbeanToExpose != null) {
0700: if (logger.isInfoEnabled()) {
0701: logger.info("Located MBean '" + beanKey
0702: + "': registering with JMX server as MBean ["
0703: + objectName + "]");
0704: }
0705: doRegister(mbeanToExpose, objectName);
0706: } else {
0707: if (logger.isInfoEnabled()) {
0708: logger.info("Located managed bean '" + beanKey
0709: + "': registering with JMX server as MBean ["
0710: + objectName + "]");
0711: }
0712: ModelMBean mbean = createAndConfigureMBean(bean, beanKey);
0713: doRegister(mbean, objectName);
0714: injectNotificationPublisherIfNecessary(bean, mbean,
0715: objectName);
0716: }
0717: return objectName;
0718: }
0719:
0720: /**
0721: * Registers beans that are configured for lazy initialization with the
0722: * <code>MBeanServer<code> indirectly through a proxy.
0723: * @param beanName the name of the bean in the <code>BeanFactory</code>
0724: * @param beanKey the key associated with this bean in the beans map
0725: * @return the <code>ObjectName</code> under which the bean was registered
0726: * with the <code>MBeanServer</code>
0727: */
0728: private ObjectName registerLazyInit(String beanName, String beanKey)
0729: throws JMException {
0730: ProxyFactory proxyFactory = new ProxyFactory();
0731: proxyFactory.setProxyTargetClass(true);
0732: proxyFactory.setFrozen(true);
0733:
0734: if (isMBean(this .beanFactory.getType(beanName))) {
0735: // A straight MBean... Let's create a simple lazy-init CGLIB proxy for it.
0736: LazyInitTargetSource targetSource = new LazyInitTargetSource();
0737: targetSource.setTargetBeanName(beanName);
0738: targetSource.setBeanFactory(this .beanFactory);
0739: proxyFactory.setTargetSource(targetSource);
0740:
0741: Object proxy = proxyFactory.getProxy(this .beanClassLoader);
0742: ObjectName objectName = getObjectName(proxy, beanKey);
0743: if (logger.isDebugEnabled()) {
0744: logger
0745: .debug("Located MBean '"
0746: + beanKey
0747: + "': registering with JMX server as lazy-init MBean ["
0748: + objectName + "]");
0749: }
0750: doRegister(proxy, objectName);
0751: return objectName;
0752: }
0753:
0754: else {
0755: // A simple bean... Let's create a lazy-init ModelMBean proxy with notification support.
0756: NotificationPublisherAwareLazyTargetSource targetSource = new NotificationPublisherAwareLazyTargetSource();
0757: targetSource.setTargetBeanName(beanName);
0758: targetSource.setBeanFactory(this .beanFactory);
0759: proxyFactory.setTargetSource(targetSource);
0760:
0761: Object proxy = proxyFactory.getProxy(this .beanClassLoader);
0762: ObjectName objectName = getObjectName(proxy, beanKey);
0763: if (logger.isDebugEnabled()) {
0764: logger
0765: .debug("Located simple bean '"
0766: + beanKey
0767: + "': registering with JMX server as lazy-init MBean ["
0768: + objectName + "]");
0769: }
0770: ModelMBean mbean = createAndConfigureMBean(proxy, beanKey);
0771: targetSource.setModelMBean(mbean);
0772: targetSource.setObjectName(objectName);
0773: doRegister(mbean, objectName);
0774: return objectName;
0775: }
0776: }
0777:
0778: /**
0779: * Retrieve the <code>ObjectName</code> for a bean.
0780: * <p>If the bean implements the <code>SelfNaming</code> interface, then the
0781: * <code>ObjectName</code> will be retrieved using <code>SelfNaming.getObjectName()</code>.
0782: * Otherwise, the configured <code>ObjectNamingStrategy</code> is used.
0783: * @param bean the name of the bean in the <code>BeanFactory</code>
0784: * @param beanKey the key associated with the bean in the beans map
0785: * @return the <code>ObjectName</code> for the supplied bean
0786: * @throws javax.management.MalformedObjectNameException
0787: * if the retrieved <code>ObjectName</code> is malformed
0788: */
0789: protected ObjectName getObjectName(Object bean, String beanKey)
0790: throws MalformedObjectNameException {
0791: if (bean instanceof SelfNaming) {
0792: return ((SelfNaming) bean).getObjectName();
0793: } else {
0794: return this .namingStrategy.getObjectName(bean, beanKey);
0795: }
0796: }
0797:
0798: /**
0799: * Determine whether the given bean class qualifies as an MBean as-is.
0800: * <p>The default implementation delegates to {@link JmxUtils#isMBean},
0801: * which checks for {@link javax.management.DynamicMBean} classes as well
0802: * as classes with corresponding "*MBean" interface (Standard MBeans)
0803: * or corresponding "*MXBean" interface (Java 6 MXBeans).
0804: * @param beanClass the bean class to analyze
0805: * @return whether the class qualifies as an MBean
0806: * @see org.springframework.jmx.support.JmxUtils#isMBean(Class)
0807: */
0808: protected boolean isMBean(Class beanClass) {
0809: return JmxUtils.isMBean(beanClass);
0810: }
0811:
0812: /**
0813: * Build an adapted MBean for the given bean instance, if possible.
0814: * <p>The default implementation builds a JMX 1.2 StandardMBean
0815: * for the target's MBean/MXBean interface in case of an AOP proxy,
0816: * delegating the interface's management operations to the proxy.
0817: * @param bean the original bean instance
0818: * @return the adapted MBean, or <code>null</code> if not possible
0819: */
0820: protected DynamicMBean adaptMBeanIfPossible(Object bean)
0821: throws JMException {
0822: Class targetClass = AopUtils.getTargetClass(bean);
0823: if (targetClass != bean.getClass()) {
0824: Class ifc = JmxUtils.getMXBeanInterface(targetClass);
0825: if (ifc != null) {
0826: if (!(ifc.isInstance(bean))) {
0827: throw new NotCompliantMBeanException(
0828: "Managed bean ["
0829: + bean
0830: + "] has a target class with an MXBean interface but does not exposed it in the proxy");
0831: }
0832: return new StandardMBean(bean, ifc, true);
0833: } else {
0834: ifc = JmxUtils.getMBeanInterface(targetClass);
0835: if (ifc != null) {
0836: if (!(ifc.isInstance(bean))) {
0837: throw new NotCompliantMBeanException(
0838: "Managed bean ["
0839: + bean
0840: + "] has a target class with an MXBean interface but does not exposed it in the proxy");
0841: }
0842: return new StandardMBean(bean, ifc);
0843: }
0844: }
0845: }
0846: return null;
0847: }
0848:
0849: /**
0850: * Creates an MBean that is configured with the appropriate management
0851: * interface for the supplied managed resource.
0852: * @param managedResource the resource that is to be exported as an MBean
0853: * @param beanKey the key associated with the managed bean
0854: * @see #createModelMBean()
0855: * @see #getMBeanInfo(Object, String)
0856: */
0857: protected ModelMBean createAndConfigureMBean(
0858: Object managedResource, String beanKey)
0859: throws MBeanExportException {
0860: try {
0861: ModelMBean mbean = createModelMBean();
0862: mbean.setModelMBeanInfo(getMBeanInfo(managedResource,
0863: beanKey));
0864: mbean.setManagedResource(managedResource,
0865: MR_TYPE_OBJECT_REFERENCE);
0866: return mbean;
0867: } catch (Exception ex) {
0868: throw new MBeanExportException(
0869: "Could not create ModelMBean for managed resource ["
0870: + managedResource + "] with key '"
0871: + beanKey + "'", ex);
0872: }
0873: }
0874:
0875: /**
0876: * Create an instance of a class that implements <code>ModelMBean</code>.
0877: * <p>This method is called to obtain a <code>ModelMBean</code> instance to
0878: * use when registering a bean. This method is called once per bean during the
0879: * registration phase and must return a new instance of <code>ModelMBean</code>
0880: * @return a new instance of a class that implements <code>ModelMBean</code>
0881: * @throws javax.management.MBeanException if creation of the ModelMBean failed
0882: */
0883: protected ModelMBean createModelMBean() throws MBeanException {
0884: return (this .exposeManagedResourceClassLoader ? new SpringModelMBean()
0885: : new RequiredModelMBean());
0886: }
0887:
0888: /**
0889: * Gets the <code>ModelMBeanInfo</code> for the bean with the supplied key
0890: * and of the supplied type.
0891: */
0892: private ModelMBeanInfo getMBeanInfo(Object managedBean,
0893: String beanKey) throws JMException {
0894: ModelMBeanInfo info = this .assembler.getMBeanInfo(managedBean,
0895: beanKey);
0896: if (logger.isWarnEnabled()
0897: && ObjectUtils.isEmpty(info.getAttributes())
0898: && ObjectUtils.isEmpty(info.getOperations())) {
0899: logger
0900: .warn("Bean with key '"
0901: + beanKey
0902: + "' has been registered as an MBean but has no exposed attributes or operations");
0903: }
0904: return info;
0905: }
0906:
0907: //---------------------------------------------------------------------
0908: // Autodetection process
0909: //---------------------------------------------------------------------
0910:
0911: /**
0912: * Invoked when using an <code>AutodetectCapableMBeanInfoAssembler</code>.
0913: * Gives the assembler the opportunity to add additional beans from the
0914: * <code>BeanFactory</code> to the list of beans to be exposed via JMX.
0915: * <p>This implementation prevents a bean from being added to the list
0916: * automatically if it has already been added manually, and it prevents
0917: * certain internal classes from being registered automatically.
0918: */
0919: private void autodetectBeans(
0920: final AutodetectCapableMBeanInfoAssembler assembler) {
0921: autodetect(new AutodetectCallback() {
0922: public boolean include(Class beanClass, String beanName) {
0923: return assembler.includeBean(beanClass, beanName);
0924: }
0925: });
0926: }
0927:
0928: /**
0929: * Attempts to detect any beans defined in the <code>ApplicationContext</code> that are
0930: * valid MBeans and registers them automatically with the <code>MBeanServer</code>.
0931: */
0932: private void autodetectMBeans() {
0933: autodetect(new AutodetectCallback() {
0934: public boolean include(Class beanClass, String beanName) {
0935: return isMBean(beanClass);
0936: }
0937: });
0938: }
0939:
0940: /**
0941: * Performs the actual autodetection process, delegating to an
0942: * <code>AutodetectCallback</code> instance to vote on the inclusion of a
0943: * given bean.
0944: * @param callback the <code>AutodetectCallback</code> to use when deciding
0945: * whether to include a bean or not
0946: */
0947: private void autodetect(AutodetectCallback callback) {
0948: String[] beanNames = this .beanFactory.getBeanNamesForType(null);
0949: for (int i = 0; i < beanNames.length; i++) {
0950: String beanName = beanNames[i];
0951: if (!isExcluded(beanName)) {
0952: Class beanClass = this .beanFactory.getType(beanName);
0953: if (beanClass != null
0954: && callback.include(beanClass, beanName)) {
0955: boolean lazyInit = isBeanDefinitionLazyInit(
0956: this .beanFactory, beanName);
0957: Object beanInstance = (!lazyInit ? this .beanFactory
0958: .getBean(beanName) : null);
0959: if (!this .beans.containsValue(beanName)
0960: && (beanInstance == null || !CollectionUtils
0961: .containsInstance(this .beans
0962: .values(), beanInstance))) {
0963: // Not already registered for JMX exposure.
0964: this .beans.put(beanName,
0965: (beanInstance != null ? beanInstance
0966: : beanName));
0967: if (logger.isInfoEnabled()) {
0968: logger
0969: .info("Bean with name '"
0970: + beanName
0971: + "' has been autodetected for JMX exposure");
0972: }
0973: } else {
0974: if (logger.isDebugEnabled()) {
0975: logger
0976: .debug("Bean with name '"
0977: + beanName
0978: + "' is already registered for JMX exposure");
0979: }
0980: }
0981: }
0982: }
0983: }
0984: }
0985:
0986: /**
0987: * Indicates whether or not a particular bean name is present in the excluded beans list.
0988: */
0989: private boolean isExcluded(String beanName) {
0990: return (this .excludedBeans != null && this .excludedBeans
0991: .contains(beanName));
0992: }
0993:
0994: //---------------------------------------------------------------------
0995: // Management of notification listeners
0996: //---------------------------------------------------------------------
0997:
0998: /**
0999: * If the supplied managed resource implements the {@link NotificationPublisherAware} an instance of
1000: * {@link org.springframework.jmx.export.notification.NotificationPublisher} is injected.
1001: */
1002: private void injectNotificationPublisherIfNecessary(
1003: Object managedResource, ModelMBean modelMBean,
1004: ObjectName objectName) {
1005:
1006: if (managedResource instanceof NotificationPublisherAware) {
1007: ((NotificationPublisherAware) managedResource)
1008: .setNotificationPublisher(new ModelMBeanNotificationPublisher(
1009: modelMBean, objectName, managedResource));
1010: }
1011: }
1012:
1013: /**
1014: * Register the configured {@link NotificationListener NotificationListeners}
1015: * with the {@link MBeanServer}.
1016: */
1017: private void registerNotificationListeners()
1018: throws MBeanExportException {
1019: for (int i = 0; i < this .notificationListeners.length; i++) {
1020: NotificationListenerBean bean = this .notificationListeners[i];
1021: NotificationListener listener = bean
1022: .getNotificationListener();
1023: NotificationFilter filter = bean.getNotificationFilter();
1024: Object handback = bean.getHandback();
1025: ObjectName[] namesToRegisterWith = getObjectNamesForNotificationListener(bean);
1026: for (int j = 0; j < namesToRegisterWith.length; j++) {
1027: ObjectName objectName = namesToRegisterWith[j];
1028: try {
1029: this .server.addNotificationListener(objectName,
1030: listener, filter, handback);
1031: } catch (InstanceNotFoundException ex) {
1032: throw new MBeanExportException(
1033: "Unable to register NotificationListener for MBean ["
1034: + objectName
1035: + "] because that MBean instance does not exist",
1036: ex);
1037: }
1038: }
1039: }
1040: }
1041:
1042: /**
1043: * Retrieve the {@link javax.management.ObjectName ObjectNames} for which a
1044: * {@link NotificationListener} should be registered.
1045: */
1046: private ObjectName[] getObjectNamesForNotificationListener(
1047: NotificationListenerBean bean) throws MBeanExportException {
1048:
1049: String[] mappedObjectNames = bean.getMappedObjectNames();
1050: if (mappedObjectNames != null) {
1051: ObjectName[] objectNames = new ObjectName[mappedObjectNames.length];
1052: for (int i = 0; i < mappedObjectNames.length; i++) {
1053: String mappedName = mappedObjectNames[i];
1054: try {
1055: objectNames[i] = ObjectNameManager
1056: .getInstance(mappedName);
1057: } catch (MalformedObjectNameException ex) {
1058: throw new MBeanExportException(
1059: "Invalid ObjectName ["
1060: + mappedName
1061: + "] specified for NotificationListener ["
1062: + bean.getNotificationListener()
1063: + "]", ex);
1064: }
1065: }
1066: return objectNames;
1067: } else {
1068: // Mapped to all MBeans registered by the MBeanExporter.
1069: return (ObjectName[]) this .registeredBeans
1070: .toArray(new ObjectName[this .registeredBeans.size()]);
1071: }
1072: }
1073:
1074: /**
1075: * Called when an MBean is registered. Notifies all registered
1076: * {@link MBeanExporterListener MBeanExporterListeners} of the registration event.
1077: * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime)
1078: * exception when notified, this will essentially interrupt the notification process
1079: * and any remaining listeners that have yet to be notified will not (obviously)
1080: * receive the {@link MBeanExporterListener#mbeanRegistered(javax.management.ObjectName)}
1081: * callback.
1082: * @param objectName the <code>ObjectName</code> of the registered MBean
1083: */
1084: protected void onRegister(ObjectName objectName) {
1085: notifyListenersOfRegistration(objectName);
1086: }
1087:
1088: /**
1089: * Called when an MBean is unregistered. Notifies all registered
1090: * {@link MBeanExporterListener MBeanExporterListeners} of the unregistration event.
1091: * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime)
1092: * exception when notified, this will essentially interrupt the notification process
1093: * and any remaining listeners that have yet to be notified will not (obviously)
1094: * receive the {@link MBeanExporterListener#mbeanUnregistered(javax.management.ObjectName)}
1095: * callback.
1096: * @param objectName the <code>ObjectName</code> of the unregistered MBean
1097: */
1098: protected void onUnregister(ObjectName objectName) {
1099: notifyListenersOfUnregistration(objectName);
1100: }
1101:
1102: /**
1103: * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the
1104: * registration of the MBean identified by the supplied {@link ObjectName}.
1105: */
1106: private void notifyListenersOfRegistration(ObjectName objectName) {
1107: if (this .listeners != null) {
1108: for (int i = 0; i < this .listeners.length; i++) {
1109: this .listeners[i].mbeanRegistered(objectName);
1110: }
1111: }
1112: }
1113:
1114: /**
1115: * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the
1116: * unregistration of the MBean identified by the supplied {@link ObjectName}.
1117: */
1118: private void notifyListenersOfUnregistration(ObjectName objectName) {
1119: if (this .listeners != null) {
1120: for (int i = 0; i < this .listeners.length; i++) {
1121: this .listeners[i].mbeanUnregistered(objectName);
1122: }
1123: }
1124: }
1125:
1126: //---------------------------------------------------------------------
1127: // Inner classes for internal use
1128: //---------------------------------------------------------------------
1129:
1130: /**
1131: * Internal callback interface for the autodetection process.
1132: */
1133: private static interface AutodetectCallback {
1134:
1135: /**
1136: * Called during the autodetection process to decide whether
1137: * or not a bean should be included.
1138: * @param beanClass the class of the bean
1139: * @param beanName the name of the bean
1140: */
1141: boolean include(Class beanClass, String beanName);
1142: }
1143:
1144: /**
1145: * Extension of {@link LazyInitTargetSource} that will inject a
1146: * {@link org.springframework.jmx.export.notification.NotificationPublisher}
1147: * into the lazy resource as it is created if required.
1148: */
1149: private class NotificationPublisherAwareLazyTargetSource extends
1150: LazyInitTargetSource {
1151:
1152: private ModelMBean modelMBean;
1153:
1154: private ObjectName objectName;
1155:
1156: public void setModelMBean(ModelMBean modelMBean) {
1157: this .modelMBean = modelMBean;
1158: }
1159:
1160: public void setObjectName(ObjectName objectName) {
1161: this .objectName = objectName;
1162: }
1163:
1164: protected void postProcessTargetObject(Object targetObject) {
1165: injectNotificationPublisherIfNecessary(targetObject,
1166: this.modelMBean, this.objectName);
1167: }
1168: }
1169:
1170: }
|