/*
* Copyright 2008 the original author or authors.
*
* 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.
*/
package groovy.jmx.builder;
import groovy.lang.Closure;
import javax.management.*;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.RequiredModelMBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
* The JmxBuilderModelMBean is the MBean class that proxies exported POGO/POJO inside the MBeanServer.
* When JmxBuilder exports an object instance, an instance of this class is created and exported inside the
* MBeanServer.
*
* @author Vladimir Vivien
*/
public class JmxBuilderModelMBean extends RequiredModelMBean implements NotificationListener {
private List<String> methodListeners = new ArrayList<String>(0);
private Object managedObject;
public JmxBuilderModelMBean(Object objectRef) throws MBeanException, RuntimeOperationsException, InstanceNotFoundException, InvalidTargetObjectTypeException {
super.setManagedResource(objectRef, "ObjectReference");
}
public JmxBuilderModelMBean() throws MBeanException, RuntimeOperationsException {
super();
}
public JmxBuilderModelMBean(ModelMBeanInfo mbi) throws MBeanException, RuntimeOperationsException {
super(mbi);
}
public synchronized void setManagedResource(Object obj) {
managedObject = obj;
try {
super.setManagedResource(obj, "ObjectReference");
} catch (Exception ex) {
throw new JmxBuilderException(ex);
}
}
/**
* Registers listeners for operation calls (i.e. method, getter, and setter calls) when
* invoked on this bean from the MBeanServer. Descriptor should contain a map with layout
* item -> [Map[methodListener:[target:"", tpe:"", callback:&Closure], ... ,]]
*
* @param descriptor MetaMap descriptor containing description of operation call listeners
*/
public void addOperationCallListeners(Map<String, Map> descriptor) {
if (descriptor == null) return;
for (Map.Entry<String, Map> item : descriptor.entrySet()) {
// set up method listeners (such as attributeListener and Operation Listeners)
// item -> [Map[methodListener:[target:"", tpe:"", callback:&Closure], ... ,]]
if (item.getValue().containsKey("methodListener")) {
Map listener = (Map) item.getValue().get("methodListener");
String target = (String) listener.get("target");
methodListeners.add(target);
String listenerType = (String) listener.get("type");
listener.put("managedObject", this.managedObject);
// register an attribute change notification listener with model mbean
if (listenerType.equals("attributeChangeListener")) {
try {
this.addAttributeChangeNotificationListener(
AttributeChangedListener.getListener(), (String) listener.get("attribute"), listener
);
} catch (MBeanException e) {
throw new JmxBuilderException(e);
}
}
if (listenerType.equals("operationCallListener")) {
String eventType = "jmx.operation.call." + target;
NotificationFilterSupport filter = new NotificationFilterSupport();
filter.enableType(eventType);
this.addNotificationListener(JmxEventListener.getListener(), filter, listener);
}
}
}
}
/**
* Sets up event listeners for this MBean as described in the descriptor.
* The descriptor contains a map with layout {item -> Map[event:"...", from:ObjectName, callback:&Closure],...,}
*
* @param server the MBeanServer is to be registered.
* @param descriptor a map containing info about the event
*/
public void addEventListeners(MBeanServer server, Map<String, Map> descriptor) {
for (Map.Entry<String, Map> item : descriptor.entrySet()) {
Map<String, Object> listener = item.getValue();
// register with server
ObjectName broadcaster = (ObjectName) listener.get("from");
try {
String eventType = (String) listener.get("event");
if (eventType != null) {
NotificationFilterSupport filter = new NotificationFilterSupport();
filter.enableType(eventType);
server.addNotificationListener(broadcaster, JmxEventListener.getListener(), filter, listener);
} else {
server.addNotificationListener(broadcaster, JmxEventListener.getListener(), null, listener);
}
} catch (InstanceNotFoundException e) {
throw new JmxBuilderException(e);
}
}
}
@Override
public Object invoke(String opName, Object[] opArgs, String[] signature) throws MBeanException, ReflectionException {
Object result = super.invoke(opName, opArgs, signature);
if (methodListeners.contains(opName)) {
this.sendNotification(buildCallListenerNotification(opName));
}
return result;
}
public void handleNotification(Notification note, Object handback) {
//System.out.println("Received note!");
}
private Notification buildCallListenerNotification(String target) {
return new Notification(
"jmx.operation.call." + target,
this,
NumberSequencer.getNextSequence(),
System.currentTimeMillis()
);
}
private static class NumberSequencer {
private static AtomicLong num;
static {
num = new AtomicLong(0);
}
public static long getNextSequence() {
return num.incrementAndGet();
}
}
/**
* Internal class AttributeChangedListener provides hooks to handle attribute-change events
* that occurs on registered MBeans.
*
* @author Vladimir Vivien
* @see groovy.jmx.builder.JmxBuilderModelMBean
*/
private static final class AttributeChangedListener implements NotificationListener {
private static AttributeChangedListener listener;
/**
* Returns an instance of the AttributeChangedListener.
*
* @return
*/
public static synchronized AttributeChangedListener getListener() {
if (listener == null) {
listener = new AttributeChangedListener();
}
return listener;
}
private AttributeChangedListener() {
}
public void handleNotification(Notification notification, Object handback) {
AttributeChangeNotification note = (AttributeChangeNotification) notification;
Map event = (Map) handback;
if (event != null) {
Object del = event.get("managedObject");
Object callback = event.get("callback");
if (callback != null && callback instanceof Closure) {
Closure closure = (Closure) callback;
closure.setDelegate(del);
if (closure.getMaximumNumberOfParameters() == 1)
closure.call(buildAttributeNotificationPacket(note));
else closure.call();
}
}
}
private static Map buildAttributeNotificationPacket(AttributeChangeNotification note) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("oldValue", note.getOldValue());
result.put("newValue", note.getNewValue());
result.put("attribute", note.getAttributeName());
result.put("attributeType", note.getAttributeType());
result.put("sequenceNumber", note.getSequenceNumber());
result.put("timeStamp", note.getTimeStamp());
return result;
}
}
}