Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created config dynamic MBean interface #107

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/JMXBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.aeonbits.owner;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.ReflectionException;

public interface JMXBean extends Accessible, DynamicMBean {

public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException,
ReflectionException;

public void setAttribute(Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException;

public AttributeList getAttributes(String[] attributes);

public AttributeList setAttributes(AttributeList attributes);

public Object invoke(String actionName, Object[] params, String[] signature)
throws MBeanException, ReflectionException;

public MBeanInfo getMBeanInfo();

}
109 changes: 108 additions & 1 deletion owner/src/main/java/org/aeonbits/owner/PropertiesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Collections.synchronizedList;
Expand All @@ -63,7 +75,7 @@
*
* @author Luigi R. Viggiano
*/
class PropertiesManager implements Reloadable, Accessible, Mutable {
class PropertiesManager implements Reloadable, Accessible, Mutable, JMXBean {

private final Class<? extends Config> clazz;
private final Map<?, ?>[] imports;
Expand Down Expand Up @@ -581,4 +593,99 @@ public int hashCode() {
}
}

@Delegate
public Object getAttribute(String attribute)
throws AttributeNotFoundException, MBeanException,
ReflectionException {
return getProperty(attribute);
}

@Delegate
public void setAttribute(Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException {
setProperty(attribute.getName(), (String) attribute.getValue());
}

@Delegate
public AttributeList getAttributes(String[] attributes) {
List<Attribute> attrList = new LinkedList<Attribute>();
for (String propertyName : attributes) {
attrList.add(new Attribute(propertyName, properties.getProperty(propertyName)));
}
return new AttributeList(attrList);
}

@Delegate
public AttributeList setAttributes(AttributeList attributes) {
for (Attribute attr : attributes.asList()) {
properties.setProperty(attr.getName(), (String) attr.getValue());
}
return attributes;
}

@Delegate
public Object invoke(String actionName, Object[] params, String[] signature)
throws MBeanException, ReflectionException {
if (actionName.equals("getProperty") &&
params != null && params.length == 1) {
return properties.getProperty((String) params[0]);
}
else if (actionName.equals("setProperty") &&
params != null && params.length == 2) {
properties.setProperty((String) params[0], (String) params[1]);
return null;
}
else if (actionName.equals("reload") &&
(params == null || params.length == 0) &&
(params == null || params.length == 0)) {
reload();
return null;
}
throw new ReflectionException(new NoSuchMethodException(actionName));
}

@Delegate
public MBeanInfo getMBeanInfo() {
List<MBeanAttributeInfo> attrList = new ArrayList<MBeanAttributeInfo>();
Enumeration<?> propertyEnum = properties.propertyNames();
while (propertyEnum.hasMoreElements()) {
String key = (String) propertyEnum.nextElement();
attrList.add(new MBeanAttributeInfo(key, "String", key, true, true,
false));
}
MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[] {};
attributes = attrList.toArray(attributes);

MBeanParameterInfo paramsKey = new MBeanParameterInfo("Propertykey",
"java.lang.String", "Key of the property");
MBeanParameterInfo paramsValue = new MBeanParameterInfo(
"Propertyvalue", "java.lang.String", "Value of the property");


MBeanOperationInfo getOperation = new MBeanOperationInfo("getProperty", "getProperties",
new MBeanParameterInfo[] { paramsKey }, "java.lang.String", MBeanOperationInfo.INFO);
MBeanOperationInfo setOperation = new MBeanOperationInfo("setProperty", "setProperties",
new MBeanParameterInfo[] { paramsKey, paramsValue },"void", MBeanOperationInfo.ACTION);
MBeanOperationInfo reloadOperation = new MBeanOperationInfo("reload", "Reload properties",
null, // no parameters
"void", MBeanOperationInfo.ACTION);

List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>();
if(Accessible.class.isAssignableFrom(clazz)){
operationsList.add(getOperation);
}
if(Mutable.class.isAssignableFrom(clazz)){
operationsList.add(setOperation);
}
if(Reloadable.class.isAssignableFrom(clazz)){
operationsList.add(reloadOperation);
}
MBeanOperationInfo[] operations = new MBeanOperationInfo[operationsList.size()];
operations = operationsList.toArray(operations);

return new MBeanInfo(clazz.getName(), "Owner MBean", attributes, null,
operations, null);
}

}
33 changes: 33 additions & 0 deletions owner/src/test/java/org/aeonbits/owner/jmx/InvalidJMXBeanTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.aeonbits.owner.jmx;

import java.lang.management.ManagementFactory;
import java.util.Properties;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import org.junit.Test;

public class InvalidJMXBeanTest {

private static interface ConfigNoJMX extends Config {
@DefaultValue("1")
int number();
}

@Test(expected=NotCompliantMBeanException.class)
public void testConfigWithoutJMXInterface() throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException{
Properties props = new Properties();
ConfigNoJMX config = ConfigFactory.create(ConfigNoJMX.class, props);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName("org.aeonbits.owner.jmx:type=testConfigWithoutJMXInterface,id=ConfigNoJMX");
mbs.registerMBean(config, mbeanName);
}

}
191 changes: 191 additions & 0 deletions owner/src/test/java/org/aeonbits/owner/jmx/JMXMBeanTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.aeonbits.owner.jmx;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;

import java.lang.management.ManagementFactory;
import java.util.Properties;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.aeonbits.owner.ConfigFactory;
import org.aeonbits.owner.JMXBean;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
import org.junit.Test;

public class JMXMBeanTest {

private static interface JMXConfigMutableReloadable extends JMXBean, Mutable, Reloadable {
@DefaultValue("8080")
int port();

@DefaultValue("http://localhost")
String hostname();

@DefaultValue("42")
int maxThreads();
}

private static interface JMXConfigOnlyAccessible extends JMXBean {
@DefaultValue("1")
int number();
}

private static interface JMXConfigMutableNoReload extends JMXBean, Mutable {
@DefaultValue("1")
int number();
}

/**
*
* Simple test case for JMX accessible mbeans.
*
* Registers a config of JMXConfigMutableReloadable.class
* under object name org.aeonbits.owner.jmx:type=testBeanHandling,id=JMXConfigMutableReloadable and
* tests getAttribute(s) methods and invokes setProperty actions with it.
*
* @throws MalformedObjectNameException
* @throws AttributeNotFoundException
* @throws InstanceNotFoundException
* @throws MBeanException
* @throws ReflectionException
* @throws InstanceAlreadyExistsException
* @throws NotCompliantMBeanException
*/
@Test
public void testBeanHandling() throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, InstanceAlreadyExistsException, NotCompliantMBeanException {
Properties props = new Properties();
JMXConfigMutableReloadable config = ConfigFactory.create(JMXConfigMutableReloadable.class, props);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName(
"org.aeonbits.owner.jmx:type=testBeanHandling,id=JMXConfigMutableReloadable");
mbs.registerMBean(config, mbeanName);

assertEquals("8080", mbs.getAttribute(mbeanName, "port"));
AttributeList attrList = new AttributeList();
attrList.add(new Attribute("port", "8080"));
attrList.add(new Attribute("hostname", "http://localhost"));
attrList.add(new Attribute("maxThreads", "42"));
assertEquals(attrList, mbs.getAttributes(mbeanName, new String[] { "port", "hostname", "maxThreads"}));

mbs.invoke(mbeanName, "setProperty", new String[] { "port", "7878" },
null);
assertEquals("7878", mbs.getAttribute(mbeanName, "port"));
assertEquals("7878", mbs.invoke(mbeanName, "getProperty", new String[] { "port"}, null));

mbs.invoke(mbeanName, "reload", null, null);
assertEquals(attrList, mbs.getAttributes(mbeanName, new String[] { "port", "hostname", "maxThreads"}));
}

/**
*
* Test case for registering multiple mbeans with same configuration object.
*
* @throws MalformedObjectNameException
* @throws AttributeNotFoundException
* @throws InstanceNotFoundException
* @throws MBeanException
* @throws ReflectionException
* @throws InstanceAlreadyExistsException
* @throws NotCompliantMBeanException
* @throws InvalidAttributeValueException
*/
@Test
public void testMultipleBeanHandling() throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, InstanceAlreadyExistsException, NotCompliantMBeanException, InvalidAttributeValueException {
Properties props = new Properties();

JMXConfigMutableReloadable config = ConfigFactory.create(JMXConfigMutableReloadable.class, props);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName1 = new ObjectName(
"org.aeonbits.owner.jmx:type=testMultipleBeanHandling,id=JMXConfigMutableReloadable");
ObjectName mbeanName2 = new ObjectName(
"org.aeonbits.owner.jmx:type=testMultipleBeanHandling2,id=JMXConfigMutableReloadable");
mbs.registerMBean(config, mbeanName1);
mbs.registerMBean(config, mbeanName2);
mbs.setAttribute(mbeanName1, new Attribute("port", "7878"));
assertEquals("7878", mbs.getAttribute(mbeanName1, "port"));
assertEquals(mbs.getAttribute(mbeanName2, "port"), mbs.getAttribute(mbeanName1, "port"));
}

/**
* Test of MBeanInfo description of immutable (and not reloadable) config instance
*
* @throws MalformedObjectNameException
* @throws AttributeNotFoundException
* @throws InstanceNotFoundException
* @throws MBeanException
* @throws ReflectionException
* @throws InstanceAlreadyExistsException
* @throws NotCompliantMBeanException
* @throws IntrospectionException
*/
@Test
public void testBeanNotMutableAndReloadable() throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, InstanceAlreadyExistsException, NotCompliantMBeanException, IntrospectionException {
Properties props = new Properties();
JMXConfigOnlyAccessible config = ConfigFactory.create(JMXConfigOnlyAccessible.class, props);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName(
"org.aeonbits.owner.jmx:type=testBeanNotReloadable,id=JMXConfigOnlyAccessible");
mbs.registerMBean(config, mbeanName);
MBeanInfo info = mbs.getMBeanInfo(mbeanName);

MBeanOperationInfo[] operationsInfo = new MBeanOperationInfo[]{
new MBeanOperationInfo("getProperty", "getProperties",
new MBeanParameterInfo[] { new MBeanParameterInfo("Propertykey", "java.lang.String", "Key of the property") },
"java.lang.String", MBeanOperationInfo.INFO)
};
assertArrayEquals(operationsInfo, info.getOperations());
}

/**
* Test of MBeanInfo description of not reloadable config instance
*
* @throws MalformedObjectNameException
* @throws AttributeNotFoundException
* @throws InstanceNotFoundException
* @throws MBeanException
* @throws ReflectionException
* @throws InstanceAlreadyExistsException
* @throws NotCompliantMBeanException
* @throws IntrospectionException
*/
@Test
public void testBeanNotReloadable() throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, InstanceAlreadyExistsException, NotCompliantMBeanException, IntrospectionException {
Properties props = new Properties();
JMXConfigMutableNoReload config = ConfigFactory.create(JMXConfigMutableNoReload.class, props);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName(
"org.aeonbits.owner.jmx:type=testBeanNotReloadable,id=JMXConfigMutableNoReload");
mbs.registerMBean(config, mbeanName);
MBeanInfo info = mbs.getMBeanInfo(mbeanName);

MBeanOperationInfo[] operationsInfo = new MBeanOperationInfo[]{
new MBeanOperationInfo("getProperty", "getProperties",
new MBeanParameterInfo[] { new MBeanParameterInfo("Propertykey", "java.lang.String", "Key of the property") },
"java.lang.String", MBeanOperationInfo.INFO),
new MBeanOperationInfo("setProperty", "setProperties",
new MBeanParameterInfo[] {
new MBeanParameterInfo("Propertykey", "java.lang.String", "Key of the property"),
new MBeanParameterInfo("Propertyvalue", "java.lang.String", "Value of the property")
},
"void", MBeanOperationInfo.ACTION)
};
assertArrayEquals(operationsInfo, info.getOperations());
}
}