package org.jolokia.osgi;
/*
* Copyright 2009-2011 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.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import javax.servlet.ServletException;
import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;
import org.jolokia.osgi.servlet.JolokiaContext;
import org.jolokia.osgi.servlet.JolokiaServlet;
import org.jolokia.config.ConfigKey;
import org.osgi.framework.*;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.http.*;
import org.osgi.service.log.LogService;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.easymock.EasyMock.*;
import static org.testng.Assert.assertNotNull;
/**
* @author roland
* @since 01.09.11
*/
public class JolokiaActivatorTest {
public static final String HTTP_SERVICE_FILTER = "(objectClass=org.osgi.service.http.HttpService)";
public static final String CONFIG_SERVICE_FILTER = "(objectClass=org.osgi.service.cm.ConfigurationAdmin)";
private BundleContext context;
private JolokiaActivator activator;
// Listener registered by the ServiceTracker
private ServiceListener httpServiceListener;
private ServiceRegistration registration;
private HttpService httpService;
private ServiceReference httpServiceReference;
private ServiceReference configAdminRef;
private ServiceListener configAdminServiceListener;
@BeforeMethod
public void setup() {
activator = new JolokiaActivator();
context = createMock(BundleContext.class);
}
@Test
public void withHttpService() throws InvalidSyntaxException, NoSuchFieldException, IllegalAccessException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
startupHttpService();
unregisterJolokiaServlet();
stopActivator(true);
verify(httpService);
}
@Test
public void withHttpServiceAndExplicitServiceShutdown() throws InvalidSyntaxException, NoSuchFieldException, IllegalAccessException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
startupHttpService();
// Expect that servlet gets unregistered
unregisterJolokiaServlet();
shutdownHttpService();
stopActivator(true);
// Check, that unregister was called
verify(httpService);
}
@Test
public void withHttpServiceAndAdditionalFilter() throws InvalidSyntaxException, NoSuchFieldException, IllegalAccessException, ServletException, NamespaceException, IOException {
startActivator(true, "(Wibble=Wobble)", null);
startupHttpService();
unregisterJolokiaServlet();
stopActivator(true);
verify(httpService);
}
@Test
public void modifiedService() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
startupHttpService();
// Expect that servlet gets unregistered
modifiedHttpService();
// Check, that unregister was called
verify(httpService);
}
@Test
public void withoutServices() throws InvalidSyntaxException, IOException {
startActivator(false, null, null);
stopActivator(false);
}
@Test
public void exceptionDuringRegistration() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
ServletException exp = new ServletException();
prepareErrorLog(exp,"Servlet");
startupHttpService(exp);
}
@Test
public void exceptionDuringRegistration2() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
NamespaceException exp = new NamespaceException("Error");
prepareErrorLog(exp,"Namespace");
startupHttpService(exp);
}
@Test
public void exceptionWithoutLogService() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
expect(context.getServiceReference(LogService.class.getName())).andReturn(null);
startupHttpService(new ServletException());
}
@Test
public void authentication() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
startupHttpService("roland","s!cr!t");
unregisterJolokiaServlet();
stopActivator(true);
verify(httpService);
}
@Test
public void authenticationSecure() throws InvalidSyntaxException, ServletException, NamespaceException, IOException {
startActivator(true, null, null);
startupHttpService("roland","s!cr!t","jaas");
unregisterJolokiaServlet();
stopActivator(true);
verify(httpService);
}
@Test
public void testConfigAdminEmptyDictionary() throws Exception {
Dictionary dict = new Hashtable();
startActivator(false, null, dict);
stopActivator(false);
}
@Test
public void testConfigAdminEmptyDictionaryNoHttpListener() throws Exception {
Dictionary dict = new Hashtable();
startActivator(false, null, dict);
stopActivator(false);
}
@Test
public void testSomePropsFromConfigAdmin() throws Exception {
Dictionary<String, String> dict = new Hashtable<String, String>();
dict.put("org.jolokia.user", "roland");
dict.put("org.jolokia.password", "s!cr!t");
dict.put("org.jolokia.authMode", "jaas");
startActivator(true, null, dict);
startupHttpServiceWithConfigAdminProps();
unregisterJolokiaServlet();
stopActivator(true);
verify(httpService);
}
// ========================================================================================================
private void prepareErrorLog(Exception exp,String msg) {
ServiceReference logServiceReference = createMock(ServiceReference.class);
LogService logService = createMock(LogService.class);
expect(context.getServiceReference(LogService.class.getName())).andReturn(logServiceReference);
expect(context.getService(logServiceReference)).andReturn(logService);
logService.log(eq(LogService.LOG_ERROR), find(msg), eq(exp));
expect(context.ungetService(logServiceReference)).andReturn(false);
replay(logServiceReference, logService);
}
private void shutdownHttpService() {
httpServiceListener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, httpServiceReference));
}
private void modifiedHttpService() {
httpServiceListener.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, httpServiceReference));
}
private void unregisterJolokiaServlet() {
reset(httpService);
// Will be called when activator is stopped
httpService.unregister(ConfigKey.AGENT_CONTEXT.getDefaultValue());
replay(httpService);
}
private void startupHttpService(Object ... args) throws ServletException, NamespaceException {
String auth[] = new String[] { null, null,null };
Exception exp = null;
int i = 0;
for (Object arg : args) {
if (arg instanceof String) {
auth[i++] = (String) arg;
} else if (arg instanceof Exception) {
exp = (Exception) arg;
}
}
httpServiceReference = createMock(ServiceReference.class);
httpService = createMock(HttpService.class);
expect(context.getService(httpServiceReference)).andReturn(httpService);
i = 0;
for (ConfigKey key : ConfigKey.values()) {
if (auth[0] != null && key == ConfigKey.USER) {
expect(context.getProperty("org.jolokia." + ConfigKey.USER.getKeyValue())).andStubReturn(auth[0]);
} else if (auth[1] != null && key == ConfigKey.PASSWORD) {
expect(context.getProperty("org.jolokia." + ConfigKey.PASSWORD.getKeyValue())).andStubReturn(auth[1]);
} else if (auth[2] != null && key == ConfigKey.AUTH_MODE) {
expect(context.getProperty("org.jolokia." + ConfigKey.AUTH_MODE.getKeyValue())).andStubReturn(auth[2]);
} else {
expect(context.getProperty("org.jolokia." + key.getKeyValue())).andStubReturn(
i++ % 2 == 0 ? key.getDefaultValue() : null);
}
}
httpService.registerServlet(eq(ConfigKey.AGENT_CONTEXT.getDefaultValue()),
isA(JolokiaServlet.class),
EasyMock.<Dictionary>anyObject(),
EasyMock.<HttpContext>anyObject());
if (exp != null) {
expectLastCall().andThrow(exp);
}
replay(context, httpServiceReference, httpService);
// Attach service
httpServiceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, httpServiceReference));
}
private void startupHttpServiceWithConfigAdminProps() throws ServletException, NamespaceException {
httpServiceReference = createMock(ServiceReference.class);
httpService = createMock(HttpService.class);
expect(context.getService(httpServiceReference)).andReturn(httpService);
int i = 0;
for (ConfigKey key : ConfigKey.values()) {
if (key == ConfigKey.USER || key == ConfigKey.PASSWORD || key == ConfigKey.AUTH_MODE) {
//ignore these, they will be provided from config admin service
} else {
expect(context.getProperty("org.jolokia." + key.getKeyValue())).andStubReturn(
i++ % 2 == 0 ? key.getDefaultValue() : null);
}
}
httpService.registerServlet(eq(ConfigKey.AGENT_CONTEXT.getDefaultValue()),
isA(JolokiaServlet.class),
EasyMock.<Dictionary>anyObject(),
EasyMock.<HttpContext>anyObject());
replay(context, httpServiceReference, httpService);
// Attach service
httpServiceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, httpServiceReference));
}
private void stopActivator(boolean withHttpListener) {
reset(context);
if (withHttpListener) {
reset(registration);
}
if (withHttpListener) {
context.removeServiceListener(httpServiceListener);
expect(context.getProperty("org.jolokia." + ConfigKey.AGENT_CONTEXT.getKeyValue()))
.andReturn(ConfigKey.AGENT_CONTEXT.getDefaultValue());
registration.unregister();
}
expect(context.ungetService(anyObject(ServiceReference.class))).andReturn(true).anyTimes();
context.removeServiceListener(configAdminServiceListener);
replay(context);
if (withHttpListener) {
replay(registration);
}
activator.stop(context);
}
private void startActivator(boolean withHttpListener, String httpFilter, Dictionary configAdminProps) throws InvalidSyntaxException, IOException {
reset(context);
prepareStart(withHttpListener, true, httpFilter, configAdminProps);
replay(context);
if (withHttpListener) {
replay(registration);
}
activator.start(context);
if (withHttpListener) {
assertNotNull(httpServiceListener);
}
assertNotNull(configAdminServiceListener);
reset(context);
}
private void prepareStart(boolean doHttpService, boolean doRestrictor, String httpFilter, Dictionary configAdminProps) throws InvalidSyntaxException, IOException {
expect(context.getProperty("org.jolokia.listenForHttpService")).andReturn("" + doHttpService);
if (doHttpService) {
expect(context.getProperty("org.jolokia.httpServiceFilter")).andReturn(httpFilter);
Filter filter = createFilterMockWithToString(HTTP_SERVICE_FILTER, httpFilter);
expect(context.createFilter(filter.toString())).andReturn(filter);
expect(context.getProperty("org.osgi.framework.version")).andReturn("4.5.0");
context.addServiceListener(httpRememberListener(), eq(filter.toString()));
expect(context.getServiceReferences(null, filter.toString())).andReturn(null);
registration = createMock(ServiceRegistration.class);
expect(context.registerService(JolokiaContext.class.getName(), activator, null)).andReturn(registration);
}
//Setup ConfigurationAdmin service
Filter configFilter = createFilterMockWithToString(CONFIG_SERVICE_FILTER, null);
expect(context.createFilter(configFilter.toString())).andReturn(configFilter);
context.addServiceListener(configAdminRememberListener(), eq(configFilter.toString()));
configAdminRef = createMock(ServiceReference.class);
expect(context.getServiceReferences(ConfigurationAdmin.class.getCanonicalName(), null)).andReturn(new ServiceReference[]{configAdminRef}).anyTimes();
ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
Configuration config = createMock(Configuration.class);
expect(config.getProperties()).andReturn(configAdminProps).anyTimes();
expect(configAdmin.getConfiguration("org.jolokia.osgi")).andReturn(config).anyTimes();
expect(context.getService(configAdminRef)).andReturn(configAdmin).anyTimes();
replay(configAdminRef, configAdmin, config);
expect(context.getProperty("org.jolokia.useRestrictorService")).andReturn("" + doRestrictor);
}
// Easymock work around given the fact you can not mock toString() using easymock (because it easymock uses toString()
// of mocked objects internally)
private Filter createFilterMockWithToString(final String filter, final String additionalFilter) {
return (Filter) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Filter.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("toString")) {
if (additionalFilter == null) {
return filter;
} else {
return "(&" + filter + additionalFilter +")" ;
}
}
throw new UnsupportedOperationException("Sorry this is a very limited proxy implementation of Filter");
}
});
}
private ServiceListener httpRememberListener() {
reportMatcher(new IArgumentMatcher() {
public boolean matches(Object argument) {
httpServiceListener = (ServiceListener) argument;
return true;
}
public void appendTo(StringBuffer buffer) {
}
});
return null;
}
private ServiceListener configAdminRememberListener() {
reportMatcher(new IArgumentMatcher() {
public boolean matches(Object argument) {
configAdminServiceListener = (ServiceListener) argument;
return true;
}
public void appendTo(StringBuffer buffer) {
}
});
return null;
}
}