blob: 1ff136552c0cb58c047c70701278c7b0b357072d [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.deltaspike.core.impl.jmx;
import org.apache.deltaspike.core.api.config.ConfigResolver;
import org.apache.deltaspike.core.api.jmx.JmxBroadcaster;
import org.apache.deltaspike.core.api.jmx.JmxManaged;
import org.apache.deltaspike.core.api.jmx.MBean;
import org.apache.deltaspike.core.api.jmx.NotificationInfo;
import org.apache.deltaspike.core.api.provider.BeanManagerProvider;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.util.ExceptionUtils;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.ImmutableDescriptor;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ReflectionException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is the MBean implementation of a CDI bean.
* It basically delegates to a CDI instance.
*/
public class DynamicMBeanWrapper extends NotificationBroadcasterSupport implements DynamicMBean, JmxBroadcaster
{
public static final Logger LOGGER = Logger.getLogger(DynamicMBeanWrapper.class.getName());
private final MBeanInfo info;
private final Map<String, AttributeAccessor> fields = new HashMap<String, AttributeAccessor>();
private final Map<String, Method> operations = new HashMap<String, Method>();
private final ClassLoader classloader;
private final Class<?> clazz;
private final boolean normalScope;
private final Annotation[] qualifiers;
private Object instance = null;
/**
* The constructor is the builder for the MBean. All the MBean parsing logic is done here.
*
* @param annotatedMBean the class of the CDI managed bean
* @param normalScope is the CDI bean @Dependent or not
* @param qualifiers qualfiers of the CDI bean (used to retrieve it)
*/
public DynamicMBeanWrapper(final Class<?> annotatedMBean,
final boolean normalScope,
final Annotation[] qualifiers)
{
this.clazz = annotatedMBean;
this.classloader = Thread.currentThread().getContextClassLoader();
this.normalScope = normalScope;
this.qualifiers = qualifiers;
final List<MBeanAttributeInfo> attributeInfos = new ArrayList<MBeanAttributeInfo>();
final List<MBeanOperationInfo> operationInfos = new ArrayList<MBeanOperationInfo>();
final List<MBeanNotificationInfo> notificationInfos = new ArrayList<MBeanNotificationInfo>();
// class
final String description =
getDescription(annotatedMBean.getAnnotation(MBean.class).description(), annotatedMBean.getName());
final NotificationInfo notification = annotatedMBean.getAnnotation(NotificationInfo.class);
if (notification != null)
{
notificationInfos.add(getNotificationInfo(notification, annotatedMBean.getName()));
}
final NotificationInfo.List notifications = annotatedMBean.getAnnotation(NotificationInfo.List.class);
if (notifications != null)
{
for (NotificationInfo notificationInfo : notifications.value())
{
notificationInfos.add(getNotificationInfo(notificationInfo, annotatedMBean.getName()));
}
}
// methods
for (Method method : annotatedMBean.getMethods())
{
final int modifiers = method.getModifiers();
final JmxManaged annotation = method.getAnnotation(JmxManaged.class);
if (method.getDeclaringClass().equals(Object.class)
|| !Modifier.isPublic(modifiers)
|| Modifier.isAbstract(modifiers)
|| Modifier.isStatic(modifiers)
|| annotation == null)
{
continue;
}
operations.put(method.getName(), method);
String operationDescr = getDescription(annotation.description(), method.getName());
operationInfos.add(new MBeanOperationInfo(operationDescr, method));
}
Class<?> clazz = annotatedMBean;
while (!Object.class.equals(clazz) && clazz != null)
{
for (Field field : clazz.getDeclaredFields())
{
final JmxManaged annotation = field.getAnnotation(JmxManaged.class);
if (annotation != null)
{
field.setAccessible(true);
final String fieldName = field.getName();
final String fieldDescription = getDescription(annotation.description(), fieldName);
final Class<?> type = field.getType();
final String methodName;
if (fieldName.length() > 1)
{
methodName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
}
else
{
methodName = Character.toString(Character.toUpperCase(fieldName.charAt(0)));
}
Method setter = null;
Method getter = null;
try
{
getter = clazz.getMethod("get" + methodName);
}
catch (NoSuchMethodException e1)
{
try
{
getter = clazz.getMethod("is" + methodName);
}
catch (NoSuchMethodException e2)
{
// ignored
}
}
try
{
setter = clazz.getMethod("set" + methodName, field.getType());
}
catch (NoSuchMethodException e)
{
// ignored
}
attributeInfos.add(new MBeanAttributeInfo(
fieldName, type.getName(), fieldDescription, getter != null, setter != null, false));
fields.put(fieldName, new AttributeAccessor(getter, setter));
}
}
clazz = clazz.getSuperclass();
}
info = new MBeanInfo(annotatedMBean.getName(),
description,
attributeInfos.toArray(new MBeanAttributeInfo[attributeInfos.size()]),
null, // default constructor is mandatory
operationInfos.toArray(new MBeanOperationInfo[operationInfos.size()]),
notificationInfos.toArray(new MBeanNotificationInfo[notificationInfos.size()]));
}
private MBeanNotificationInfo getNotificationInfo(final NotificationInfo notificationInfo, String sourceInfo)
{
return new MBeanNotificationInfo(
notificationInfo.types(),
notificationInfo.notificationClass().getName(),
getDescription(notificationInfo.description(), sourceInfo),
new ImmutableDescriptor(notificationInfo.descriptorFields()));
}
private String getDescription(final String description, String defaultDescription)
{
if (description.isEmpty())
{
return defaultDescription;
}
String descriptionValue = description.trim();
if (descriptionValue.startsWith("{") && descriptionValue.endsWith("}"))
{
return ConfigResolver.getPropertyValue(
descriptionValue.substring(1, descriptionValue.length() - 1), defaultDescription);
}
return description;
}
@Override
public MBeanInfo getMBeanInfo()
{
return info;
}
@Override
public Object getAttribute(final String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException
{
if (fields.containsKey(attribute))
{
final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classloader);
try
{
return fields.get(attribute).get(instance());
}
catch (IllegalArgumentException e)
{
LOGGER.log(Level.SEVERE, "can't get " + attribute + " value", e);
}
catch (IllegalAccessException e)
{
LOGGER.log(Level.SEVERE, "can't get " + attribute + " value", e);
}
catch (InvocationTargetException e)
{
LOGGER.log(Level.SEVERE, "can't get " + attribute + " value", e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
throw new AttributeNotFoundException();
}
@Override
public void setAttribute(final Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
{
if (fields.containsKey(attribute.getName()))
{
final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classloader);
try
{
fields.get(attribute.getName()).set(instance(), attribute.getValue());
}
catch (IllegalArgumentException e)
{
LOGGER.log(Level.SEVERE, "can't set " + attribute + " value", e);
}
catch (IllegalAccessException e)
{
LOGGER.log(Level.SEVERE, "can't set " + attribute + " value", e);
}
catch (InvocationTargetException e)
{
LOGGER.log(Level.SEVERE, "can't set " + attribute + " value", e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
else
{
throw new AttributeNotFoundException();
}
}
@Override
public AttributeList getAttributes(final String[] attributes)
{
final AttributeList list = new AttributeList();
for (String n : attributes)
{
try
{
list.add(new Attribute(n, getAttribute(n)));
}
catch (Exception ignore)
{
// no-op
}
}
return list;
}
@Override
public AttributeList setAttributes(final AttributeList attributes)
{
final AttributeList list = new AttributeList();
for (Object o : attributes)
{
final Attribute attr = (Attribute) o;
try
{
setAttribute(attr);
list.add(attr);
}
catch (Exception ignore)
{
// no-op
}
}
return list;
}
@Override
public Object invoke(final String actionName, final Object[] params, final String[] signature)
throws MBeanException, ReflectionException
{
if (operations.containsKey(actionName))
{
final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classloader);
try
{
return operations.get(actionName).invoke(instance(), params);
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, actionName + " can't be invoked", e);
throw ExceptionUtils.throwAsRuntimeException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
throw new MBeanException(new IllegalArgumentException(), actionName + " doesn't exist");
}
private synchronized Object instance()
{
final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classloader);
try
{
if (instance != null)
{
return instance;
}
if (normalScope)
{
instance = BeanProvider.getContextualReference(clazz, qualifiers);
}
else
{
final BeanManager bm = BeanManagerProvider.getInstance().getBeanManager();
final Set<Bean<?>> beans = bm.getBeans(clazz, qualifiers);
if (beans == null || beans.isEmpty())
{
throw new IllegalStateException("Could not find beans for Type=" + clazz
+ " and qualifiers:" + Arrays.toString(qualifiers));
}
final Bean<?> resolvedBean = bm.resolve(beans);
final CreationalContext<?> creationalContext = bm.createCreationalContext(resolvedBean);
instance = bm.getReference(resolvedBean, clazz, creationalContext);
creationalContext.release();
}
return instance;
}
finally
{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
@Override
public void send(final Notification notification)
{
sendNotification(notification);
}
}