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