blob: f523bda0a8317b9ab28d793471e8230f68ae4d3d [file] [log] [blame]
/*
* 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.blueprint.proxy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.aries.blueprint.proxy.AbstractProxyTest.TestListener;
import org.apache.aries.mocks.BundleMock;
import org.apache.aries.proxy.impl.interfaces.InterfaceProxyGenerator;
import org.apache.aries.unittest.mocks.MethodCall;
import org.apache.aries.unittest.mocks.Skeleton;
import org.apache.aries.util.ClassLoaderProxy;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
public class InterfaceProxyingTest {
public final static class TestCallable implements Callable<Object> {
private Object list = new Callable<Object>() {
public Object call() throws Exception {
return null;
}
};
public Object call() throws Exception {
return list;
}
public void setReturn(Object o) {
list = o;
}
}
private Bundle testBundle;
/**
* Extended BundleMock which handles update() and adapt() methods
*/
public static class BundleMockEx extends BundleMock {
private BundleWiring currentWiring = Skeleton.newMock(BundleWiring.class);
public BundleMockEx(String name, Dictionary<?, ?> properties) {
super(name, properties);
}
public <A> A adapt(Class<A> type) {
if (type == BundleWiring.class) {
return (A) currentWiring;
}
return null;
}
public void update() throws BundleException {
this.currentWiring = Skeleton.newMock(BundleWiring.class);
}
}
@Before
public void setup() {
testBundle = Skeleton.newMock(new BundleMockEx("test",
new Hashtable<Object, Object>()), Bundle.class);
}
@Test
public void testGetProxyInstance1() throws Exception{
Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Closeable.class));
Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
assertTrue(o instanceof Closeable);
}
@Test
public void testGetProxyInstance2() throws Exception{
Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Closeable.class,
Iterable.class, Map.class));
Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
assertTrue(o instanceof Closeable);
assertTrue(o instanceof Iterable);
assertTrue(o instanceof Map);
}
/**
* Test a class whose super couldn't be woven
*/
@Test
public void testDelegationAndInterception() throws Exception
{
Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Callable.class));
TestListener tl = new TestListener();
TestCallable tc = new TestCallable();
Callable o = (Callable) InterfaceProxyGenerator.getProxyInstance(testBundle,
null, classes, tc, tl);
assertCalled(tl, false, false, false);
assertNull(null, o.call());
assertCalled(tl, true, true, false);
assertEquals(Callable.class.getMethod("call"),
tl.getLastMethod());
tl.clear();
assertCalled(tl, false, false, false);
tc.setReturn(new Callable<Object>() {
public Object call() throws Exception {
throw new RuntimeException();
}
});
try {
o.call();
fail("Should throw an exception");
} catch (RuntimeException re) {
assertCalled(tl, true, false, true);
assertSame(re, tl.getLastThrowable());
}
tl.clear();
assertCalled(tl, false, false, false);
tc.setReturn(new Callable<Object>() {
public Object call() throws Exception {
try {
throw new RuntimeException();
} catch (RuntimeException re) {
return new Object();
}
}
});
try {
assertNotNull(o.call());
} finally {
assertCalled(tl, true, true, false);
}
}
@Test
public void testCaching() throws Exception {
Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Closeable.class));
Object o1 = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
Object o2 = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
assertSame(o1.getClass(), o2.getClass());
}
@Test
public void testComplexInterface() throws Exception {
Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(ProxyTestInterface.class));
final TestCallable tc = new TestCallable();
tc.setReturn(5);
Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(tc), null);
assertTrue(o instanceof ProxyTestInterface);
assertTrue(o instanceof Callable);
assertEquals(5, ((Callable)o).call());
}
@Test
public void testHandlesObjectMethods() throws Exception {
TestListener listener = new TestListener();
List<String> list = Arrays.asList("one", "two", "three");
Object proxied = InterfaceProxyGenerator.getProxyInstance(testBundle, null, Arrays.<Class<?>>asList(List.class), constantly(list), listener);
// obeys hashCode and equals, they *are* on the interface (actually they're
// on several interfaces, we process them in alphabetical order, so Collection
// comes ahead of List.
assertTrue(proxied.equals(Arrays.asList("one", "two", "three")));
assertEquals(Collection.class.getMethod("equals", Object.class), listener.getLastMethod());
listener.clear();
assertEquals(Arrays.asList("one", "two", "three").hashCode(), proxied.hashCode());
assertEquals(Collection.class.getMethod("hashCode"), listener.getLastMethod());
listener.clear();
// and toString
assertEquals(list.toString(), proxied.toString());
assertEquals(Object.class.getMethod("toString"), listener.getLastMethod());
listener.clear();
Runnable runnable = new Runnable() {
public void run() {}
};
proxied = InterfaceProxyGenerator.getProxyInstance(testBundle, null, Arrays.<Class<?>>asList(Runnable.class), constantly(runnable), listener);
// obeys hashCode and equals, they *are not* on the interface
assertTrue(proxied.equals(runnable));
assertEquals(Object.class.getMethod("equals", Object.class), listener.getLastMethod());
listener.clear();
assertEquals(runnable.hashCode(), proxied.hashCode());
assertEquals(Object.class.getMethod("hashCode"), listener.getLastMethod());
listener.clear();
}
private static class TestClassLoader extends ClassLoader {
public TestClassLoader() throws Exception {
InputStream is = TestClassLoader.class.getClassLoader().getResourceAsStream("org/apache/aries/blueprint/proxy/TestInterface.class");
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int b;
while ((b = is.read()) != -1) {
bout.write(b);
}
is.close();
byte[] bytes = bout.toByteArray();
defineClass("org.apache.aries.blueprint.proxy.TestInterface", bytes, 0, bytes.length);
}
}
@Test
public void testNoStaleProxiesForRefreshedBundle() throws Exception {
Bundle bundle = (Bundle) Skeleton.newMock(new Class<?>[] { Bundle.class, ClassLoaderProxy.class });
Skeleton skel = Skeleton.getSkeleton(bundle);
TestClassLoader loader = new TestClassLoader();
skel.setReturnValue(new MethodCall(ClassLoaderProxy.class, "getClassLoader"), loader);
skel.setReturnValue(new MethodCall(Bundle.class, "getLastModified"), 10l);
skel.setReturnValue(new MethodCall(Bundle.class, "adapt", BundleWiring.class), Skeleton.newMock(BundleWiring.class));
Class<?> clazz = loader.loadClass("org.apache.aries.blueprint.proxy.TestInterface");
Object proxy = InterfaceProxyGenerator.getProxyInstance(bundle, null, Arrays.<Class<?>>asList(clazz), constantly(null), null);
assertTrue(clazz.isInstance(proxy));
ClassLoader parent1 = proxy.getClass().getClassLoader().getParent();
/* Now again but with a changed classloader as if the bundle had refreshed */
TestClassLoader loaderToo = new TestClassLoader();
skel.setReturnValue(new MethodCall(ClassLoaderProxy.class, "getClassLoader"), loaderToo);
skel.setReturnValue(new MethodCall(Bundle.class, "getLastModified"), 20l);
// let's change the returned revision
skel.setReturnValue(new MethodCall(Bundle.class, "adapt", BundleWiring.class), Skeleton.newMock(BundleWiring.class));
Class<?> clazzToo = loaderToo.loadClass("org.apache.aries.blueprint.proxy.TestInterface");
Object proxyToo = InterfaceProxyGenerator.getProxyInstance(bundle, null, Arrays.<Class<?>>asList(clazzToo), constantly(null), null);
assertTrue(clazzToo.isInstance(proxyToo));
ClassLoader parent2= proxyToo.getClass().getClassLoader().getParent();
// parents should be different, as the are the classloaders of different bundle revisions
assertTrue(parent1 != parent2);
}
protected void assertCalled(TestListener listener, boolean pre, boolean post, boolean ex) {
assertEquals(pre, listener.preInvoke);
assertEquals(post, listener.postInvoke);
assertEquals(ex, listener.postInvokeExceptionalReturn);
}
private Callable<Object> constantly(final Object result) {
return new Callable<Object>() {
public Object call() throws Exception {
return result;
}
};
}
}