blob: 2258b0e38d3edc9f11eff513e5f23a28af0381fa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.ipojo.handlers.jmx;
import java.lang.management.ManagementFactory;
import java.util.*;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.apache.felix.ipojo.FieldInterceptor;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
/**
* This class implements iPOJO Handler. it builds the dynamic MBean from
* metadata.xml and exposes it to the MBean Server.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class MBeanHandler extends PrimitiveHandler {
/**
* The name of the MBeanRegistration postDeregister method.
*/
public static final String POST_DEREGISTER_METH_NAME = "postDeregister";
/**
* The name of the MBeanRegistration preDeregister method.
*/
public static final String PRE_DEREGISTER_METH_NAME = "preDeregister";
/**
* The name of the MBeanRegistration postRegister method.
*/
public static final String POST_REGISTER_METH_NAME = "postRegister";
/**
* The name of the MBeanRegistration preRegister method.
*/
public static final String PRE_REGISTER_METH_NAME = "preRegister";
/**
* The name of the global configuration element.
*/
private static final String JMX_CONFIG_ELT = "config";
/**
* The name of the global configuration element.
*/
private static final String JMX_CONFIG_ALT_ELT = "JmxBean";
/**
* The name of the component object full name attribute.
*/
private static final String JMX_OBJ_NAME_ELT = "objectName";
/**
* The name of the component object name domain attribute.
*/
private static final String JMX_OBJ_NAME_DOMAIN_ELT = "domain";
/**
* The name of the component object name attribute.
*/
private static final String JMX_OBJ_NAME_WO_DOMAIN_ELT = "name";
/**
* The name of the attribute indicating if the handler uses MOSGi MBean server.
*/
private static final String JMX_USES_MOSGI_ELT = "usesMOSGi";
/**
* The name of a method element.
*/
private static final String JMX_METHOD_ELT = "method";
/**
* The alternative name of a method element.
*/
private static final String JMX_METHOD_ELT_ALT = "JmxMethod";
/**
* The name of the property or method name attribute.
*/
private static final String JMX_NAME_ELT = "name";
/**
* The name of a method description attribute.
*/
private static final String JMX_DESCRIPTION_ELT = "description";
/**
* The name of a property element.
*/
private static final String JMX_PROPERTY_ELT = "property";
/**
* The alternative name of a property element.
*/
private static final String JMX_PROPERTY_ELT_ALT = "JmxProperty";
/**
* The name of the field attribute.
*/
private static final String JMX_FIELD_ELT = "field";
/**
* The name of the notification attribute.
*/
private static final String JMX_NOTIFICATION_ELT = "notification";
/**
* The name of the rights attribute.
*/
private static final String JMX_RIGHTS_ELT = "rights";
/**
* The instance manager. Used to store the InstanceManager instance.
*/
private InstanceManager m_instanceManager;
/**
* The service registration. Used to register and unregister the Dynamic MBean.
*/
private ServiceRegistration m_serviceRegistration;
/**
* Stores data when parsing metadata.xml.
*/
private JmxConfigFieldMap m_jmxConfigFieldMap;
/**
* Stores the Dynamic MBean.
*/
private DynamicMBeanImpl m_MBean;
/**
* Constant storing the name of the class.
*/
private String m_namespace = "org.apache.felix.ipojo.handlers.jmx";
/**
* The flag used to inform if we use the MOSGi framework.
*/
private boolean m_usesMOSGi;
/**
* The ObjectName used to register the MBean.
*/
private ObjectName m_objectName;
/**
* The flag used to inform if the MBean is registered.
*/
private boolean m_registered;
/**
* The ObjectName specified in handler configuration. It can be null.
*/
private String m_completeObjNameElt;
/**
* The ObjectName without domain specified in handler configuration. It can be null.
*/
private String m_objNameWODomainElt;
/**
* The ObjectName domain specified in handler configuration. It can be null.
*/
private String m_domainElt;
/**
* The flag informing if the POJO implements the MBeanRegistration interface.
*/
private boolean m_registerCallbacks;
/**
* The preRegister method of MBeanRegistration interface. It is null if POJO doesn't implement MBeanRegistration interface.
*/
private MethodMetadata m_preRegisterMeth;
/**
* The postRegister method of MBeanRegistration interface. It is null if POJO doesn't implement MBeanRegistration interface.
*/
private MethodMetadata m_postRegisterMeth;
/**
* The preDeregister method of MBeanRegistration interface. It is null if POJO doesn't implement MBeanRegistration interface.
*/
private MethodMetadata m_preDeregisterMeth;
/**
* The postDeregister method of MBeanRegistration interface. It is null if POJO doesn't implement MBeanRegistration interface.
*/
private MethodMetadata m_postDeregisterMeth;
/**
* Constructs the structure JmxConfigFieldMap and the Dynamic Mbean.
*
* @param metadata the component metadata
* @param dict the instance configuration
*/
public void configure(Element metadata, Dictionary dict) {
PojoMetadata manipulation = getPojoMetadata();
m_instanceManager = getInstanceManager();
m_jmxConfigFieldMap = new JmxConfigFieldMap();
// Build the hashmap
Element[] mbeans = metadata.getElements(JMX_CONFIG_ELT, m_namespace);
if (mbeans == null || mbeans.length == 0) {
mbeans = metadata.getElements(JMX_CONFIG_ALT_ELT, m_namespace);
}
if (mbeans.length != 1) {
error("A component must have exactly one " + JMX_CONFIG_ELT + " or " + JMX_CONFIG_ALT_ELT + " element.");
error("The JMX handler configuration is ignored.");
return;
}
Element mbean = mbeans[0];
// retrieve kind of MBeanServer to use
m_usesMOSGi = Boolean.parseBoolean(mbean.getAttribute(JMX_USES_MOSGI_ELT));
// retrieve object name
m_completeObjNameElt = mbean.getAttribute(JMX_OBJ_NAME_ELT);
m_domainElt = mbean.getAttribute(JMX_OBJ_NAME_DOMAIN_ELT);
m_objNameWODomainElt = mbean.getAttribute(JMX_OBJ_NAME_WO_DOMAIN_ELT);
// test if Pojo is interested in registration callbacks
m_registerCallbacks = manipulation
.isInterfaceImplemented(MBeanRegistration.class.getName());
if (m_registerCallbacks) {
// don't need to check that methods exist, the pojo implements
// MBeanRegistration interface
String[] preRegisterParams = { MBeanServer.class.getName(),
ObjectName.class.getName() };
m_preRegisterMeth = manipulation.getMethod(PRE_REGISTER_METH_NAME,
preRegisterParams);
String[] postRegisterParams = { Boolean.class.getName() };
m_postRegisterMeth = manipulation.getMethod(
POST_REGISTER_METH_NAME, postRegisterParams);
m_preDeregisterMeth = manipulation.getMethod(
PRE_DEREGISTER_METH_NAME, new String[0]);
m_postDeregisterMeth = manipulation.getMethod(
POST_DEREGISTER_METH_NAME, new String[0]);
}
// set property
Element[] attributes = mbean.getElements(JMX_PROPERTY_ELT, m_namespace);
Element[] attributesAlt = mbean.getElements(JMX_PROPERTY_ELT_ALT, m_namespace);
List<Element> listOfAttributes = new ArrayList<Element>();
if (attributes != null) {
listOfAttributes.addAll(Arrays.asList(attributes));
}
if (attributesAlt != null) {
listOfAttributes.addAll(Arrays.asList(attributesAlt));
}
Element[] attributesOld = mbeans[0].getElements(JMX_PROPERTY_ELT);
if (attributesOld != null) {
warn("The JMX property element should use the '" + m_namespace + "' namespace.");
listOfAttributes.addAll(Arrays.asList(attributesOld));
}
for (Element attribute : listOfAttributes) {
boolean notif = false;
String rights;
String name;
String field = attribute.getAttribute(JMX_FIELD_ELT);
if (attribute.containsAttribute(JMX_NAME_ELT)) {
name = attribute.getAttribute(JMX_NAME_ELT);
} else {
name = field;
}
if (attribute.containsAttribute(JMX_RIGHTS_ELT)) {
rights = attribute.getAttribute(JMX_RIGHTS_ELT);
} else {
rights = "r";
}
PropertyField property = new PropertyField(name, field, rights,
getTypeFromAttributeField(field, manipulation));
if (attribute.containsAttribute(JMX_NOTIFICATION_ELT)) {
notif = Boolean.parseBoolean(attribute
.getAttribute(JMX_NOTIFICATION_ELT));
}
property.setNotifiable(notif);
if (notif) {
// add the new notifiable property in structure
NotificationField notification = new NotificationField(
name, this.getClass().getName() + "." + field, null);
m_jmxConfigFieldMap.addNotificationFromName(name,
notification);
}
m_jmxConfigFieldMap.addPropertyFromName(name, property);
getInstanceManager().register(manipulation.getField(field),
this);
info("property exposed:" + name + " " + field + ":"
+ getTypeFromAttributeField(field, manipulation) + " "
+ rights + ", Notif=" + notif);
}
// set methods
Element[] methods = mbean.getElements(JMX_METHOD_ELT, m_namespace);
Element[] methodsAlt = mbean.getElements(JMX_METHOD_ELT_ALT, m_namespace);
List<Element> listOfMethods = new ArrayList<Element>();
if (methods != null) {
listOfMethods.addAll(Arrays.asList(methods));
}
if (methodsAlt != null) {
listOfMethods.addAll(Arrays.asList(methodsAlt));
}
Element[] methodsOld = mbeans[0].getElements(JMX_PROPERTY_ELT);
if (methodsOld != null) {
warn("The JMX method element should use the '" + m_namespace + "' namespace.");
listOfMethods.addAll(Arrays.asList(methodsOld));
}
for (Element method : listOfMethods) {
String name = method.getAttribute(JMX_NAME_ELT);
if (name == null) {
name = method.getAttribute("method");
}
String description = null;
if (method.containsAttribute(JMX_DESCRIPTION_ELT)) {
description = method.getAttribute(JMX_DESCRIPTION_ELT);
}
MethodField[] meth = getMethodsFromName(name, manipulation,
description);
for (int j = 0; j < meth.length; j++) {
m_jmxConfigFieldMap.addMethodFromName(name, meth[j]);
info("method exposed:" + meth[j].getReturnType() + " " + name);
}
}
}
/**
* Registers the Dynamic Mbean.
*/
public void start() {
// create the corresponding MBean
if (m_registerCallbacks) {
m_MBean = new DynamicMBeanWRegisterImpl(m_jmxConfigFieldMap,
m_instanceManager, m_preRegisterMeth, m_postRegisterMeth,
m_preDeregisterMeth, m_postDeregisterMeth);
} else {
m_MBean = new DynamicMBeanImpl(m_jmxConfigFieldMap,
m_instanceManager);
}
if (m_usesMOSGi) {
// use whiteboard pattern to register MBean
if (m_serviceRegistration != null) {
m_serviceRegistration.unregister();
}
// Register the ManagedService
BundleContext bundleContext = m_instanceManager.getContext();
Dictionary<String, String> properties = new Hashtable<String, String>();
try {
m_objectName = new ObjectName(getObjectNameString());
properties.put("jmxagent.objectName", m_objectName.toString());
m_serviceRegistration = bundleContext.registerService(
javax.management.DynamicMBean.class.getName(), m_MBean,
properties);
m_registered = true;
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
m_objectName = new ObjectName(getObjectNameString());
ObjectInstance instance = ManagementFactory
.getPlatformMBeanServer().registerMBean(m_MBean,
m_objectName);
// we must retrieve object name used to register the MBean.
// It can have been changed by preRegister method of
// MBeanRegistration interface.
if (m_registerCallbacks) {
m_objectName = instance.getObjectName();
}
m_registered = true;
} catch (Exception e) {
error("Registration of MBean failed.", e);
}
}
}
/**
* Returns the object name of the exposed component.
*
* @return the object name of the exposed component.
*/
private String getObjectNameString() {
if (m_completeObjNameElt != null) {
return m_completeObjNameElt;
}
String domain;
if (m_domainElt != null) {
domain = m_domainElt;
} else {
domain = getPackageName(m_instanceManager.getClassName());
}
String name = "type=" + m_instanceManager.getClassName() + ",instance="
+ m_instanceManager.getInstanceName();
if (m_objNameWODomainElt != null) {
name = "name=" + m_objNameWODomainElt;
}
StringBuffer sb = new StringBuffer();
if ((domain != null) && (domain.length() > 0)) {
sb.append(domain + ":");
}
sb.append(name);
info("Computed Objectname: " + sb.toString());
return sb.toString();
}
/**
* Extracts the package name from of given type.
*
* @param className the type name.
* @return the package name of the given type.
*/
private String getPackageName(String className) {
String packageName = "";
int plotIdx = className.lastIndexOf(".");
if (plotIdx != -1) {
packageName = className.substring(0, plotIdx);
}
return packageName;
}
/**
* Unregisters the Dynamic Mbean.
*/
public void stop() {
if (m_usesMOSGi) {
if (m_serviceRegistration != null) {
m_serviceRegistration.unregister();
}
} else {
if (m_objectName != null) {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(
m_objectName);
} catch (Exception e) {
error("Unregistration of MBean failed.", e);
}
m_objectName = null;
}
}
m_MBean = null;
m_registered = false;
}
/**
* Called when a POJO member is modified externally.
*
* @param pojo the modified POJO object
* @param fieldName the name of the modified field
* @param value the new value of the field
* @see FieldInterceptor#onSet(Object, String, Object)
*/
public void onSet(Object pojo, String fieldName, Object value) {
// Check if the field is a configurable property
PropertyField propertyField = (PropertyField) m_jmxConfigFieldMap
.getPropertyFromField(fieldName);
if (propertyField != null) {
if (propertyField.isNotifiable()) {
// TODO should send notif only when value has changed to a value
// different than the last one.
m_MBean.sendNotification(propertyField.getName() + " changed",
propertyField.getName(), propertyField.getType(),
propertyField.getValue(), value);
}
propertyField.setValue(value);
}
}
/**
* Called when a POJO member is read by the MBean.
*
* @param pojo the read POJO object.
* @param fieldName the name of the modified field
* @param value the old value of the field
* @return the (injected) value of the field
* @see FieldInterceptor#onGet(Object, String, Object)
*/
public Object onGet(Object pojo, String fieldName, Object value) {
// Check if the field is a configurable property
PropertyField propertyField = (PropertyField) m_jmxConfigFieldMap
.getPropertyFromField(fieldName);
if (propertyField != null) {
// Do we have a value to inject ?
Object v = propertyField.getValue();
if (v == null) {
String type = propertyField.getType();
if ("boolean".equals(type)) { v = Boolean.FALSE; }
else if ("byte".equals(type)) { v = new Byte((byte) 0); }
else if ("short".equals(type)) { v = new Short((short) 0); }
else if ("int".equals(type)) { v = new Integer(0); }
else if ("long".equals(type)) { v = new Long(0); }
else if ("float".equals(type)) { v = new Float(0); }
else if ("double".equals(type)) { v =new Double(0); }
else if ("char".equals(type)) { v = new Character((char) 0); }
return v;
}
m_instanceManager.onSet(pojo, fieldName, propertyField.getValue());
return propertyField.getValue();
}
return value;
}
/**
* Gets the type from a field name.
*
* @param fieldRequire the name of the required field
* @param manipulation the metadata extracted from metadata.xml file
* @return the type of the field or {@code null} if it wasn't found
*/
private static String getTypeFromAttributeField(String fieldRequire,
PojoMetadata manipulation) {
FieldMetadata field = manipulation.getField(fieldRequire);
if (field == null) {
return null;
} else {
return FieldMetadata.getReflectionType(field.getFieldType());
}
}
/**
* Gets all the methods available which get this name.
*
* @param methodName the name of the required methods
* @param manipulation the metadata extract from metadata.xml file
* @param description the description which appears in JMX console
* @return the array of methods with the right name
*/
private MethodField[] getMethodsFromName(String methodName,
PojoMetadata manipulation, String description) {
MethodMetadata[] methods = manipulation.getMethods(methodName);
if (methods.length == 0) {
return null;
}
MethodField[] ret = new MethodField[methods.length];
if (methods.length == 1) {
ret[0] = new MethodField(methods[0], description);
return ret;
} else {
for (int i = 0; i < methods.length; i++) {
ret[i] = new MethodField(methods[i], description);
}
return ret;
}
}
/**
* Gets the JMX handler description.
*
* @return the JMX handler description.
* @see org.apache.felix.ipojo.Handler#getDescription()
*/
public HandlerDescription getDescription() {
return new JMXHandlerDescription(this);
}
/**
* Returns the objectName used to register the MBean. If the MBean is not registered, return an empty string.
*
* @return the objectName used to register the MBean.
* @see org.apache.felix.ipojo.Handler#getDescription()
*/
public String getUsedObjectName() {
if (m_objectName != null) {
return m_objectName.toString();
} else {
return "";
}
}
/**
* Returns true if the MBean is registered.
*
* @return true if the MBean is registered.
*/
public boolean isRegistered() {
return m_registered;
}
/**
* Returns true if the MBean must be registered thanks to white board pattern of MOSGi.
*
* @return {@code true} if the MBean must be registered thanks to white board pattern of MOSGi, false otherwise.
*/
public boolean isUsesMOSGi() {
return m_usesMOSGi;
}
/**
* Returns true if the MOSGi framework is present on the OSGi platform.
*
* @return {@code true} if the MOSGi framework is present on the OSGi platform, false otherwise.
*/
public boolean isMOSGiExists() {
for (Bundle bundle : m_instanceManager.getContext().getBundles()) {
String symbolicName = bundle.getSymbolicName();
if ("org.apache.felix.mosgi.jmx.agent".equals(symbolicName)) {
return true;
}
}
return false;
}
}