1 16 17 package org.springframework.jmx.export; 18 19 import java.util.ArrayList ; 20 import java.util.Arrays ; 21 import java.util.HashMap ; 22 import java.util.HashSet ; 23 import java.util.Iterator ; 24 import java.util.List ; 25 import java.util.Map ; 26 import java.util.Set ; 27 28 import javax.management.InstanceNotFoundException ; 29 import javax.management.JMException ; 30 import javax.management.MBeanException ; 31 import javax.management.MBeanServer ; 32 import javax.management.MalformedObjectNameException ; 33 import javax.management.NotificationFilter ; 34 import javax.management.NotificationListener ; 35 import javax.management.ObjectName ; 36 import javax.management.modelmbean.ModelMBean ; 37 import javax.management.modelmbean.ModelMBeanInfo ; 38 import javax.management.modelmbean.RequiredModelMBean ; 39 40 import org.springframework.aop.framework.ProxyFactory; 41 import org.springframework.aop.target.LazyInitTargetSource; 42 import org.springframework.beans.factory.BeanClassLoaderAware; 43 import org.springframework.beans.factory.BeanFactory; 44 import org.springframework.beans.factory.BeanFactoryAware; 45 import org.springframework.beans.factory.DisposableBean; 46 import org.springframework.beans.factory.InitializingBean; 47 import org.springframework.beans.factory.ListableBeanFactory; 48 import org.springframework.beans.factory.NoSuchBeanDefinitionException; 49 import org.springframework.beans.factory.config.BeanDefinition; 50 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 51 import org.springframework.core.Constants; 52 import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler; 53 import org.springframework.jmx.export.assembler.MBeanInfoAssembler; 54 import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler; 55 import org.springframework.jmx.export.naming.KeyNamingStrategy; 56 import org.springframework.jmx.export.naming.ObjectNamingStrategy; 57 import org.springframework.jmx.export.naming.SelfNaming; 58 import org.springframework.jmx.export.notification.ModelMBeanNotificationPublisher; 59 import org.springframework.jmx.export.notification.NotificationPublisherAware; 60 import org.springframework.jmx.support.JmxUtils; 61 import org.springframework.jmx.support.MBeanRegistrationSupport; 62 import org.springframework.jmx.support.ObjectNameManager; 63 import org.springframework.util.Assert; 64 import org.springframework.util.ClassUtils; 65 import org.springframework.util.CollectionUtils; 66 import org.springframework.util.ObjectUtils; 67 68 98 public class MBeanExporter extends MBeanRegistrationSupport 99 implements MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { 100 101 104 public static final int AUTODETECT_NONE = 0; 105 106 109 public static final int AUTODETECT_MBEAN = 1; 110 111 115 public static final int AUTODETECT_ASSEMBLER = 2; 116 117 120 public static final int AUTODETECT_ALL = AUTODETECT_MBEAN | AUTODETECT_ASSEMBLER; 121 122 123 127 private static final String WILDCARD = "*"; 128 129 130 private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference"; 131 132 133 private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_"; 134 135 136 137 private static final Constants constants = new Constants(MBeanExporter.class); 138 139 140 private Map beans; 141 142 143 private int autodetectMode = AUTODETECT_NONE; 144 145 146 private boolean ensureUniqueRuntimeObjectNames = true; 147 148 149 private boolean exposeManagedResourceClassLoader = false; 150 151 152 private Set excludedBeans; 153 154 155 private MBeanExporterListener[] listeners; 156 157 158 private NotificationListenerBean[] notificationListeners = new NotificationListenerBean[0]; 159 160 161 private MBeanInfoAssembler assembler = new SimpleReflectiveMBeanInfoAssembler(); 162 163 164 private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy(); 165 166 167 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 168 169 170 private ListableBeanFactory beanFactory; 171 172 173 190 public void setBeans(Map beans) { 191 this.beans = beans; 192 } 193 194 205 public void setAutodetect(boolean autodetect) { 206 this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE); 207 } 208 209 219 public void setAutodetectMode(int autodetectMode) { 220 if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(new Integer (autodetectMode))) { 221 throw new IllegalArgumentException ("Only values of autodetect constants allowed"); 222 } 223 this.autodetectMode = autodetectMode; 224 } 225 226 236 public void setAutodetectModeName(String constantName) { 237 if (constantName == null || !constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) { 238 throw new IllegalArgumentException ("Only autodetect constants allowed"); 239 } 240 this.autodetectMode = constants.asNumber(constantName).intValue(); 241 } 242 243 254 public void setAssembler(MBeanInfoAssembler assembler) { 255 this.assembler = assembler; 256 } 257 258 264 public void setNamingStrategy(ObjectNamingStrategy namingStrategy) { 265 this.namingStrategy = namingStrategy; 266 } 267 268 273 public void setListeners(MBeanExporterListener[] listeners) { 274 this.listeners = listeners; 275 } 276 277 280 public void setExcludedBeans(String [] excludedBeans) { 281 this.excludedBeans = (excludedBeans != null ? new HashSet (Arrays.asList(excludedBeans)) : null); 282 } 283 284 291 public void setEnsureUniqueRuntimeObjectNames(boolean ensureUniqueRuntimeObjectNames) { 292 this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames; 293 } 294 295 300 public void setExposeManagedResourceClassLoader(boolean exposeManagedResourceClassLoader) { 301 this.exposeManagedResourceClassLoader = exposeManagedResourceClassLoader; 302 } 303 304 311 public void setNotificationListeners(NotificationListenerBean[] notificationListeners) { 312 this.notificationListeners = notificationListeners; 313 } 314 315 326 public void setNotificationListenerMappings(Map listeners) { 327 Assert.notNull(listeners, "Property 'notificationListenerMappings' must not be null"); 328 List notificationListeners = new ArrayList (listeners.size()); 329 330 for (Iterator iterator = listeners.entrySet().iterator(); iterator.hasNext();) { 331 Map.Entry entry = (Map.Entry ) iterator.next(); 332 333 Object value = entry.getValue(); 335 if (!(value instanceof NotificationListener )) { 336 throw new IllegalArgumentException ( 337 "Map entry value [" + value + "] is not a NotificationListener"); 338 } 339 NotificationListenerBean bean = new NotificationListenerBean((NotificationListener ) value); 340 341 Object key = entry.getKey(); 343 if (key != null && !WILDCARD.equals(key)) { 344 bean.setMappedObjectName(entry.getKey().toString()); 346 } 347 348 notificationListeners.add(bean); 349 } 350 351 this.notificationListeners = (NotificationListenerBean[]) 352 notificationListeners.toArray(new NotificationListenerBean[notificationListeners.size()]); 353 } 354 355 public void setBeanClassLoader(ClassLoader classLoader) { 356 this.beanClassLoader = classLoader; 357 } 358 359 367 public void setBeanFactory(BeanFactory beanFactory) { 368 if (beanFactory instanceof ListableBeanFactory) { 369 this.beanFactory = (ListableBeanFactory) beanFactory; 370 } 371 else { 372 logger.info("MBeanExporter not running in a ListableBeanFactory: Autodetection of MBeans not available."); 373 } 374 } 375 376 377 381 386 public void afterPropertiesSet() { 387 logger.info("Registering beans for JMX exposure on startup"); 388 registerBeans(); 389 } 390 391 395 public void destroy() { 396 logger.info("Unregistering JMX-exposed beans on shutdown"); 397 unregisterBeans(); 398 } 399 400 401 405 public ObjectName registerManagedResource(Object managedResource) throws MBeanExportException { 406 Assert.notNull(managedResource, "Managed resource must not be null"); 407 try { 408 ObjectName objectName = getObjectName(managedResource, null); 409 if (this.ensureUniqueRuntimeObjectNames) { 410 objectName = JmxUtils.appendIdentityToObjectName(objectName, managedResource); 411 } 412 registerManagedResource(managedResource, objectName); 413 return objectName; 414 } 415 catch (MalformedObjectNameException ex) { 416 throw new MBeanExportException("Unable to generate ObjectName for MBean [" + managedResource + "]", ex); 417 } 418 } 419 420 public void registerManagedResource(Object managedResource, ObjectName objectName) throws MBeanExportException { 421 Assert.notNull(managedResource, "Managed resource must not be null"); 422 Assert.notNull(objectName, "ObjectName must not be null"); 423 Object mbean = null; 424 if (isMBean(managedResource.getClass())) { 425 mbean = managedResource; 426 } 427 else { 428 mbean = createAndConfigureMBean(managedResource, managedResource.getClass().getName()); 429 } 430 try { 431 doRegister(mbean, objectName); 432 } 433 catch (JMException ex) { 434 throw new UnableToRegisterMBeanException( 435 "Unable to register MBean [" + managedResource + "] with object name [" + objectName + "]", ex); 436 } 437 } 438 439 440 444 456 protected void registerBeans() { 457 if (this.server == null) { 461 this.server = JmxUtils.locateMBeanServer(); 462 } 463 464 if (this.beans == null) { 467 this.beans = new HashMap (); 468 } 469 470 if (this.autodetectMode != AUTODETECT_NONE) { 472 if (this.beanFactory == null) { 473 throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory"); 474 } 475 476 if (isAutodetectModeEnabled(AUTODETECT_MBEAN)) { 477 logger.info("Autodetecting user-defined JMX MBeans"); 479 autodetectMBeans(); 480 } 481 482 if (isAutodetectModeEnabled(AUTODETECT_ASSEMBLER) && 484 this.assembler instanceof AutodetectCapableMBeanInfoAssembler) { 485 autodetectBeans((AutodetectCapableMBeanInfoAssembler) this.assembler); 486 } 487 } 488 489 if (this.beans.isEmpty()) { 491 throw new IllegalArgumentException ("Must specify at least one bean for registration"); 492 } 493 494 try { 495 for (Iterator it = this.beans.entrySet().iterator(); it.hasNext();) { 496 Map.Entry entry = (Map.Entry ) it.next(); 497 String beanKey = (String ) entry.getKey(); 498 Object value = entry.getValue(); 499 registerBeanNameOrInstance(value, beanKey); 500 } 501 502 registerNotificationListeners(); 504 } 505 catch (MBeanExportException ex) { 506 unregisterBeans(); 508 throw ex; 509 } 510 } 511 512 519 protected boolean isBeanDefinitionLazyInit(ListableBeanFactory beanFactory, String beanName) { 520 if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { 521 return false; 522 } 523 try { 524 BeanDefinition bd = ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName); 525 return bd.isLazyInit(); 526 } 527 catch (NoSuchBeanDefinitionException ex) { 528 return false; 530 } 531 } 532 533 552 protected ObjectName registerBeanNameOrInstance(Object mapValue, String beanKey) throws MBeanExportException { 553 try { 554 if (mapValue instanceof String ) { 555 if (this.beanFactory == null) { 557 throw new MBeanExportException("Cannot resolve bean names if not running in a BeanFactory"); 558 } 559 String beanName = (String ) mapValue; 560 if (isBeanDefinitionLazyInit(this.beanFactory, beanName)) { 561 return registerLazyInit(beanName, beanKey); 562 } 563 else { 564 Object bean = this.beanFactory.getBean(beanName); 565 return registerBeanInstance(bean, beanKey); 566 } 567 } 568 else { 569 return registerBeanInstance(mapValue, beanKey); 571 } 572 } 573 catch (JMException ex) { 574 throw new UnableToRegisterMBeanException( 575 "Unable to register MBean [" + mapValue + "] with key '" + beanKey + "'", ex); 576 } 577 } 578 579 587 private ObjectName registerBeanInstance(Object bean, String beanKey) throws JMException { 588 ObjectName objectName = getObjectName(bean, beanKey); 589 if (isMBean(bean.getClass())) { 590 if (logger.isDebugEnabled()) { 591 logger.debug("Located MBean '" + beanKey + "': registering with JMX server as MBean [" + 592 objectName + "]"); 593 } 594 doRegister(bean, objectName); 595 } 596 else { 597 if (logger.isDebugEnabled()) { 598 logger.debug("Located simple bean '" + beanKey + "': registering with JMX server as MBean [" + 599 objectName + "]"); 600 } 601 ModelMBean mbean = createAndConfigureMBean(bean, beanKey); 602 doRegister(mbean, objectName); 603 injectNotificationPublisherIfNecessary(bean, mbean, objectName); 604 } 605 return objectName; 606 } 607 608 616 private ObjectName registerLazyInit(String beanName, String beanKey) throws JMException { 617 ProxyFactory proxyFactory = new ProxyFactory(); 618 proxyFactory.setProxyTargetClass(true); 619 proxyFactory.setFrozen(true); 620 621 if (isMBean(this.beanFactory.getType(beanName))) { 622 LazyInitTargetSource targetSource = new LazyInitTargetSource(); 624 targetSource.setTargetBeanName(beanName); 625 targetSource.setBeanFactory(this.beanFactory); 626 proxyFactory.setTargetSource(targetSource); 627 628 Object proxy = proxyFactory.getProxy(this.beanClassLoader); 629 ObjectName objectName = getObjectName(proxy, beanKey); 630 if (logger.isDebugEnabled()) { 631 logger.debug("Located MBean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + 632 objectName + "]"); 633 } 634 doRegister(proxy, objectName); 635 return objectName; 636 } 637 638 else { 639 NotificationPublisherAwareLazyTargetSource targetSource = new NotificationPublisherAwareLazyTargetSource(); 641 targetSource.setTargetBeanName(beanName); 642 targetSource.setBeanFactory(this.beanFactory); 643 proxyFactory.setTargetSource(targetSource); 644 645 Object proxy = proxyFactory.getProxy(this.beanClassLoader); 646 ObjectName objectName = getObjectName(proxy, beanKey); 647 if (logger.isDebugEnabled()) { 648 logger.debug("Located simple bean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + 649 objectName + "]"); 650 } 651 ModelMBean mbean = createAndConfigureMBean(proxy, beanKey); 652 targetSource.setModelMBean(mbean); 653 targetSource.setObjectName(objectName); 654 doRegister(mbean, objectName); 655 return objectName; 656 } 657 } 658 659 670 protected ObjectName getObjectName(Object bean, String beanKey) throws MalformedObjectNameException { 671 if (bean instanceof SelfNaming) { 672 return ((SelfNaming) bean).getObjectName(); 673 } 674 else { 675 return this.namingStrategy.getObjectName(bean, beanKey); 676 } 677 } 678 679 689 protected boolean isMBean(Class beanClass) { 690 return JmxUtils.isMBean(beanClass); 691 } 692 693 701 protected ModelMBean createAndConfigureMBean(Object managedResource, String beanKey) 702 throws MBeanExportException { 703 try { 704 ModelMBean mbean = createModelMBean(); 705 mbean.setModelMBeanInfo(getMBeanInfo(managedResource, beanKey)); 706 mbean.setManagedResource(managedResource, MR_TYPE_OBJECT_REFERENCE); 707 return mbean; 708 } 709 catch (Exception ex) { 710 throw new MBeanExportException("Could not create ModelMBean for managed resource [" + 711 managedResource + "] with key '" + beanKey + "'", ex); 712 } 713 } 714 715 723 protected ModelMBean createModelMBean() throws MBeanException { 724 return (this.exposeManagedResourceClassLoader ? new SpringModelMBean() : new RequiredModelMBean ()); 725 } 726 727 731 private ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException { 732 ModelMBeanInfo info = this.assembler.getMBeanInfo(managedBean, beanKey); 733 if (logger.isWarnEnabled() && ObjectUtils.isEmpty(info.getAttributes()) && 734 ObjectUtils.isEmpty(info.getOperations())) { 735 logger.warn("Bean with key '" + beanKey + 736 "' has been registered as an MBean but has no exposed attributes or operations"); 737 } 738 return info; 739 } 740 741 742 746 750 private boolean isAutodetectModeEnabled(int mode) { 751 return (this.autodetectMode & mode) == mode; 752 } 753 754 762 private void autodetectBeans(final AutodetectCapableMBeanInfoAssembler assembler) { 763 autodetect(new AutodetectCallback() { 764 public boolean include(Class beanClass, String beanName) { 765 return assembler.includeBean(beanClass, beanName); 766 } 767 }); 768 } 769 770 774 private void autodetectMBeans() { 775 autodetect(new AutodetectCallback() { 776 public boolean include(Class beanClass, String beanName) { 777 return isMBean(beanClass); 778 } 779 }); 780 } 781 782 789 private void autodetect(AutodetectCallback callback) { 790 String [] beanNames = this.beanFactory.getBeanNamesForType(null); 791 for (int i = 0; i < beanNames.length; i++) { 792 String beanName = beanNames[i]; 793 if (!isExcluded(beanName)) { 794 Class beanClass = this.beanFactory.getType(beanName); 795 if (beanClass != null && callback.include(beanClass, beanName)) { 796 boolean lazyInit = isBeanDefinitionLazyInit(this.beanFactory, beanName); 797 Object beanInstance = (!lazyInit ? this.beanFactory.getBean(beanName) : null); 798 if (!this.beans.containsValue(beanName) && 799 (beanInstance == null || !CollectionUtils.containsInstance(this.beans.values(), beanInstance))) { 800 this.beans.put(beanName, (beanInstance != null ? beanInstance : beanName)); 802 if (logger.isInfoEnabled()) { 803 logger.info("Bean with name '" + beanName + "' has been autodetected for JMX exposure"); 804 } 805 } 806 else { 807 if (logger.isDebugEnabled()) { 808 logger.debug("Bean with name '" + beanName + "' is already registered for JMX exposure"); 809 } 810 } 811 } 812 } 813 } 814 } 815 816 819 private boolean isExcluded(String beanName) { 820 return (this.excludedBeans != null && this.excludedBeans.contains(beanName)); 821 } 822 823 824 828 832 private void injectNotificationPublisherIfNecessary( 833 Object managedResource, ModelMBean modelMBean, ObjectName objectName) { 834 if (managedResource instanceof NotificationPublisherAware) { 835 ((NotificationPublisherAware) managedResource).setNotificationPublisher( 836 new ModelMBeanNotificationPublisher(modelMBean, objectName, managedResource)); 837 } 838 } 839 840 844 private void registerNotificationListeners() throws MBeanExportException { 845 for (int i = 0; i < this.notificationListeners.length; i++) { 846 NotificationListenerBean bean = this.notificationListeners[i]; 847 NotificationListener listener = bean.getNotificationListener(); 848 NotificationFilter filter = bean.getNotificationFilter(); 849 Object handback = bean.getHandback(); 850 ObjectName [] namesToRegisterWith = getObjectNamesForNotificationListener(bean); 851 for (int j = 0; j < namesToRegisterWith.length; j++) { 852 ObjectName objectName = namesToRegisterWith[j]; 853 try { 854 this.server.addNotificationListener(objectName, listener, filter, handback); 855 } 856 catch (InstanceNotFoundException ex) { 857 throw new MBeanExportException("Unable to register NotificationListener for MBean [" + 858 objectName + "] because that MBean instance does not exist", ex); 859 } 860 } 861 } 862 } 863 864 868 private ObjectName [] getObjectNamesForNotificationListener(NotificationListenerBean bean) 869 throws MBeanExportException { 870 871 String [] mappedObjectNames = bean.getMappedObjectNames(); 872 if (mappedObjectNames != null) { 873 ObjectName [] objectNames = new ObjectName [mappedObjectNames.length]; 874 for (int i = 0; i < mappedObjectNames.length; i++) { 875 String mappedName = mappedObjectNames[i]; 876 try { 877 objectNames[i] = ObjectNameManager.getInstance(mappedName); 878 } 879 catch (MalformedObjectNameException ex) { 880 throw new MBeanExportException( 881 "Invalid ObjectName [" + mappedName + "] specified for NotificationListener [" + 882 bean.getNotificationListener() + "]", ex); 883 } 884 } 885 return objectNames; 886 } 887 else { 888 return (ObjectName []) this.registeredBeans.toArray(new ObjectName [this.registeredBeans.size()]); 890 } 891 } 892 893 903 protected void onRegister(ObjectName objectName) { 904 notifyListenersOfRegistration(objectName); 905 } 906 907 917 protected void onUnregister(ObjectName objectName) { 918 notifyListenersOfUnregistration(objectName); 919 } 920 921 922 926 private void notifyListenersOfRegistration(ObjectName objectName) { 927 if (this.listeners != null) { 928 for (int i = 0; i < this.listeners.length; i++) { 929 this.listeners[i].mbeanRegistered(objectName); 930 } 931 } 932 } 933 934 938 private void notifyListenersOfUnregistration(ObjectName objectName) { 939 if (this.listeners != null) { 940 for (int i = 0; i < this.listeners.length; i++) { 941 this.listeners[i].mbeanUnregistered(objectName); 942 } 943 } 944 } 945 946 947 951 954 private static interface AutodetectCallback { 955 956 962 boolean include(Class beanClass, String beanName); 963 } 964 965 966 971 private class NotificationPublisherAwareLazyTargetSource extends LazyInitTargetSource { 972 973 private ModelMBean modelMBean; 974 975 private ObjectName objectName; 976 977 public void setModelMBean(ModelMBean modelMBean) { 978 this.modelMBean = modelMBean; 979 } 980 981 public void setObjectName(ObjectName objectName) { 982 this.objectName = objectName; 983 } 984 985 protected void postProcessTargetObject(Object targetObject) { 986 injectNotificationPublisherIfNecessary(targetObject, this.modelMBean, this.objectName); 987 } 988 } 989 990 } 991 | Popular Tags |