Java tutorial
/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.module.management.agent; import org.mule.AbstractAgent; import org.mule.api.MuleException; import org.mule.api.MuleRuntimeException; import org.mule.api.context.notification.MuleContextNotificationListener; import org.mule.api.lifecycle.InitialisationException; import org.mule.api.model.Model; import org.mule.api.service.Service; import org.mule.api.transport.Connector; import org.mule.api.transport.MessageReceiver; import org.mule.config.i18n.CoreMessages; import org.mule.config.spring.SpringRegistry; import org.mule.construct.AbstractFlowConstruct; import org.mule.context.notification.MuleContextNotification; import org.mule.context.notification.NotificationException; import org.mule.management.stats.FlowConstructStatistics; import org.mule.module.management.i18n.ManagementMessages; import org.mule.module.management.mbean.ApplicationService; import org.mule.module.management.mbean.ConnectorService; import org.mule.module.management.mbean.ConnectorServiceMBean; import org.mule.module.management.mbean.EndpointService; import org.mule.module.management.mbean.EndpointServiceMBean; import org.mule.module.management.mbean.FlowConstructService; import org.mule.module.management.mbean.FlowConstructServiceMBean; import org.mule.module.management.mbean.ModelService; import org.mule.module.management.mbean.ModelServiceMBean; import org.mule.module.management.mbean.MuleConfigurationService; import org.mule.module.management.mbean.MuleConfigurationServiceMBean; import org.mule.module.management.mbean.MuleService; import org.mule.module.management.mbean.MuleServiceMBean; import org.mule.module.management.mbean.ServiceService; import org.mule.module.management.mbean.ServiceServiceMBean; import org.mule.module.management.mbean.StatisticsService; import org.mule.module.management.mbean.StatisticsServiceMBean; import org.mule.module.management.support.AutoDiscoveryJmxSupportFactory; import org.mule.module.management.support.JmxSupport; import org.mule.module.management.support.JmxSupportFactory; import org.mule.module.management.support.SimplePasswordJmxAuthenticator; import org.mule.transport.AbstractConnector; import org.mule.util.StringUtils; import java.lang.management.ManagementFactory; import java.net.URI; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.ExportException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; /** * <code>AbstractJmxAgent</code> registers Mule Jmx management beans with an MBean server. */ public abstract class AbstractJmxAgent extends AbstractAgent { public static final String NAME = "jmx-agent"; public static final String DEFAULT_REMOTING_URI = "service:jmx:rmi:///jndi/rmi://localhost:1099/server"; // populated with values below in a static initializer public static final Map<String, String> DEFAULT_CONNECTOR_SERVER_PROPERTIES; /** * Default JMX Authenticator to use for securing remote access. */ public static final String DEFAULT_JMX_AUTHENTICATOR = SimplePasswordJmxAuthenticator.class.getName(); /** * Logger used by this class */ protected static final Log logger = LogFactory.getLog(AbstractJmxAgent.class); /** * Should MBeanServer be discovered. */ protected boolean locateServer = true; protected boolean containerMode = true; // don't create mbean server by default, use a platform mbean server private boolean createServer = false; private String connectorServerUrl; private MBeanServer mBeanServer; private JMXConnectorServer connectorServer; private Map<String, Object> connectorServerProperties = null; private boolean enableStatistics = true; private final AtomicBoolean serverCreated = new AtomicBoolean(false); private final AtomicBoolean initialized = new AtomicBoolean(false); private JmxSupportFactory jmxSupportFactory = AutoDiscoveryJmxSupportFactory.getInstance(); private JmxSupport jmxSupport = jmxSupportFactory.getJmxSupport(); private ConfigurableJMXAuthenticator jmxAuthenticator; //Used is RMI is being used private Registry rmiRegistry; private boolean createRmiRegistry = true; /** * Username/password combinations for JMX Remoting authentication. */ private Map<String, String> credentials = new HashMap<String, String>(); private AbstractJmxAgent.MuleContextStartedListener muleContextStartedListener; private AbstractJmxAgent.MuleContextStoppedListener muleContextStoppedListener; static { Map<String, String> props = new HashMap<String, String>(1); props.put(RMIConnectorServer.JNDI_REBIND_ATTRIBUTE, "true"); DEFAULT_CONNECTOR_SERVER_PROPERTIES = Collections.unmodifiableMap(props); } public AbstractJmxAgent() { super(NAME); connectorServerProperties = new HashMap<String, Object>(DEFAULT_CONNECTOR_SERVER_PROPERTIES); } @Override public String getDescription() { if (connectorServerUrl != null) { return name + ": " + connectorServerUrl; } else { return "JMX Agent"; } } /** * {@inheritDoc} */ public void initialise() throws InitialisationException { if (initialized.get()) { return; } this.containerMode = muleContext.getConfiguration().isContainerMode(); try { Object agent = muleContext.getRegistry().lookupObject(this.getClass()); // if we find ourselves, but not initialized yet - proceed with init, otherwise return if (agent == this && this.initialized.get()) { if (logger.isDebugEnabled()) { logger.debug("Found an existing JMX agent in the registry, we're done here."); } return; } } catch (Exception e) { throw new InitialisationException(e, this); } if (mBeanServer == null && createServer) { // here we create a new mbean server, not using a platform one mBeanServer = MBeanServerFactory.createMBeanServer(); serverCreated.set(true); } if (mBeanServer == null && locateServer) { mBeanServer = ManagementFactory.getPlatformMBeanServer(); } if (mBeanServer == null) { throw new InitialisationException(ManagementMessages.cannotLocateOrCreateServer(), this); } if (StringUtils.isBlank(muleContext.getConfiguration().getId())) { // TODO i18n the message properly throw new IllegalArgumentException( "Manager ID is mandatory when running with JmxAgent. Give your Mule configuration a valid ID."); } try { // We need to register all the services once the server has initialised muleContextStartedListener = new MuleContextStartedListener(); muleContext.registerListener(muleContextStartedListener); // and unregister once context stopped muleContextStoppedListener = new MuleContextStoppedListener(); muleContext.registerListener(muleContextStoppedListener); } catch (NotificationException e) { throw new InitialisationException(e, this); } initialized.compareAndSet(false, true); } protected void initRMI() throws Exception { String connectUri = (connectorServerUrl != null ? connectorServerUrl : StringUtils.EMPTY); if (connectUri.contains("jmx:rmi")) { int i = connectUri.lastIndexOf("rmi://"); URI uri = new URI(connectUri.substring(i)); if (rmiRegistry == null) { try { if (isCreateRmiRegistry()) { try { rmiRegistry = LocateRegistry.createRegistry(uri.getPort()); } catch (ExportException e) { logger.info("Registry on " + uri + " already bound. Attempting to use that instead"); rmiRegistry = LocateRegistry.getRegistry(uri.getHost(), uri.getPort()); } } else { rmiRegistry = LocateRegistry.getRegistry(uri.getHost(), uri.getPort()); } } catch (RemoteException e) { throw new InitialisationException(e, this); } } } } public void start() throws MuleException { try { // TODO cleanup rmi registry creation too initRMI(); if (connectorServerUrl == null) { return; } logger.info("Creating and starting JMX agent connector Server"); JMXServiceURL url = new JMXServiceURL(connectorServerUrl); if (connectorServerProperties == null) { connectorServerProperties = new HashMap<String, Object>(DEFAULT_CONNECTOR_SERVER_PROPERTIES); } if (!credentials.isEmpty()) { connectorServerProperties.put(JMXConnectorServer.AUTHENTICATOR, this.getJmxAuthenticator()); } connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, connectorServerProperties, mBeanServer); connectorServer.start(); } catch (ExportException e) { throw new JmxManagementException(CoreMessages.failedToStart("Jmx Agent"), e); } catch (Exception e) { throw new JmxManagementException(CoreMessages.failedToStart("Jmx Agent"), e); } } public void stop() throws MuleException { if (connectorServer != null) { try { connectorServer.stop(); } catch (Exception e) { throw new JmxManagementException(CoreMessages.failedToStop("Jmx Connector"), e); } } } /** * {@inheritDoc} */ public void dispose() { unregisterMBeansIfNecessary(); unregisterListeners(); if (serverCreated.get()) { MBeanServerFactory.releaseMBeanServer(mBeanServer); } mBeanServer = null; serverCreated.compareAndSet(true, false); initialized.set(false); } private void unregisterListeners() { muleContext.unregisterListener(muleContextStartedListener); muleContext.unregisterListener(muleContextStoppedListener); } /** * Register a Java Service Wrapper agent. * * @throws org.mule.api.MuleException if registration failed */ protected void registerWrapperService() throws MuleException { // WrapperManager to support restarts final WrapperManagerAgent wmAgent = new WrapperManagerAgent(); if (muleContext.getRegistry().lookupAgent(wmAgent.getName()) == null) { muleContext.getRegistry().registerAgent(wmAgent); } } protected void registerStatisticsService() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { ObjectName on = jmxSupport.getObjectName(String.format("%s:%s", jmxSupport.getDomainName(muleContext, !containerMode), StatisticsServiceMBean.DEFAULT_JMX_NAME)); StatisticsService service = new StatisticsService(); service.setMuleContext(muleContext); service.setEnabled(isEnableStatistics()); ClassloaderSwitchingMBeanWrapper mBean = new ClassloaderSwitchingMBeanWrapper(service, StatisticsServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering statistics with name: " + on); mBeanServer.registerMBean(mBean, on); } protected void registerModelServices() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { for (Model model : muleContext.getRegistry().lookupObjects(Model.class)) { ModelServiceMBean service = new ModelService(model); String rawName = service.getName() + "(" + service.getType() + ")"; String name = jmxSupport.escape(rawName); final String jmxName = String.format("%s:%s%s", jmxSupport.getDomainName(muleContext, !containerMode), ModelServiceMBean.DEFAULT_JMX_NAME_PREFIX, name); ObjectName on = jmxSupport.getObjectName(jmxName); ClassloaderSwitchingMBeanWrapper mBean = new ClassloaderSwitchingMBeanWrapper(service, ModelServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering model with name: " + on); mBeanServer.registerMBean(mBean, on); } } protected void registerMuleService() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { ObjectName on = jmxSupport.getObjectName(String.format("%s:%s", jmxSupport.getDomainName(muleContext, !containerMode), MuleServiceMBean.DEFAULT_JMX_NAME)); if (muleContext.getConfiguration().isContainerMode() && mBeanServer.isRegistered(on)) { // while in container mode, a previous stop() action leaves MuleContext MBean behind for remote start() operation return; } MuleService service = new MuleService(muleContext); ClassloaderSwitchingMBeanWrapper serviceMBean = new ClassloaderSwitchingMBeanWrapper(service, MuleServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering mule with name: " + on); mBeanServer.registerMBean(serviceMBean, on); } protected void registerConfigurationService() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { ObjectName on = jmxSupport .getObjectName(String.format("%s:%s", jmxSupport.getDomainName(muleContext, !containerMode), MuleConfigurationServiceMBean.DEFAULT_JMX_NAME)); MuleConfigurationServiceMBean service = new MuleConfigurationService(muleContext.getConfiguration()); ClassloaderSwitchingMBeanWrapper mBean = new ClassloaderSwitchingMBeanWrapper(service, MuleConfigurationServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering configuration with name: " + on); mBeanServer.registerMBean(mBean, on); } protected void registerServiceServices() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { for (Service service : muleContext.getRegistry().lookupObjects(Service.class)) { final String rawName = service.getName(); final String escapedName = jmxSupport.escape(rawName); final String jmxName = String.format("%s:%s%s", jmxSupport.getDomainName(muleContext, !containerMode), ServiceServiceMBean.DEFAULT_JMX_NAME_PREFIX, escapedName); ObjectName on = jmxSupport.getObjectName(jmxName); ServiceServiceMBean serviceMBean = new ServiceService(rawName, muleContext); ClassloaderSwitchingMBeanWrapper wrapper = new ClassloaderSwitchingMBeanWrapper(serviceMBean, ServiceServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering service with name: " + on); mBeanServer.registerMBean(wrapper, on); } } protected void registerFlowConstructServices() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { for (AbstractFlowConstruct flowConstruct : muleContext.getRegistry() .lookupObjects(AbstractFlowConstruct.class)) { final String rawName = flowConstruct.getName(); final String name = jmxSupport.escape(rawName); final String jmxName = String.format("%s:type=%s,name=%s", jmxSupport.getDomainName(muleContext, !containerMode), flowConstruct.getConstructType(), name); ObjectName on = jmxSupport.getObjectName(jmxName); FlowConstructServiceMBean fcMBean = new FlowConstructService(flowConstruct.getConstructType(), rawName, muleContext, flowConstruct.getStatistics()); ClassloaderSwitchingMBeanWrapper wrapper = new ClassloaderSwitchingMBeanWrapper(fcMBean, FlowConstructServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering service with name: " + on); mBeanServer.registerMBean(wrapper, on); } } protected void registerApplicationServices() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { FlowConstructStatistics appStats = muleContext.getStatistics().getApplicationStatistics(); if (appStats != null) { final String rawName = appStats.getName(); final String name = jmxSupport.escape(rawName); final String jmxName = String.format("%s:type=%s,name=%s", jmxSupport.getDomainName(muleContext, !containerMode), appStats.getFlowConstructType(), name); ObjectName on = jmxSupport.getObjectName(jmxName); FlowConstructServiceMBean fcMBean = new ApplicationService(appStats.getFlowConstructType(), rawName, muleContext, appStats); ClassloaderSwitchingMBeanWrapper wrapper = new ClassloaderSwitchingMBeanWrapper(fcMBean, FlowConstructServiceMBean.class, muleContext.getExecutionClassLoader()); logger.debug("Registering application statistics with name: " + on); mBeanServer.registerMBean(wrapper, on); } } protected void registerEndpointServices() throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException { for (Connector connector : muleContext.getRegistry().lookupObjects(Connector.class)) { if (connector instanceof AbstractConnector) { for (MessageReceiver messageReceiver : ((AbstractConnector) connector).getReceivers().values()) { if (muleContext.equals(messageReceiver.getFlowConstruct().getMuleContext())) { EndpointServiceMBean service = new EndpointService(messageReceiver); String fullName = buildFullyQualifiedEndpointName(service, connector); if (logger.isInfoEnabled()) { logger.info("Attempting to register service with name: " + fullName); } ObjectName on = jmxSupport.getObjectName(fullName); ClassloaderSwitchingMBeanWrapper mBean = new ClassloaderSwitchingMBeanWrapper(service, EndpointServiceMBean.class, muleContext.getExecutionClassLoader()); mBeanServer.registerMBean(mBean, on); if (logger.isInfoEnabled()) { logger.info("Registered Endpoint Service with name: " + on); } } } } else { logger.warn("Connector: " + connector + " is not an istance of AbstractConnector, cannot obtain Endpoint MBeans from it"); } } } protected String buildFullyQualifiedEndpointName(EndpointServiceMBean mBean, Connector connector) { String rawName = jmxSupport.escape(mBean.getName()); StringBuilder fullName = new StringBuilder(128); fullName.append(jmxSupport.getDomainName(muleContext, !containerMode)); fullName.append(":type=Endpoint,service="); fullName.append(jmxSupport.escape(mBean.getComponentName())); fullName.append(",connector="); fullName.append(connector.getName()); fullName.append(",name="); fullName.append(rawName); return fullName.toString(); } protected void registerConnectorServices() throws MalformedObjectNameException, NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException { for (Connector connector : muleContext.getRegistry().lookupLocalObjects(Connector.class)) { ConnectorServiceMBean service = new ConnectorService(connector); final String rawName = service.getName(); final String name = jmxSupport.escape(rawName); final String jmxName = String.format("%s:%s%s", jmxSupport.getDomainName(muleContext, !containerMode), ConnectorServiceMBean.DEFAULT_JMX_NAME_PREFIX, name); if (logger.isDebugEnabled()) { logger.debug("Attempting to register service with name: " + jmxName); } ObjectName oName = jmxSupport.getObjectName(jmxName); ClassloaderSwitchingMBeanWrapper mBean = new ClassloaderSwitchingMBeanWrapper(service, ConnectorServiceMBean.class, muleContext.getExecutionClassLoader()); mBeanServer.registerMBean(mBean, oName); logger.info("Registered Connector Service with name " + oName); } } public boolean isCreateServer() { return createServer; } public void setCreateServer(boolean createServer) { this.createServer = createServer; } public boolean isLocateServer() { return locateServer; } public void setLocateServer(boolean locateServer) { this.locateServer = locateServer; } public String getConnectorServerUrl() { return connectorServerUrl; } public void setConnectorServerUrl(String connectorServerUrl) { this.connectorServerUrl = connectorServerUrl; } public boolean isEnableStatistics() { return enableStatistics; } public void setEnableStatistics(boolean enableStatistics) { this.enableStatistics = enableStatistics; } public MBeanServer getMBeanServer() { return mBeanServer; } public void setMBeanServer(MBeanServer mBeanServer) { this.mBeanServer = mBeanServer; } public Map<String, Object> getConnectorServerProperties() { return connectorServerProperties; } /** * Setter for property 'connectorServerProperties'. Set to {@code null} to use defaults ({@link * #DEFAULT_CONNECTOR_SERVER_PROPERTIES}). Pass in an empty map to use no parameters. * Passing a non-empty map will replace defaults. * * @param connectorServerProperties Value to set for property 'connectorServerProperties'. */ public void setConnectorServerProperties(Map<String, Object> connectorServerProperties) { this.connectorServerProperties = connectorServerProperties; } public JmxSupportFactory getJmxSupportFactory() { return jmxSupportFactory; } public void setJmxSupportFactory(JmxSupportFactory jmxSupportFactory) { this.jmxSupportFactory = jmxSupportFactory; } /** * Setter for property 'credentials'. * * @param newCredentials Value to set for property 'credentials'. */ public void setCredentials(final Map<String, String> newCredentials) { this.credentials.clear(); if (newCredentials != null && !newCredentials.isEmpty()) { this.credentials.putAll(newCredentials); } } protected void unregisterMBeansIfNecessary() { unregisterMBeansIfNecessary(false); } /** * @param containerMode when true, MuleContext will still be exposed to enable the 'start' operation */ protected void unregisterMBeansIfNecessary(boolean containerMode) { if (mBeanServer == null) { return; } try { // note that we don't try to resolve a domain name clash here. // e.g. when stopping an app via jmx, we want to obtain current domain only, // but the execution thread is different, and doesn't have the resolved domain info final String domain = jmxSupport.getDomainName(muleContext, false); ObjectName query = jmxSupport.getObjectName(domain + ":*"); Set<ObjectName> mbeans = mBeanServer.queryNames(query, null); while (!mbeans.isEmpty()) { ObjectName name = mbeans.iterator().next(); try { if (!(containerMode && MuleServiceMBean.DEFAULT_JMX_NAME .equals(name.getCanonicalKeyPropertyListString()))) { mBeanServer.unregisterMBean(name); } } catch (Exception e) { logger.warn( String.format("Failed to unregister MBean: %s. Error is: %s", name, e.getMessage())); } // query mbeans again, as some mbeans have cascaded unregister operations, // this prevents duplicate unregister attempts mbeans = mBeanServer.queryNames(query, null); if (containerMode) { // filter out MuleContext MBean to avoid an endless loop mbeans.remove(jmxSupport .getObjectName(String.format("%s:%s", domain, MuleServiceMBean.DEFAULT_JMX_NAME))); } } } catch (MalformedObjectNameException e) { logger.warn("Failed to create ObjectName query", e); } } public Registry getRmiRegistry() { return rmiRegistry; } public void setRmiRegistry(Registry rmiRegistry) { this.rmiRegistry = rmiRegistry; } public boolean isCreateRmiRegistry() { return createRmiRegistry; } public void setCreateRmiRegistry(boolean createRmiRegistry) { this.createRmiRegistry = createRmiRegistry; } protected class MuleContextStartedListener implements MuleContextNotificationListener<MuleContextNotification> { public void onNotification(MuleContextNotification notification) { if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED) { try { registerServices(); } catch (Exception e) { throw new MuleRuntimeException(CoreMessages.objectFailedToInitialise("MBeans"), e); } } } } protected abstract void registerServices() throws MuleException, NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException, MalformedObjectNameException; protected class MuleContextStoppedListener implements MuleContextNotificationListener<MuleContextNotification> { public void onNotification(MuleContextNotification notification) { if (notification.getAction() == MuleContextNotification.CONTEXT_STOPPED) { boolean containerMode = notification.getMuleContext().getConfiguration().isContainerMode(); unregisterMBeansIfNecessary(containerMode); } } } public ConfigurableJMXAuthenticator getJmxAuthenticator() { if (this.jmxAuthenticator == null) { this.jmxAuthenticator = new SimplePasswordJmxAuthenticator(); this.jmxAuthenticator.configure(credentials); } return jmxAuthenticator; } public void setJmxAuthenticator(ConfigurableJMXAuthenticator jmxAuthenticator) { this.jmxAuthenticator = jmxAuthenticator; } }