/*
* 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.aries.jndi.url;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.Callable;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import org.apache.aries.jndi.api.JNDIConstants;
import org.apache.aries.mocks.BundleContextMock;
import org.apache.aries.mocks.BundleMock;
import org.apache.aries.proxy.ProxyManager;
import org.apache.aries.unittest.mocks.MethodCall;
import org.apache.aries.unittest.mocks.MethodCallHandler;
import org.apache.aries.unittest.mocks.Skeleton;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
/**
* Tests for our JNDI implementation for the service registry.
*/
public class ServiceRegistryContextTest
{
/** The service we register by default */
private Runnable service;
/** The bundle context for the test */
private BundleContext bc;
/** The service registration for the service */
private ServiceRegistration reg;
/**
* This method does the setup to ensure we always have a service.
* @throws NamingException
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
@Before
public void registerService() throws NamingException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException
{
bc = Skeleton.newMock(new BundleContextMock(), BundleContext.class);
registerProxyManager();
new org.apache.aries.jndi.startup.Activator().start(bc);
new Activator().start(bc);
service = Skeleton.newMock(Runnable.class);
registerService(service);
}
private void registerProxyManager()
{
ProxyManager mgr = Skeleton.newMock(ProxyManager.class);
// public Object createDelegatingProxy(Bundle clientBundle, Collection<Class<?>> classes, Callable<Object> dispatcher, Object template) throws UnableToProxyException;
Skeleton.getSkeleton(mgr).registerMethodCallHandler(new MethodCall(ProxyManager.class, "createDelegatingProxy", Bundle.class, Collection.class, Callable.class, Object.class),
new MethodCallHandler()
{
public Object handle(MethodCall methodCall, Skeleton skeleton) throws Exception
{
@SuppressWarnings("unchecked")
Collection<Class<?>> interfaceClasses = (Collection<Class<?>>) methodCall.getArguments()[1];
Class<?>[] classes = new Class<?>[interfaceClasses.size()];
Iterator<Class<?>> it = interfaceClasses.iterator();
for (int i = 0; it.hasNext(); i++) {
classes[i] = it.next();
}
@SuppressWarnings("unchecked")
final Callable<Object> target = (Callable<Object>) methodCall.getArguments()[2];
return Proxy.newProxyInstance(this.getClass().getClassLoader(), classes, new InvocationHandler()
{
public Object invoke(Object mock, Method method, Object[] arguments)
throws Throwable
{
return method.invoke(target.call(), arguments);
}
});
}
});
bc.registerService(ProxyManager.class.getName(), mgr, null);
}
/**
* Register a service in our map.
*
* @param service2 The service to register.
*/
private void registerService(Runnable service2)
{
ServiceFactory factory = Skeleton.newMock(ServiceFactory.class);
Skeleton skel = Skeleton.getSkeleton(factory);
skel.setReturnValue(new MethodCall(ServiceFactory.class, "getService", Bundle.class, ServiceRegistration.class), service2);
Hashtable<String, String> props = new Hashtable<String, String>();
props.put("rubbish", "smelly");
reg = bc.registerService(new String[] {"java.lang.Runnable"}, factory, props);
}
/**
* Make sure we clear the caches out before the next test.
*/
@After
public void teardown()
{
BundleContextMock.clear();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
}
@Test
public void testBaseLookup() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
Context ctx2 = (Context) ctx.lookup("osgi:service");
Runnable r1 = (Runnable) ctx2.lookup("java.lang.Runnable");
assertNotNull(r1);
assertTrue("expected proxied service class", r1 != service);
Runnable r2 = (Runnable) ctx.lookup("aries:services/java.lang.Runnable");
assertNotNull(r2);
assertTrue("expected non-proxied service class", r2 == service);
}
@Test
public void testLookupWithPause() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
Hashtable<Object, Object> env = new Hashtable<Object, Object>();
env.put(JNDIConstants.REBIND_TIMEOUT, 1000);
InitialContext ctx = new InitialContext(env);
Context ctx2 = (Context) ctx.lookup("osgi:service");
Runnable r1 = (Runnable) ctx2.lookup("java.lang.Runnable");
reg.unregister();
long startTime = System.currentTimeMillis();
try {
r1.run();
fail("Should have received an exception");
} catch (ServiceException e) {
long endTime = System.currentTimeMillis();
long diff = endTime - startTime;
assertTrue("The run method did not fail in the expected time (1s): " + diff, diff >= 1000);
}
}
/**
* This test checks that we correctly register and deregister the url context
* object factory in the service registry.
*/
@Test
public void testJNDIRegistration()
{
ServiceReference ref = bc.getServiceReference(ObjectFactory.class.getName());
assertNotNull("The aries url context object factory was not registered", ref);
ObjectFactory factory = (ObjectFactory) bc.getService(ref);
assertNotNull("The aries url context object factory was null", factory);
}
@Test
public void jndiLookupServiceNameTest() throws NamingException, SQLException
{
InitialContext ctx = new InitialContext(new Hashtable<Object, Object>());
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
DataSource first = Skeleton.newMock(DataSource.class);
DataSource second = Skeleton.newMock(DataSource.class);
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.put("osgi.jndi.service.name", "jdbc/myDataSource");
bc.registerService(DataSource.class.getName(), first, properties);
properties = new Hashtable<String, String>();
properties.put("osgi.jndi.service.name", "jdbc/myDataSource2");
bc.registerService(DataSource.class.getName(), second, properties);
DataSource s = (DataSource) ctx.lookup("osgi:service/jdbc/myDataSource");
assertNotNull(s);
s = (DataSource) ctx.lookup("osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/myDataSource2)");
assertNotNull(s);
s.isWrapperFor(DataSource.class); // don't care about the method, just need to call something.
Skeleton.getSkeleton(second).assertCalled(new MethodCall(DataSource.class, "isWrapperFor", Class.class));
}
/**
* This test does a simple JNDI lookup to prove that works.
* @throws NamingException
*/
@Test
public void simpleJNDILookup() throws NamingException
{
System.setProperty(Context.URL_PKG_PREFIXES, "helloMatey");
InitialContext ctx = new InitialContext(new Hashtable<Object, Object>());
BundleMock mock = new BundleMock("scooby.doo.1", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
Runnable s = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
assertNotNull("We didn't get a service back from our lookup :(", s);
s.run();
Skeleton.getSkeleton(service).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
Skeleton skel = Skeleton.getSkeleton(mock.getBundleContext());
skel.assertCalled(new MethodCall(BundleContext.class, "getServiceReferences", "java.lang.Runnable", null));
ctx = new InitialContext(new Hashtable<Object, Object>());
mock = new BundleMock("scooby.doo.2", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
s = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
// Check we have the packages set correctly
String packages = System.getProperty(Context.URL_PKG_PREFIXES, null);
assertTrue(ctx.getEnvironment().containsValue(packages));
assertNotNull("We didn't get a service back from our lookup :(", s);
s.run();
Skeleton.getSkeleton(service).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 2);
skel = Skeleton.getSkeleton(mock.getBundleContext());
skel.assertCalled(new MethodCall(BundleContext.class, "getServiceReferences", "java.lang.Runnable", null));
}
/**
* This test checks that we can pass a filter in without things blowing up.
* Right now our mock service registry does not implement filtering, so the
* effect is the same as simpleJNDILookup, but we at least know it is not
* blowing up.
*
* @throws NamingException
*/
@Test
public void jndiLookupWithFilter() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
Object s = ctx.lookup("osgi:service/java.lang.Runnable/(rubbish=smelly)");
assertNotNull("We didn't get a service back from our lookup :(", s);
service.run();
Skeleton.getSkeleton(service).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
Skeleton.getSkeleton(mock.getBundleContext()).assertCalled(new MethodCall(BundleContext.class, "getServiceReferences", "java.lang.Runnable", "(rubbish=smelly)"));
}
/**
* Check that we get a NameNotFoundException if we lookup after the service
* has been unregistered.
*
* @throws NamingException
*/
@Test(expected=NameNotFoundException.class)
public void testLookupWhenServiceHasBeenRemoved() throws NamingException
{
reg.unregister();
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
ctx.lookup("osgi:service/java.lang.Runnable");
}
/**
* Check that we get a NameNotFoundException if we lookup something not in the
* registry.
*
* @throws NamingException
*/
@Test(expected=NameNotFoundException.class)
public void testLookupForServiceWeNeverHad() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
ctx.lookup("osgi:service/java.lang.Integer");
}
/**
* This test checks that we can list the contents of the repository using the
* list method
*
* @throws NamingException
*/
public void listRepositoryContents() throws NamingException
{
InitialContext ctx = new InitialContext();
NamingEnumeration<NameClassPair> serviceList = ctx.list("osgi:service/java.lang.Runnable/(rubbish=smelly)");
checkThreadRetrievedViaListMethod(serviceList);
assertFalse("The repository contained more objects than we expected", serviceList.hasMoreElements());
//Now add a second service
registerService(new Thread());
serviceList = ctx.list("osgi:service/java.lang.Runnable/(rubbish=smelly)");
checkThreadRetrievedViaListMethod(serviceList);
checkThreadRetrievedViaListMethod(serviceList);
assertFalse("The repository contained more objects than we expected", serviceList.hasMoreElements());
}
@Test
public void checkProxyDynamism() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
String className = Runnable.class.getName();
Runnable t = Skeleton.newMock(Runnable.class);
Runnable t2 = Skeleton.newMock(Runnable.class);
// we don't want the default service
reg.unregister();
ServiceRegistration reg = bc.registerService(className, t, null);
bc.registerService(className, t2, null);
Runnable r = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
r.run();
Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
Skeleton.getSkeleton(t2).assertNotCalled(new MethodCall(Runnable.class, "run"));
reg.unregister();
r.run();
Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
Skeleton.getSkeleton(t2).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
}
@Test
public void checkServiceListLookup() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
String className = Runnable.class.getName();
Runnable t = Skeleton.newMock(Runnable.class);
// we don't want the default service
reg.unregister();
ServiceRegistration reg = bc.registerService(className, t, null);
ServiceRegistration reg2 = bc.registerService("java.lang.Thread", new Thread(), null);
Context ctx2 = (Context) ctx.lookup("osgi:servicelist/java.lang.Runnable");
Runnable r = (Runnable) ctx2.lookup(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID)));
r.run();
Skeleton.getSkeleton(t).assertCalled(new MethodCall(Runnable.class, "run"));
reg.unregister();
try {
r.run();
fail("Should have received a ServiceException");
} catch (ServiceException e) {
assertEquals("service exception has the wrong type", ServiceException.UNREGISTERED, e.getType());
}
try {
ctx2.lookup(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID)));
fail("Expected a NameNotFoundException");
} catch (NameNotFoundException e) {
}
}
@Test
public void checkServiceListList() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
String className = Runnable.class.getName();
Runnable t = Skeleton.newMock(Runnable.class);
// we don't want the default service
reg.unregister();
ServiceRegistration reg = bc.registerService(className, t, null);
ServiceRegistration reg2 = bc.registerService(className, new Thread(), null);
NamingEnumeration<NameClassPair> ne = ctx.list("osgi:servicelist/" + className);
assertTrue(ne.hasMoreElements());
NameClassPair ncp = ne.nextElement();
assertEquals(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID)), ncp.getName());
assertTrue("Class name not correct. Was: " + ncp.getClassName(), ncp.getClassName().contains("Proxy"));
assertTrue(ne.hasMoreElements());
ncp = ne.nextElement();
assertEquals(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID)), ncp.getName());
assertEquals("Class name not correct.", Thread.class.getName(), ncp.getClassName());
assertFalse(ne.hasMoreElements());
}
@Test
public void checkServiceListListBindings() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
String className = Runnable.class.getName();
MethodCall run = new MethodCall(Runnable.class, "run");
Runnable t = Skeleton.newMock(Runnable.class);
Runnable t2 = Skeleton.newMock(Runnable.class);
// we don't want the default service
reg.unregister();
ServiceRegistration reg = bc.registerService(className, t, null);
ServiceRegistration reg2 = bc.registerService(className, t2, null);
NamingEnumeration<Binding> ne = ctx.listBindings("osgi:servicelist/" + className);
assertTrue(ne.hasMoreElements());
Binding bnd = ne.nextElement();
assertEquals(String.valueOf(reg.getReference().getProperty(Constants.SERVICE_ID)), bnd.getName());
assertTrue("Class name not correct. Was: " + bnd.getClassName(), bnd.getClassName().contains("Proxy") || bnd.getClassName().contains("EnhancerByCGLIB"));
Runnable r = (Runnable) bnd.getObject();
assertNotNull(r);
r.run();
Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(run, 1);
Skeleton.getSkeleton(t2).assertNotCalled(run);
assertTrue(ne.hasMoreElements());
bnd = ne.nextElement();
assertEquals(String.valueOf(reg2.getReference().getProperty(Constants.SERVICE_ID)), bnd.getName());
assertTrue("Class name not correct. Was: " + bnd.getClassName(), bnd.getClassName().contains("Proxy") || bnd.getClassName().contains("EnhancerByCGLIB"));
r = (Runnable) bnd.getObject();
assertNotNull(r);
r.run();
Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(run, 1);
Skeleton.getSkeleton(t2).assertCalledExactNumberOfTimes(run, 1);
assertFalse(ne.hasMoreElements());
}
@Test(expected=ServiceException.class)
public void checkProxyWhenServiceGoes() throws ServiceException, NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
Runnable r = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
r.run();
Skeleton.getSkeleton(service).assertCalled(new MethodCall(Runnable.class, "run"));
reg.unregister();
r.run();
}
@Test
public void checkServiceOrderObserved() throws NamingException
{
BundleMock mock = new BundleMock("scooby.doo", new Properties());
Thread.currentThread().setContextClassLoader(mock.getClassLoader());
InitialContext ctx = new InitialContext();
String className = Runnable.class.getName();
Runnable t = Skeleton.newMock(Runnable.class);
Runnable t2 = Skeleton.newMock(Runnable.class);
// we don't want the default service
reg.unregister();
ServiceRegistration reg = bc.registerService(className, t, null);
ServiceRegistration reg2 = bc.registerService(className, t2, null);
Runnable r = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
r.run();
Skeleton.getSkeleton(t).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
Skeleton.getSkeleton(t2).assertNotCalled(new MethodCall(Runnable.class, "run"));
reg.unregister();
reg2.unregister();
Hashtable<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_RANKING, 55);
t = Skeleton.newMock(Runnable.class);
t2 = Skeleton.newMock(Runnable.class);
bc.registerService(className, t, null);
bc.registerService(className, t2, props);
r = (Runnable) ctx.lookup("osgi:service/java.lang.Runnable");
r.run();
Skeleton.getSkeleton(t).assertNotCalled(new MethodCall(Runnable.class, "run"));
Skeleton.getSkeleton(t2).assertCalledExactNumberOfTimes(new MethodCall(Runnable.class, "run"), 1);
}
/**
* Check that the NamingEnumeration passed in has another element, which represents a java.lang.Thread
* @param serviceList
* @throws NamingException
*/
private void checkThreadRetrievedViaListMethod(NamingEnumeration<NameClassPair> serviceList)
throws NamingException
{
assertTrue("The repository was empty", serviceList.hasMoreElements());
NameClassPair ncp = serviceList.next();
assertNotNull("We didn't get a service back from our lookup :(", ncp);
assertNotNull("The object from the SR was null", ncp.getClassName());
assertEquals("The service retrieved was not of the correct type", "java.lang.Thread", ncp.getClassName());
assertEquals("osgi:service/java.lang.Runnable/(rubbish=smelly)", ncp.getName().toString());
}
/**
* This test checks that we can list the contents of the repository using the
* list method
*
* @throws NamingException
*/
public void listRepositoryBindings() throws NamingException
{
InitialContext ctx = new InitialContext();
NamingEnumeration<Binding> serviceList = ctx.listBindings("osgi:service/java.lang.Runnable/(rubbish=smelly)");
Object returnedService = checkThreadRetrievedViaListBindingsMethod(serviceList);
assertFalse("The repository contained more objects than we expected", serviceList.hasMoreElements());
assertTrue("The returned service was not the service we expected", returnedService == service);
//Now add a second service
Thread secondService = new Thread();
registerService(secondService);
serviceList = ctx.listBindings("osgi:service/java.lang.Runnable/(rubbish=smelly)");
Object returnedService1 = checkThreadRetrievedViaListBindingsMethod(serviceList);
Object returnedService2 = checkThreadRetrievedViaListBindingsMethod(serviceList);
assertFalse("The repository contained more objects than we expected", serviceList.hasMoreElements());
assertTrue("The services were not the ones we expected!",(returnedService1 == service || returnedService2 == service) && (returnedService1 == secondService || returnedService2 == secondService) && (returnedService1 != returnedService2));
}
/**
* Check that the NamingEnumeration passed in has another element, which represents a java.lang.Thread
* @param serviceList
* @return the object in the registry
* @throws NamingException
*/
private Object checkThreadRetrievedViaListBindingsMethod(NamingEnumeration<Binding> serviceList)
throws NamingException
{
assertTrue("The repository was empty", serviceList.hasMoreElements());
Binding binding = serviceList.nextElement();
assertNotNull("We didn't get a service back from our lookup :(", binding);
assertNotNull("The object from the SR was null", binding.getObject());
assertTrue("The service retrieved was not of the correct type", binding.getObject() instanceof Thread);
assertEquals("osgi:service/java.lang.Runnable/(rubbish=smelly)", binding.getName().toString());
return binding.getObject();
}
}