Skip to content

Commit

Permalink
Introduced MBeanPlugins #177
Browse files Browse the repository at this point in the history
  • Loading branch information
rhuss committed Jan 13, 2015
1 parent cf008b0 commit 75ad0b3
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ public <R extends JmxRequest> Object handleRequest(JsonRequestHandler<R> pReques
/**
* Lifecycle method called at the end of life for this object.
*/
public void destroy() {
super.destroy();
public void unregisterFromMBeanNotifications() {
super.unregisterFromMBeanNotifications();
mbeanServers.destroy();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

import org.jolokia.backend.executor.MBeanServerExecutor;
import org.jolokia.backend.executor.NotChangedException;
import org.jolokia.backend.plugin.*;
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.*;
import org.jolokia.util.LogHandler;
import org.jolokia.util.ServiceObjectFactory;

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

private void initPlugins(LogHandler pLogHandler) {
List<MBeanPlugin> plugins = ServiceObjectFactory.createServiceObjects("META-INF/mbean-plugins");
if (plugins.size() > 0) {
MBeanPluginContext ctx = createMBeanPluginContext();
for (MBeanPlugin plugin : plugins) {
plugin.init(ctx);
}
}
}

// Delegate to internal objects
private MBeanPluginContext createMBeanPluginContext() {
return new MBeanPluginContext() {
public ObjectName registerMBean(Object pMBean, String... pOptionalName) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException {
return MBeanServerHandler.this.registerMBean(pMBean,pOptionalName);
}

public void each(ObjectName pObjectName, MBeanEachCallback pCallback) throws IOException, ReflectionException, MBeanException {
mBeanServerManager.each(pObjectName,pCallback);
}

public <R> R call(ObjectName pObjectName, MBeanAction<R> pMBeanAction, Object... pExtraArgs) throws IOException, ReflectionException, MBeanException, AttributeNotFoundException, InstanceNotFoundException {
return mBeanServerManager.call(pObjectName,pMBeanAction,pExtraArgs);
}

public Set<ObjectName> queryNames(ObjectName pObjectName) throws IOException {
return mBeanServerManager.queryNames(pObjectName);
}

public boolean hasMBeansListChangedSince(long pTimestamp) {
return mBeanServerManager.hasMBeansListChangedSince(pTimestamp);
}
};
}


/**
* Initialize the server handle.
* @param pConfig configuration passed through to the server detectors
Expand Down Expand Up @@ -124,7 +163,7 @@ public final ObjectName registerMBean(Object pMBean,String ... pOptionalName)
return registeredName;
} catch (RuntimeException exp) {
throw new IllegalStateException("Could not register " + pMBean + ": " + exp, exp);
} catch (MBeanRegistrationException exp) {
} catch (MBeanRegistrationException exp) {
throw new IllegalStateException("Could not register " + pMBean + ": " + exp, exp);
}
}
Expand Down Expand Up @@ -166,7 +205,7 @@ public final void destroy() throws JMException {
}

// Unregister any notification listener
mBeanServerManager.destroy();
mBeanServerManager.unregisterFromMBeanNotifications();
}

/**
Expand Down Expand Up @@ -219,6 +258,10 @@ private List<ServerDetector> lookupDetectors() {
return detectors;
}

private List<MBeanPlugin> lookupMBeanPlugins() {
return ServiceObjectFactory.createServiceObjects("META-INF/mbean-plugins");
}

// Detect the server by delegating it to a set of predefined detectors. These will be created
// by a lookup mechanism, queried and thrown away after this method
private ServerHandle detectServers(List<ServerDetector> pDetectors, LogHandler pLogHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ protected void registerForMBeanNotifications() {
/**
* Unregister us as listener from every registered server
*/
public void destroy() {
public void unregisterFromMBeanNotifications() {
Set<MBeanServerConnection> servers = getMBeanServers();
Exception lastExp = null;
StringBuilder errors = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,6 @@ <R> R call(ObjectName pObjectName, MBeanAction<R> pMBeanAction, Object... pExtra
*/
Set<ObjectName> queryNames(ObjectName pObjectName) throws IOException;

/**
* Destructor method. After this method has been called, this executor is out of service and
* must not be used anymore
*/
void destroy();

/**
* Check whether the set of MBeans in all managed MBeanServer has been changed
* since the given time. The input is the epoch time in seconds, however, milliseconds
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.jolokia.backend.plugin;

/*
*
* 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.
*/

/**
* 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>
*
* @author roland
* @since 12/01/15
*/
public interface MBeanPlugin {

/**
* Init method for the plugin. The plugin purpose is to register MBeans and to remember the context so
* that it can be later used for doing JMX calls. The context should be propagated to the registered MBean so
* that it can be reused for JMX lookups during its operation.
*
* @param ctx the context in order to access JMX
*/
void init(MBeanPluginContext ctx);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.jolokia.backend.plugin;/*
*
* 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 javax.management.*;

import org.jolokia.backend.executor.MBeanServerExecutor;

/**
* A {@link MBeanPlugin}'s context for accessing JMX with all known MBeanServers. Also it allows to register
* on MBeans for offering own functionality.
*
* @author roland
* @since 12/01/15
*/
public interface MBeanPluginContext extends MBeanServerExecutor {

/**
* Register a MBean under a certain name to the platform MBeanServer. No neeed to unregister, the MBean will be
* automatically unregistered when shutting down the agent. It is recommended to use the domain <code>jolokia</code>
* and a type <code>type=plugin</code> when registering MBeans.
*
* @param pMBean MBean to register
* @param pOptionalName optional name under which the bean should be registered. If not provided,
* it depends on whether the MBean to register implements {@link javax.management.MBeanRegistration} or
* not.
*
* @return the name under which the MBean is registered.
*/
public ObjectName registerMBean(Object pMBean,String ... pOptionalName)
throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException;

}
16 changes: 7 additions & 9 deletions agent/core/src/main/java/org/jolokia/detector/ServerHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
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;

/**
* Information about the the server product the agent is running in.
Expand Down Expand Up @@ -198,14 +199,11 @@ private void addNullSafe(JSONObject pRet, String pKey, Object pValue) {
* @return the detector specific configuration
*/
protected JSONObject getDetectorOptions(Configuration pConfig, LogHandler pLogHandler) {
String optionString = pConfig.get(ConfigKey.DETECTOR_OPTIONS);
if (optionString != null) {
try {
JSONObject opts = (JSONObject) new JSONParser().parse(optionString);
return (JSONObject) opts.get(getProduct());
} catch (Exception e) {
pLogHandler.error("Could not parse options '" + optionString + "' as JSON object: " + e,e);
}
String options = pConfig.get(ConfigKey.DETECTOR_OPTIONS);
try {
return ServiceObjectFactory.extractServiceConfiguration(options,getProduct());
} catch (ParseException e) {
pLogHandler.error("Could not parse detector options '" + options + "' as JSON object: " + e,e);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
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 @@ -53,7 +57,7 @@ private ServiceObjectFactory() {}
* Normally, default service should be given as first parameter so that custom
* descriptors have a chance to remove a default service.
* @param <T> type of the service objects to create
* @return a ordered list of created services.
* @return a ordered list of created services or an empty list.
*/
public static <T> List<T> createServiceObjects(String... pDescriptorPaths) {
try {
Expand Down Expand Up @@ -150,6 +154,22 @@ 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
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ public void updateChangeTest() throws MalformedObjectNameException, NotCompliant
}
}
} finally {
executor.destroy();
executor.unregisterFromMBeanNotifications();
}
}

@Test
public void destroyWithoutPriorRegistration() throws NoSuchFieldException, IllegalAccessException {
// Should always work, even when no registration has happened. Non exisiting listeners will be simplu ignored, since we didnt do any registration before
executor.destroy();
executor.unregisterFromMBeanNotifications();
}

private long currentTime() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void detectorOptionsEmpty() {
@Test
public void detectOptionsFail() {
LogHandler handler = EasyMock.createMock(LogHandler.class);
handler.error(matches("^.*parse options.*"),isA(Exception.class));
handler.error(matches("^.*parse detector options.*"),isA(Exception.class));
replay(handler);

Configuration opts = new Configuration(ConfigKey.DETECTOR_OPTIONS,"blub: bla");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private void createHandler() {

@AfterMethod
private void destroy() {
executor.destroy();
executor.unregisterFromMBeanNotifications();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
* limitations under the License.
*/

import org.testng.annotations.Test;

import java.util.Iterator;
import java.util.List;

import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

/**
* @author roland
Expand Down Expand Up @@ -53,6 +56,20 @@ public void classCastException() {
String bla = services.get(0);
}

@Test
public void extractServiceConfiguration() throws ParseException {
String configString = "{ \"healthCheck\" : { \"path\": \"/tmp\"}}";
JSONObject extra =
ServiceObjectFactory.extractServiceConfiguration(configString, "healthCheck");
assertEquals("/tmp",extra.get("path"));
assertEquals(extra.size(),1);
assertNull(ServiceObjectFactory.extractServiceConfiguration(configString,"blub"));
}

@Test(expectedExceptions = ParseException.class)
public void extractServiceConfigurationFailed() throws ParseException {
ServiceObjectFactory.extractServiceConfiguration("{{ blub }", "healthCheck");
}

interface TestService { String getName(); }
public static class Test1 implements TestService { public String getName() { return "one"; } }
Expand Down

0 comments on commit 75ad0b3

Please sign in to comment.