package org.jolokia.backend.executor; /* * Copyright 2009-2013 Roland Huss * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.util.*; import javax.management.*; import org.jolokia.util.JmxUtil; /** * Base class for providing access to the list of MBeanServer handled by this agent. * * @author roland * @since 22.01.13 */ public abstract class AbstractMBeanServerExecutor implements MBeanServerExecutor, NotificationListener { // Timestamp of last MBeanServer change in milliseconds private long lastMBeanRegistrationChange; /** * Get all MBeanServers * * @return all MBeanServers in the merge order */ protected abstract Set<MBeanServerConnection> getMBeanServers(); /** * Override this method if you want to provide a Jolokia private MBeanServer. Note, that * this method should only return a non-null value, if the Jolokia private MBean Server has * some MBeans registered * * @return the Jolokia MBeanServer */ protected MBeanServerConnection getJolokiaMBeanServer() { return null; } /** {@inheritDoc} */ public void each(ObjectName pObjectName, MBeanEachCallback pCallback) throws IOException, ReflectionException, MBeanException { try { Set<ObjectName> visited = new HashSet<ObjectName>(); for (MBeanServerConnection server : getMBeanServers()) { // Query for a full name is the same as a direct lookup for (ObjectName nameObject : server.queryNames(pObjectName, null)) { // Don't add if already visited previously if (!visited.contains(nameObject)) { pCallback.callback(server, nameObject); visited.add(nameObject); } } } } catch (InstanceNotFoundException exp) { // Well, should not happen, since we do a query before and the returned value are supposed to exist // on the mbean-server. But, who knows ... throw new IllegalArgumentException("Cannot find MBean " + (pObjectName != null ? "(MBean " + pObjectName + ")" : "") + ": " + exp,exp); } } /** {@inheritDoc} */ public <T> T call(ObjectName pObjectName, MBeanAction<T> pMBeanAction, Object ... pExtraArgs) throws IOException, ReflectionException, MBeanException, AttributeNotFoundException, InstanceNotFoundException { InstanceNotFoundException objNotFoundException = null; for (MBeanServerConnection server : getMBeanServers()) { // Only the first MBeanServer holding the MBean wins try { // Still to decide: Should we check eagerly or let an InstanceNotFound Exception // bubble ? Exception bubbling was the former behaviour, so it is left in. However, // it would be interesting how large the performance impact is here. All unit tests BTW are // prepared for switching the guard below on or off. //if (server.isRegistered(pObjectName)) { return pMBeanAction.execute(server, pObjectName, pExtraArgs); //} } catch (InstanceNotFoundException exp) { // Remember exceptions for later use objNotFoundException = exp; } } // Must be != null, otherwise we would not have left the loop throw objNotFoundException; // When we reach this, no MBeanServer know about the requested MBean. // Hence, we throw our own InstanceNotFoundException here //throw exception != null ? // new IllegalArgumentException(errorMsg + ": " + exception,exception) : // new IllegalArgumentException(errorMsg); } /** {@inheritDoc} */ public Set<ObjectName> queryNames(ObjectName pObjectName) throws IOException { Set<ObjectName> names = new LinkedHashSet<ObjectName>(); for (MBeanServerConnection server : getMBeanServers()) { names.addAll(server.queryNames(pObjectName,null)); } return names; } /** * Add this executor as listener for MBeanServer notification so that we can update * the local timestamp for when the set of registered MBeans has changed last. */ protected void registerForMBeanNotifications() { Set<MBeanServerConnection> servers = getMBeanServers(); Exception lastExp = null; StringBuilder errors = new StringBuilder(); for (MBeanServerConnection server : servers) { try { JmxUtil.addMBeanRegistrationListener(server,this,null); } catch (IllegalStateException e) { lastExp = updateErrorMsg(errors,e); } } if (lastExp != null) { throw new IllegalStateException(errors.substring(0,errors.length()-1),lastExp); } } /** * Unregister us as listener from every registered server */ public void unregisterFromMBeanNotifications() { Set<MBeanServerConnection> servers = getMBeanServers(); Exception lastExp = null; StringBuilder errors = new StringBuilder(); for (MBeanServerConnection server : servers) { try { JmxUtil.removeMBeanRegistrationListener(server,this); } catch (IllegalStateException e) { lastExp = updateErrorMsg(errors, e); } } if (lastExp != null) { throw new IllegalStateException(errors.substring(0,errors.length()-1),lastExp); } } /** {@inheritDoc} */ // Remember current timestamp public void handleNotification(Notification pNotification, Object pHandback) { // Update timestamp lastMBeanRegistrationChange = System.currentTimeMillis(); } /** {@inheritDoc} */ public boolean hasMBeansListChangedSince(long pTimestamp) { return (lastMBeanRegistrationChange / 1000) >= pTimestamp; } // Helper method for adding the exception for an appropriate error message private Exception updateErrorMsg(StringBuilder pErrors, Exception exp) { pErrors.append(exp.getClass()).append(": ").append(exp.getMessage()).append("\n"); return exp; } }