All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.jmx.MBeanContainer Maven / Gradle / Ivy

//
//  ========================================================================
//  Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.jmx;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;

import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Container;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * Container class for the MBean instances
 */
@ManagedObject("The component that registers beans as MBeans")
public class MBeanContainer implements Container.InheritedListener, Dumpable, Destroyable
{
    private static final Logger LOG = Log.getLogger(MBeanContainer.class.getName());
    private static final ConcurrentMap __unique = new ConcurrentHashMap<>();
    private static final Container ROOT = new ContainerLifeCycle();

    private final MBeanServer _mbeanServer;
    private final boolean _useCacheForOtherClassLoaders;
    private final ConcurrentMap _metaData = new ConcurrentHashMap<>();
    private final ConcurrentMap _beans = new ConcurrentHashMap<>();
    private final ConcurrentMap _mbeans = new ConcurrentHashMap<>();
    private String _domain = null;

    /**
     * Constructs MBeanContainer
     *
     * @param server instance of MBeanServer for use by container
     */
    public MBeanContainer(MBeanServer server)
    {
        this(server, true);
    }

    /**
     * Constructs MBeanContainer
     *
     * @param server instance of MBeanServer for use by container
     * @param cacheOtherClassLoaders If true,  MBeans from other classloaders (eg WebAppClassLoader) will be cached.
     * The cache is never flushed, so this should be false if some classloaders do not live forever.
     */
    public MBeanContainer(MBeanServer server, boolean cacheOtherClassLoaders)
    {
        _mbeanServer = server;
        _useCacheForOtherClassLoaders = cacheOtherClassLoaders;
    }

    /**
     * Retrieve instance of MBeanServer used by container
     *
     * @return instance of MBeanServer
     */
    public MBeanServer getMBeanServer()
    {
        return _mbeanServer;
    }

    @ManagedAttribute(value = "Whether to use the cache for MBeans loaded by other ClassLoaders", readonly = true)
    public boolean isUseCacheForOtherClassLoaders()
    {
        return _useCacheForOtherClassLoaders;
    }

    /**
     * Set domain to be used to add MBeans
     *
     * @param domain domain name
     */
    public void setDomain(String domain)
    {
        _domain = domain;
    }

    /**
     * Retrieve domain name used to add MBeans
     *
     * @return domain name
     */
    @ManagedAttribute("The default ObjectName domain")
    public String getDomain()
    {
        return _domain;
    }

    /**
     * 

Creates an ObjectMBean for the given object.

*

Attempts to create an ObjectMBean for the object by searching the package * and class name space. For example an object of the type:

*
     * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
     * 
*

then this method would look for the following classes:

*
    *
  • com.acme.jmx.MyClassMBean
  • *
  • com.acme.util.jmx.BaseClassMBean
  • *
  • org.eclipse.jetty.jmx.ObjectMBean
  • *
* * @param o The object * @return A new instance of an MBean for the object or null. */ public Object mbeanFor(Object o) { return mbeanFor(this, o); } static Object mbeanFor(MBeanContainer container, Object o) { if (o == null) return null; Object mbean = findMetaData(container, o.getClass()).newInstance(o); if (mbean instanceof ObjectMBean) ((ObjectMBean)mbean).setMBeanContainer(container); if (LOG.isDebugEnabled()) { LOG.debug("MBean for {} is {}", o, mbean); if (mbean instanceof ObjectMBean) { MBeanInfo info = ((ObjectMBean)mbean).getMBeanInfo(); for (Object a : info.getAttributes()) { LOG.debug(" {}", a); } for (Object a : info.getOperations()) { LOG.debug(" {}", a); } } } return mbean; } static MetaData findMetaData(MBeanContainer container, Class klass) { if (klass == null) return null; MetaData metaData = getMetaData(container, klass); if (metaData != null) { if (LOG.isDebugEnabled()) LOG.debug("Found cached {}", metaData); return metaData; } return newMetaData(container, klass); } private static MetaData getMetaData(MBeanContainer container, Class klass) { return container == null ? null : container._metaData.get(klass); } private static MetaData newMetaData(MBeanContainer container, Class klass) { if (klass == null) return null; if (klass == Object.class) return new MetaData(klass, null, null, Collections.emptyList()); List interfaces = Arrays.stream(klass.getInterfaces()) .map(intf -> findMetaData(container, intf)) .collect(Collectors.toList()); MetaData metaData = new MetaData(klass, findConstructor(klass), findMetaData(container, klass.getSuperclass()), interfaces); if (container != null) { if (container.isUseCacheForOtherClassLoaders() || klass.getClassLoader() == container.getClass().getClassLoader()) { MetaData existing = container._metaData.putIfAbsent(klass, metaData); if (existing != null) metaData = existing; if (LOG.isDebugEnabled()) LOG.debug("Cached {}", metaData); } } return metaData; } private static Constructor findConstructor(Class klass) { Package pkg = klass.getPackage(); if (pkg == null) return null; String pName = pkg.getName(); String cName = klass.getName().substring(pName.isEmpty() ? 0 : pName.length() + 1); String mName = pName + ".jmx." + cName + "MBean"; try { Class mbeanClass = Loader.loadClass(klass, mName); Constructor constructor = ModelMBean.class.isAssignableFrom(mbeanClass) ? mbeanClass.getConstructor() : mbeanClass.getConstructor(Object.class); if (LOG.isDebugEnabled()) LOG.debug("Found MBean wrapper: {} for {}", mName, klass.getName()); return constructor; } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("MBean wrapper not found: {} for {}", mName, klass.getName()); return null; } } /** * Lookup an object name by instance * * @param object instance for which object name is looked up * @return object name associated with specified instance, or null if not found */ public ObjectName findMBean(Object object) { return _mbeans.get(object); } /** * Lookup an instance by object name * * @param objectName object name of instance * @return instance associated with specified object name, or null if not found */ public Object findBean(ObjectName objectName) { for (Map.Entry entry : _mbeans.entrySet()) { if (entry.getValue().equals(objectName)) return entry.getKey(); } return null; } @Override public void beanAdded(Container parent, Object obj) { if (LOG.isDebugEnabled()) LOG.debug("beanAdded {}->{}", parent, obj); if (obj == null) return; if (parent == null) parent = ROOT; // Is the bean already tracked ? if (_beans.putIfAbsent(obj, parent) != null) return; // Is there an object name for the parent ? ObjectName parentObjectName = null; if (parent != ROOT) { parentObjectName = findMBean(parent); if (parentObjectName == null) { // Create the parent bean. beanAdded(null, parent); parentObjectName = findMBean(parent); } } try { // Create an MBean for the object. Object mbean = mbeanFor(obj); if (mbean == null) return; ObjectName objectName = null; if (mbean instanceof ObjectMBean) { objectName = ((ObjectMBean)mbean).getObjectName(); } // No override of the mbean's ObjectName, so make a generic one. if (objectName == null) { Class klass = obj.getClass(); while (klass.isArray()) { klass = klass.getComponentType(); } // If no explicit domain, create one. String domain = _domain; if (domain == null) { Package pkg = klass.getPackage(); domain = pkg == null ? "" : pkg.getName(); } String type = klass.getName().toLowerCase(Locale.ENGLISH); int dot = type.lastIndexOf('.'); if (dot >= 0) type = type.substring(dot + 1); StringBuilder buf = new StringBuilder(); String context = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectContextBasis()) : null; if (context == null && parentObjectName != null) context = parentObjectName.getKeyProperty("context"); if (context != null && context.length() > 1) buf.append("context=").append(context).append(","); buf.append("type=").append(type); String name = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectNameBasis()) : context; if (name != null && name.length() > 1) buf.append(",").append("name=").append(name); String basis = buf.toString(); AtomicInteger count = __unique.get(basis); if (count == null) { count = new AtomicInteger(); AtomicInteger existing = __unique.putIfAbsent(basis, count); if (existing != null) count = existing; } objectName = ObjectName.getInstance(domain + ":" + basis + ",id=" + count.getAndIncrement()); } _mbeanServer.registerMBean(mbean, objectName); if (LOG.isDebugEnabled()) LOG.debug("Registered {}", objectName); _mbeans.put(obj, objectName); } catch (Throwable x) { LOG.warn("bean: " + obj, x); } } @Override public void beanRemoved(Container parent, Object obj) { if (LOG.isDebugEnabled()) LOG.debug("beanRemoved {}->{}", parent, obj); if (parent == null) parent = ROOT; if (_beans.remove(obj, parent)) { ObjectName objectName = _mbeans.remove(obj); if (objectName != null) unregister(objectName); } } /** * @param basis name to strip of special characters. * @return normalized name */ public String makeName(String basis) { return StringUtil.sanitizeFileSystemName(basis); } @Override public void dump(Appendable out, String indent) throws IOException { Dumpable.dumpObjects(out, indent, this, _mbeans.entrySet()); } @Override public String dump() { return Dumpable.dump(this); } @Override public void destroy() { _metaData.clear(); _mbeans.values().stream() .filter(Objects::nonNull) .forEach(this::unregister); _mbeans.clear(); _beans.clear(); } private void unregister(ObjectName objectName) { try { getMBeanServer().unregisterMBean(objectName); if (LOG.isDebugEnabled()) LOG.debug("Unregistered {}", objectName); } catch (MBeanRegistrationException | InstanceNotFoundException x) { LOG.ignore(x); } catch (Throwable x) { LOG.warn(x); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy