Skip to content

Commit

Permalink
Added unit tests #177
Browse files Browse the repository at this point in the history
  • Loading branch information
rhuss committed Jan 13, 2015
1 parent c4d3521 commit 5c3a77b
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 47 deletions.
Expand Up @@ -8,14 +8,18 @@

import org.jolokia.backend.executor.MBeanServerExecutor;
import org.jolokia.backend.executor.NotChangedException;
import org.jolokia.backend.plugin.*;
import org.jolokia.backend.plugin.MBeanPlugin;
import org.jolokia.backend.plugin.MBeanPluginContext;
import org.jolokia.config.ConfigKey;
import org.jolokia.config.Configuration;
import org.jolokia.detector.*;
import org.jolokia.handler.JsonRequestHandler;
import org.jolokia.request.JmxRequest;
import org.jolokia.util.LogHandler;
import org.jolokia.util.ServiceObjectFactory;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/*
* Copyright 2009-2013 Roland Huss
Expand Down Expand Up @@ -71,19 +75,33 @@ public MBeanServerHandler(Configuration pConfig, LogHandler pLogHandler) {
mBeanServerManager = new MBeanServerExecutorLocal(detectors);
initServerHandle(pConfig, pLogHandler, detectors);
initMBean();
initPlugins(pLogHandler);
initPlugins(pConfig, pLogHandler);
}

private void initPlugins(LogHandler pLogHandler) {
List<MBeanPlugin> plugins = ServiceObjectFactory.createServiceObjects("META-INF/mbean-plugins");
private void initPlugins(Configuration pConfig, LogHandler pLogHandler) {
List<MBeanPlugin> plugins = ServiceObjectFactory.createServiceObjects("META-INF/plugins");
if (plugins.size() > 0) {
MBeanPluginContext ctx = createMBeanPluginContext();
Map pluginConfigs = getPluginOptions(pConfig,pLogHandler);
for (MBeanPlugin plugin : plugins) {
plugin.init(ctx);
try {
plugin.init(ctx,(Map) pluginConfigs.get(plugin.getId()));
} catch (JMException exp) {
pLogHandler.error("Error while initializing plugin " + plugin.getId(),exp);
}
}
}
}

private Map getPluginOptions(Configuration pConfig, LogHandler pLogHandler) {
String options = pConfig.get(ConfigKey.MBEAN_PLUGIN_OPTIONS);
try {
return options != null ? (JSONObject) new JSONParser().parse(options) : new JSONObject();
} catch (ParseException e) {
throw new IllegalStateException("Could not parse plugin options '" + options + "' as JSON Objects" + e,e);
}
}

// Delegate to internal objects
private MBeanPluginContext createMBeanPluginContext() {
return new MBeanPluginContext() {
Expand Down
Expand Up @@ -17,6 +17,10 @@
* limitations under the License.
*/

import java.util.Map;

import javax.management.JMException;

/**
* Interface describing a plugin which can be used to register extra MBeans for enhancing the Jolokia API.
* MBeanPlugins are looked up from the classpath and should be registered in <code>META-INF/mbean-plugins</code>
Expand All @@ -32,6 +36,14 @@ public interface MBeanPlugin {
* that it can be reused for JMX lookups during its operation.
*
* @param ctx the context in order to access JMX
* @param map
*/
void init(MBeanPluginContext ctx, Map map) throws JMException;

/**
* Get unique id for this plugin. This id is also used for looking up plugin specific configuration.
*
* @return unique id for this plugin
*/
void init(MBeanPluginContext ctx);
String getId();
}
9 changes: 9 additions & 0 deletions agent/core/src/main/java/org/jolokia/config/ConfigKey.java
Expand Up @@ -19,6 +19,8 @@
import java.util.HashMap;
import java.util.Map;

import org.jolokia.backend.plugin.MBeanPlugin;

/**
* Enumeration defining the various configuration constant names which
* can be used to configure the agent globally (e.g. in web.xml) or
Expand Down Expand Up @@ -238,6 +240,13 @@ public enum ConfigKey {
*/
DETECTOR_OPTIONS("detectorOptions",true, false),

/**
* Extra options which are passed to {@link MBeanPlugin}. As for {@link #DETECTOR_OPTIONS}, the value
* must be a JSON object in string representation where the keys are MBean plugin ids and the values are
* JSON objects whith the plugin specific configuration.
*/
MBEAN_PLUGIN_OPTIONS("mbeanPluginOptions",true , false),

/**
* The ID to uniquely identify this agent within a JVM. There
* can be multiple agents registered a JVM. This id is e.g. used to
Expand Down
Expand Up @@ -25,8 +25,8 @@
import org.jolokia.config.Configuration;
import org.jolokia.request.JmxRequest;
import org.jolokia.util.LogHandler;
import org.jolokia.util.ServiceObjectFactory;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
Expand Down Expand Up @@ -201,7 +201,11 @@ private void addNullSafe(JSONObject pRet, String pKey, Object pValue) {
protected JSONObject getDetectorOptions(Configuration pConfig, LogHandler pLogHandler) {
String options = pConfig.get(ConfigKey.DETECTOR_OPTIONS);
try {
return ServiceObjectFactory.extractServiceConfiguration(options,getProduct());
if (options != null) {
JSONObject opts = (JSONObject) new JSONParser().parse(options);
return (JSONObject) opts.get(getProduct());
}
return null;
} catch (ParseException e) {
pLogHandler.error("Could not parse detector options '" + options + "' as JSON object: " + e,e);
}
Expand Down
Expand Up @@ -20,10 +20,6 @@
import java.net.URL;
import java.util.*;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
* A simple factory for creating services with no-arg constructors from a textual
* descriptor. This descriptor, which must be a resource loadable by this class'
Expand Down Expand Up @@ -154,22 +150,6 @@ private static void closeReader(LineNumberReader pReader) {
}
}

/**
* Helper method for parsing a string as a JSON Object and returns the JSON object which is stored under a certain key
*
* @param serviceConfigString string holding the extra configuration
* @param key key to lookup the extra configuration
* @return the extra configuration or <code>null</code> if none has been given.
* @throws ParseException if parsing of the original string as JSON object fails.
*/
public static JSONObject extractServiceConfiguration(String serviceConfigString, String key) throws ParseException {
if (serviceConfigString != null) {
JSONObject opts = (JSONObject) new JSONParser().parse(serviceConfigString);
return (JSONObject) opts.get(key);
}
return null;
}

// =============================================================================

static class ServiceEntry implements Comparable<ServiceEntry> {
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.util.List;

import javax.management.*;
import javax.management.openmbean.CompositeData;

import org.easymock.EasyMock;
import org.jolokia.backend.executor.MBeanServerExecutor;
Expand Down Expand Up @@ -52,14 +53,38 @@ public class MBeanServerHandlerTest {
@BeforeMethod
public void setup() throws MalformedObjectNameException {
TestDetector.reset();
Configuration config = new Configuration(ConfigKey.MBEAN_QUALIFIER,"qualifier=test");
Configuration config = new Configuration(ConfigKey.MBEAN_QUALIFIER,"qualifier=test",
ConfigKey.MBEAN_PLUGIN_OPTIONS,"{\"test\" : { \"path\" : \"/tmp\" }}");
handler = new MBeanServerHandler(config,getEmptyLogHandler());
request = new JmxRequestBuilder(RequestType.READ,"java.lang:type=Memory").attribute("HeapMemoryUsage").build();
}

@AfterMethod
public void tearDown() throws JMException {
handler.destroy();
assertTrue(TestMBeanPlugin.isInitCalled());
}

@Test
public void testPluginUsed() throws MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, IOException {
MBeanServerConnection conn = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("jolokia:type=plugin,name=test");
long memBean = (Long) conn.getAttribute(name,"MemoryUsed");
CompositeData memSystemCd = (CompositeData) conn.getAttribute(new ObjectName("java.lang:type=Memory"),"HeapMemoryUsage");
long memSystem = (Long) memSystemCd.get("used");
double diff = Math.abs(memBean - memSystem) / memSystem * 100;
// Less than 10% difference
assertTrue(diff < 10);
}

@Test
public void testPluginMax() throws MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, IOException {
MBeanServerConnection conn = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("jolokia:type=plugin,name=test");
long memBean = (Long) conn.getAttribute(name,"MemoryMax");
CompositeData memSystemCd = (CompositeData) conn.getAttribute(new ObjectName("java.lang:type=Memory"),"HeapMemoryUsage");
long memSystem = (Long) memSystemCd.get("max");
assertEquals(memBean,memSystem);
}

@Test
Expand Down
113 changes: 113 additions & 0 deletions agent/core/src/test/java/org/jolokia/backend/TestMBeanPlugin.java
@@ -0,0 +1,113 @@
package org.jolokia.backend;/*
*
* Copyright 2014 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 javax.management.openmbean.CompositeData;

import org.jolokia.backend.executor.MBeanServerExecutor;
import org.jolokia.backend.plugin.MBeanPlugin;
import org.jolokia.backend.plugin.MBeanPluginContext;

import static org.testng.Assert.*;

/**
* @author roland
* @since 13/01/15
*/
public class TestMBeanPlugin implements MBeanPlugin {

private static boolean initCalled = false;

public void init(MBeanPluginContext ctx, Map map) throws JMException {
assertNotNull(ctx);
if (map != null) {
assertEquals(map.size(), 1);
assertEquals(map.get("path"), "/tmp");
}
ctx.registerMBean(new Test(ctx), "jolokia:type=plugin,name=test");

initCalled = true;
}

private void checkJmx(MBeanPluginContext ctx) throws MalformedObjectNameException, InstanceAlreadyExistsException, NotCompliantMBeanException {
ctx.registerMBean(new Test(ctx),"jolokia:type=plugin,name=test");
}

public static boolean isInitCalled() {
return initCalled;
}

public String getId() {
return "test";
}

public interface TestMBean {
int convert(String arg);
void error() throws Exception;
long getMemoryUsed() throws MalformedObjectNameException, IOException, ReflectionException, MBeanException, AttributeNotFoundException, InstanceNotFoundException;
long getMemoryMax() throws MalformedObjectNameException, MBeanException, IOException, ReflectionException;
}

public class Test implements TestMBean {

MBeanPluginContext ctx;

public Test(MBeanPluginContext ctx) {
this.ctx = ctx;
}

public int convert(String arg) {
return Integer.parseInt(arg);
}

public void error() throws Exception {
throw new Exception();
}

public long getMemoryUsed() throws MalformedObjectNameException, IOException, ReflectionException, MBeanException, AttributeNotFoundException, InstanceNotFoundException {
Set<ObjectName> names = ctx.queryNames(new ObjectName("java.lang:type=Memory"));
ObjectName memName = names.iterator().next();
assertEquals(names.size(),1);
return ctx.call(memName, new MBeanServerExecutor.MBeanAction<Long>() {
public Long execute(MBeanServerConnection pConn, ObjectName pName, Object... extraArgs) throws ReflectionException, InstanceNotFoundException, IOException, MBeanException, AttributeNotFoundException {
CompositeData data = (CompositeData) pConn.getAttribute(pName,"HeapMemoryUsage");
return (Long) data.get("used");
}
});
}

public long getMemoryMax() throws MalformedObjectNameException, MBeanException, IOException, ReflectionException {
final List<Long> mems = new ArrayList<Long>();
ctx.each(new ObjectName("java.lang:type=Memory"), new MBeanServerExecutor.MBeanEachCallback() {
public void callback(MBeanServerConnection pConn, ObjectName pName) throws ReflectionException, InstanceNotFoundException, IOException, MBeanException {
CompositeData cd = null;
try {
cd = (CompositeData) pConn.getAttribute(pName,"HeapMemoryUsage");
mems.add((Long) cd.get("max"));
} catch (AttributeNotFoundException e) {
// Ignore it ...
}
}
});
assertEquals(mems.size(), 1);
return mems.get(0);
}
}
}
@@ -0,0 +1,37 @@
package org.jolokia.backend;/*
*
* Copyright 2014 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.util.Map;

import javax.management.JMException;

import org.jolokia.backend.plugin.MBeanPlugin;
import org.jolokia.backend.plugin.MBeanPluginContext;

/**
* @author roland
* @since 13/01/15
*/
public class TestMBeanPluginFailing implements MBeanPlugin {
public void init(MBeanPluginContext ctx, Map map) throws JMException {
throw new JMException("Error");
}

public String getId() {
return "test-error";
}
}
Expand Up @@ -20,6 +20,7 @@
import java.net.SocketException;
import java.util.*;

import javax.management.JMException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -529,6 +530,8 @@ private void initConfigMocks(String[] pInitParams, String[] pContextParams,Strin
context.log((String) anyObject());
expectLastCall().asStub();
context.log(find("TestDetector"),isA(RuntimeException.class));
context.log((String) anyObject(),isA(JMException.class));
expectLastCall().anyTimes();
}

private StringWriter initRequestResponseMocks() throws IOException {
Expand Down

0 comments on commit 5c3a77b

Please sign in to comment.