/* * Bitronix Transaction Manager * * Copyright (c) 2010, Bitronix Software. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package bitronix.tm.utils; import bitronix.tm.Configuration; import bitronix.tm.TransactionManagerServices; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.*; import java.lang.management.ManagementFactory; import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.ArrayBlockingQueue; /** * JMX facade used to (un)register any JMX enabled instances. * <p/> * In case there is no JMX implementation available, calling methods of this class have no effect. * JMX registrations may be synchronous or asynchronous using a work-queue and worker thread. * The latter enables higher throughput by avoiding the registration of very short lived instances and * by that fact the JMX registrations can work on uncontended thread synchronization. * * @author lorban, jkellerer */ public final class ManagementRegistrar { private final static Logger log = LoggerFactory.getLogger(ManagementRegistrar.class); private final static MBeanServer mbeanServer; static { boolean enableJmx = !TransactionManagerServices.getConfiguration().isDisableJmx(); if (enableJmx) { mbeanServer = ManagementFactory.getPlatformMBeanServer(); } else { mbeanServer = null; } } private final static Queue<ManagementCommand> commandQueue; static { Configuration configuration = TransactionManagerServices.getConfiguration(); commandQueue = mbeanServer == null || configuration.isSynchronousJmxRegistration() ? null : new ArrayBlockingQueue<ManagementCommand>(1024); // TODO: BEGIN - This part should use a system wide shared ScheduledExecutorService instead. // TODO: To be changed when the Executors system was refactored. if (commandQueue != null) { new Thread() { { setName("bitronix-async-jmx-worker"); setDaemon(true); } @Override public void run() { while (!isInterrupted()) { try { normalizeAndRunQueuedCommands(); sleep(250); // sampling interval } catch (InterruptedException t) { return; } catch (Throwable t) { log.error("An unexpected fatal error occurred in JMX asynchronous registration code.", t); } } } }.start(); } // TODO: END } static { if (mbeanServer != null) { if (log.isDebugEnabled()) { log.debug("Enabled JMX with MBeanServer " + mbeanServer + "; MBean registration is '" + (commandQueue == null ? "synchronous" : "asynchronous") + "'."); } } else { if (log.isDebugEnabled()) { log.debug("JMX support is disabled."); } } } private ManagementRegistrar() { } /** * Replace characters considered illegal in a management object's name. * * @param name the name to work on. * @return a fully valid name where all invalid characters have been replaced with '_'. */ public static String makeValidName(String name) { return name.replaceAll("[\\:\\,\\=,\\.]", "_"); } /** * Register the specified management object. * * @param name the name of the object. * @param obj the management object. */ public static void register(String name, Object obj) { if (mbeanServer == null) return; runOrEnqueueCommand(new ManagementRegisterCommand(name, obj)); } /** * Unregister the management object with the specified name. * * @param name the name of the object. */ public static void unregister(String name) { if (mbeanServer == null) return; runOrEnqueueCommand(new ManagementUnregisterCommand(name)); } private static void runOrEnqueueCommand(ManagementCommand command) { if (commandQueue == null) command.run(); else { // Try to enqueue the command unless the queue is full. // Recover from a full queue by running already queued commands first to protect the async. implementation // from being vulnerable to DOS attacks. while (!commandQueue.offer(command)) normalizeAndRunQueuedCommands(); } } static void normalizeAndRunQueuedCommands() { if (commandQueue == null) return; // Synchronizing on commandQueue to ensure that even if 2 threads try to poll, only one can process the commands // that were scheduled at a time (happens if queue is full or during unit tests). // The latter is important to ensure that the command calling order is kept intact as parallel polling would destroy it. synchronized (commandQueue) { final Map<String, ManagementCommand> mappedCommands = new LinkedHashMap<String, ManagementCommand>(commandQueue.size()); ManagementCommand command; while ((command = commandQueue.poll()) != null) { String name = command.getName(); ManagementCommand previousCommand = mappedCommands.put(name, command); if (previousCommand instanceof ManagementRegisterCommand) { // Avoid that we have unbound un-register commands in the work queue. if (command instanceof ManagementUnregisterCommand && !((ManagementRegisterCommand) previousCommand).isReplace()) mappedCommands.remove(name); } else if (previousCommand instanceof ManagementUnregisterCommand) { // We already have this MBean, flagging it for replacement. if (command instanceof ManagementRegisterCommand) ((ManagementRegisterCommand) command).setReplace(true); } } for (ManagementCommand c : mappedCommands.values()) c.run(); } } /** * Registers the given instance within the JMX environment. */ private static class ManagementRegisterCommand extends ManagementCommand { private final WeakReference<Object> instance; private boolean replace; ManagementRegisterCommand(String name, Object instance) { super(name); // Using a WeakReference to avoid holding hard refs on instances that may already be obsolete. this.instance = new WeakReference<Object>(instance); } boolean isReplace() { return replace; } void setReplace(boolean replace) { this.replace = replace; } @Override protected void runCommand() throws Exception { final Object object = instance.get(); if (object != null) { ObjectName objectName = new ObjectName(name); if (replace && mbeanServer.isRegistered(objectName)) mbeanServer.unregisterMBean(objectName); mbeanServer.registerMBean(object, objectName); } } } /** * Unregisters the given instance within the JMX environment. */ private static class ManagementUnregisterCommand extends ManagementCommand { ManagementUnregisterCommand(String name) { super(name); } @Override protected void runCommand() throws Exception { try { mbeanServer.unregisterMBean(new ObjectName(name)); } catch (InstanceNotFoundException e) { if (log.isDebugEnabled()) { log.debug("Failed to unregister the JMX instance of name '" + name + "' as it doesn't exist."); } } } } /** * Base class for management related commands. */ private static abstract class ManagementCommand implements Runnable { final String name; protected ManagementCommand(String name) { this.name = name; } public String getName() { return name; } public final void run() { try { if (log.isDebugEnabled()) { log.debug("Calling " + getClass().getSimpleName() + " on object with name " + name); } runCommand(); } catch (Exception ex) { log.warn("Cannot execute " + getClass().getSimpleName() + " on object with name " + name, ex); } } protected abstract void runCommand() throws Exception; @Override public String toString() { return getClass().getSimpleName() + "{" + "name='" + name + '\'' + '}'; } } }