/* | |
* 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.assertFalse; | |
import static org.junit.Assert.assertNotNull; | |
import static org.junit.Assert.assertTrue; | |
import static org.junit.Assert.fail; | |
import java.io.ByteArrayOutputStream; | |
import java.io.InputStream; | |
import java.io.ObjectOutputStream; | |
import java.lang.reflect.Method; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.Callable; | |
import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassInner; | |
import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassStaticInner; | |
import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassUnweavableInnerChild; | |
import org.apache.aries.blueprint.proxy.pkg.ProxyTestClassUnweavableSuperWithDefaultMethodWrongPackageParent; | |
import org.apache.aries.proxy.FinalModifierException; | |
import org.apache.aries.proxy.InvocationListener; | |
import org.apache.aries.proxy.UnableToProxyException; | |
import org.apache.aries.proxy.impl.AsmProxyManager; | |
import org.apache.aries.proxy.impl.SingleInstanceDispatcher; | |
import org.apache.aries.proxy.impl.SystemModuleClassLoader; | |
import org.apache.aries.proxy.impl.gen.ProxySubclassMethodHashSet; | |
import org.apache.aries.proxy.impl.weaving.WovenProxyGenerator; | |
import org.apache.aries.proxy.weaving.WovenProxy; | |
import org.apache.aries.unittest.mocks.MethodCall; | |
import org.apache.aries.unittest.mocks.Skeleton; | |
import org.apache.aries.util.ClassLoaderProxy; | |
import org.junit.BeforeClass; | |
import org.junit.Test; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.wiring.BundleWiring; | |
public class WovenProxyGeneratorTest extends AbstractProxyTest | |
{ | |
private static final String hexPattern = "[0-9_a-f]"; | |
private static final int[] uuid_pattern = new int[] {8,4,4,4,12}; | |
private static final String regexp; | |
static { | |
StringBuilder sb = new StringBuilder(".*"); | |
for(int i : uuid_pattern) { | |
for(int j = 0; j < i; j++){ | |
sb.append(hexPattern); | |
} | |
sb.append("_"); | |
} | |
sb.deleteCharAt(sb.length() -1); | |
sb.append("\\d*"); | |
regexp = sb.toString(); | |
} | |
/** An array of classes that will be woven - note no UnweavableParents should be in here! */ | |
private static final List<Class<?>> CLASSES = Arrays.asList(new Class<?>[]{ProxyTestClassGeneral.class, ProxyTestClassSuper.class, | |
ProxyTestClassFinalMethod.class, ProxyTestClassFinal.class, ProxyTestClassGeneric.class, | |
ProxyTestClassGenericSuper.class, ProxyTestClassCovariant.class, ProxyTestClassCovariantOverride.class, | |
ProxyTestClassUnweavableChild.class, ProxyTestClassUnweavableSibling.class, ProxyTestClassInner.class, | |
ProxyTestClassStaticInner.class, ProxyTestClassUnweavableInnerChild.class, | |
ProxyTestClassUnweavableChildWithFinalMethodParent.class, | |
ProxyTestClassUnweavableChildWithDefaultMethodWrongPackageParent.class, | |
ProxyTestClassSerializable.class, ProxyTestClassSerializableWithSVUID.class, | |
ProxyTestClassSerializableChild.class, ProxyTestClassSerializableInterface.class, | |
ProxyTestClassStaticInitOfChild.class, ProxyTestClassAbstract.class}); | |
/** An array of classes that are loaded by the WeavingLoader, but not actually woven **/ | |
private static final List<Class<?>> OTHER_CLASSES = Arrays.asList(new Class<?>[] {ProxyTestClassUnweavableSuper.class, | |
ProxyTestClassStaticInitOfChildParent.class, ProxyTestClassChildOfAbstract.class}); | |
private static final Map<String, byte[]> rawClasses = new HashMap<String, byte[]>(); | |
protected static final ClassLoader weavingLoader = new SystemModuleClassLoader() { | |
public Class<?> loadClass(String className) throws ClassNotFoundException | |
{ | |
return loadClass(className, false); | |
} | |
public Class<?> loadClass(String className, boolean b) throws ClassNotFoundException | |
{ | |
if (!!!className.startsWith("org.apache.aries.blueprint.proxy.ProxyTest")){ | |
return Class.forName(className); | |
} | |
Class<?> clazz = findLoadedClass(className); | |
if(clazz != null) | |
return clazz; | |
byte[] bytes = rawClasses.get(className); | |
if(bytes == null) | |
return super.loadClass(className, b); | |
boolean weave = false; | |
for(Class<?> c : CLASSES) { | |
if(c.getName().equals(className)) { | |
weave = true; | |
break; | |
} | |
} | |
if(weave) | |
bytes = WovenProxyGenerator.getWovenProxy(bytes, this); | |
return defineClass(className, bytes, 0, bytes.length); | |
} | |
protected URL findResource(String resName) { | |
return WovenProxyGeneratorTest.class.getResource(resName); | |
} | |
}; | |
/** | |
* @throws java.lang.Exception | |
*/ | |
@BeforeClass | |
public static void setUp() throws Exception | |
{ | |
List<Class<?>> classes = new ArrayList(CLASSES.size() + OTHER_CLASSES.size()); | |
classes.addAll(CLASSES); | |
classes.addAll(OTHER_CLASSES); | |
for(Class<?> clazz : classes) { | |
InputStream is = clazz.getClassLoader().getResourceAsStream( | |
clazz.getName().replace('.', '/') + ".class"); | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
byte[] buffer = new byte[2048]; | |
int read = is.read(buffer); | |
while(read != -1) { | |
baos.write(buffer, 0, read); | |
read = is.read(buffer); | |
} | |
rawClasses.put(clazz.getName(), baos.toByteArray()); | |
} | |
} | |
/** | |
* This test uses the WovenProxyGenerator to generate and load the specified getTestClass(). | |
* | |
* Once the subclass is generated we check that it wasn't null. | |
* | |
* Test method for | |
* {@link WovenProxyGenerator#getProxySubclass(byte[], String)}. | |
*/ | |
@Test | |
public void testGenerateAndLoadProxy() throws Exception | |
{ | |
super.testGenerateAndLoadProxy(); | |
assertTrue("Should be a WovenProxy", WovenProxy.class.isAssignableFrom(getProxyClass(getTestClass()))); | |
} | |
/** | |
* Test that the methods found declared on the generated proxy are | |
* the ones that we expect. | |
*/ | |
@Test | |
public void testExpectedMethods() throws Exception | |
{ | |
ProxySubclassMethodHashSet<String> originalMethods = getMethods(getTestClass()); | |
ProxySubclassMethodHashSet<String> generatedMethods = getMethods(weavingLoader. | |
loadClass(getTestClass().getName())); | |
// check that all the methods we have generated were expected | |
for (String gen : generatedMethods) { | |
assertTrue("Unexpected method: " + gen, originalMethods.contains(gen)); | |
} | |
// check that all the expected methods were generated | |
for (String exp : originalMethods) { | |
assertTrue("Method was not generated: " + exp, generatedMethods.contains(exp)); | |
} | |
// check the sets were the same | |
assertEquals("Sets were not the same", originalMethods, generatedMethods); | |
} | |
private ProxySubclassMethodHashSet<String> getMethods(Class<?> clazz) { | |
ProxySubclassMethodHashSet<String> foundMethods = | |
new ProxySubclassMethodHashSet<String>(12); | |
do { | |
Method[] declaredMethods = clazz.getDeclaredMethods(); | |
List<Method> listOfDeclaredMethods = new ArrayList<Method>(); | |
for (Method m : declaredMethods) { | |
if(m.getName().startsWith(WovenProxy.class.getName().replace('.', '_')) || | |
m.getName().startsWith("getListener") || m.getName().startsWith("getInvocationTarget") || | |
//four hex digits | |
m.getName().matches(regexp)) | |
continue; | |
listOfDeclaredMethods.add(m); | |
} | |
declaredMethods = listOfDeclaredMethods.toArray(new Method[] {}); | |
foundMethods.addMethodArray(declaredMethods); | |
clazz = clazz.getSuperclass(); | |
} while (clazz != null); | |
return foundMethods; | |
} | |
/** | |
* Test a method marked final | |
*/ | |
@Test | |
public void testFinalMethod() throws Exception | |
{ | |
assertNotNull(weavingLoader.loadClass(ProxyTestClassFinalMethod.class | |
.getName())); | |
} | |
/** | |
* Test a class marked final | |
*/ | |
@Test | |
public void testFinalClass() throws Exception | |
{ | |
assertNotNull(weavingLoader.loadClass(ProxyTestClassFinal.class | |
.getName())); | |
} | |
/** | |
* Test a private constructor | |
*/ | |
@Test | |
public void testPrivateConstructor() throws Exception | |
{ | |
assertNotNull(weavingLoader.loadClass(ProxyTestClassFinal.class | |
.getName())); | |
} | |
/** | |
* Test a class whose super couldn't be woven | |
*/ | |
@Test | |
public void testUnweavableSuper() throws Exception | |
{ | |
Class<?> woven = getProxyClass(ProxyTestClassUnweavableChild.class); | |
assertNotNull(woven); | |
assertNotNull(getProxyInstance(woven)); | |
TestListener tl = new TestListener(); | |
Object ptcuc = getProxyInstance(woven, tl); | |
assertCalled(tl, false, false, false); | |
Method m = ptcuc.getClass().getMethod("doStuff"); | |
assertEquals("Hi!", m.invoke(ptcuc)); | |
assertCalled(tl, true, true, false); | |
assertEquals(ProxyTestClassUnweavableGrandParent.class.getMethod("doStuff"), | |
tl.getLastMethod()); | |
tl.clear(); | |
//Because default access works on the package, and we are defined on a different classloader | |
//we have to call setAccessible... | |
m = getDeclaredMethod(ProxyTestClassUnweavableChild.class, "doStuff2"); | |
m.setAccessible(true); | |
assertEquals("Hello!", m.invoke(ptcuc)); | |
assertCalled(tl, true, true, false); | |
assertEquals(weavingLoader.loadClass(ProxyTestClassUnweavableSuper.class.getName()).getDeclaredMethod("doStuff2"), | |
tl.getLastMethod()); | |
} | |
@Test | |
public void testUnweavableSuperWithNoNoargsAllTheWay() throws Exception | |
{ | |
try { | |
getProxyClass(ProxyTestClassUnweavableSibling.class); | |
fail(); | |
} catch (RuntimeException re) { | |
assertTrue(re.getCause() instanceof UnableToProxyException); | |
assertEquals(ProxyTestClassUnweavableSibling.class.getName(), | |
((UnableToProxyException)re.getCause()).getClassName()); | |
} | |
} | |
/** | |
* Test a class whose super couldn't be woven | |
*/ | |
@Test | |
public void testUnweavableSuperWithFinalMethod() throws Exception | |
{ | |
try{ | |
getProxyClass(ProxyTestClassUnweavableChildWithFinalMethodParent.class); | |
fail(); | |
} catch (RuntimeException re) { | |
assertTrue(re.getCause() instanceof FinalModifierException); | |
assertEquals(ProxyTestClassUnweavableSuperWithFinalMethod.class.getName(), | |
((FinalModifierException)re.getCause()).getClassName()); | |
assertEquals("doStuff2", ((FinalModifierException)re.getCause()) | |
.getFinalMethods()); | |
} | |
} | |
/** | |
* Test a class whose super couldn't be woven | |
*/ | |
@Test | |
public void testUnweavableSuperWithDefaultMethodInWrongPackage() throws Exception | |
{ | |
try{ | |
getProxyClass(ProxyTestClassUnweavableChildWithDefaultMethodWrongPackageParent.class); | |
fail(); | |
} catch (RuntimeException re) { | |
assertTrue(re.getCause() instanceof UnableToProxyException); | |
assertEquals(ProxyTestClassUnweavableSuperWithDefaultMethodWrongPackageParent | |
.class.getName(), ((UnableToProxyException)re.getCause()).getClassName()); | |
} | |
} | |
@Test | |
public void testInnerWithNoParentNoArgs() throws Exception { | |
//An inner class has no no-args (the parent gets added as an arg) so we can't | |
//get an instance | |
try{ | |
getProxyClass(ProxyTestClassUnweavableInnerChild.class); | |
fail(); | |
} catch (RuntimeException re) { | |
assertTrue(re.getCause() instanceof UnableToProxyException); | |
assertEquals(ProxyTestClassUnweavableInnerChild.class.getName(), | |
((UnableToProxyException)re.getCause()).getClassName()); | |
} | |
} | |
@Test(expected=NoSuchFieldException.class) | |
public void testNonSerializableClassHasNoGeneratedSerialVersionUID() throws Exception { | |
Class<?> woven = getProxyClass(getTestClass()); | |
woven.getDeclaredField("serialVersionUID"); | |
} | |
@Test | |
public void testSerialization() throws Exception { | |
ProxyTestClassSerializable in = new ProxyTestClassSerializable(); | |
in.value = 5; | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(baos); | |
oos.writeObject(in); | |
ProxyTestClassSerializable.checkDeserialization(baos.toByteArray(), 5); | |
Class<?> woven = getProxyClass(ProxyTestClassSerializable.class); | |
woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 5); | |
} | |
@Test | |
public void testInheritedSerialization() throws Exception { | |
ProxyTestClassSerializableChild in = new ProxyTestClassSerializableChild(); | |
in.value = 4; | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(baos); | |
oos.writeObject(in); | |
ProxyTestClassSerializable.checkDeserialization(baos.toByteArray(), 4); | |
Class<?> woven = getProxyClass(ProxyTestClassSerializable.class); | |
woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 4); | |
} | |
@Test | |
public void testInterfaceInheritedSerialization() throws Exception { | |
ProxyTestClassSerializableInterface in = new ProxyTestClassSerializableInterface(); | |
in.value = 3; | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(baos); | |
oos.writeObject(in); | |
ProxyTestClassSerializableInterface.checkDeserialization(baos.toByteArray(), 3); | |
Class<?> woven = getProxyClass(ProxyTestClassSerializableInterface.class); | |
woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 3); | |
} | |
@Test | |
public void testGeneratedSVUIDisSynthetic() throws Exception { | |
Class<?> woven = getProxyClass(ProxyTestClassSerializable.class); | |
assertTrue(woven.getDeclaredField("serialVersionUID").isSynthetic()); | |
woven = getProxyClass(ProxyTestClassSerializableWithSVUID.class); | |
assertFalse(woven.getDeclaredField("serialVersionUID").isSynthetic()); | |
} | |
/** | |
* This test covers a weird case on Mac VMs where we sometimes | |
* get a ClassCircularityError if a static initializer in a | |
* non-woven superclass references a subclass that's being | |
* woven, and gets triggered by the weaving process. Not known | |
* to fail on IBM or Sun/Oracle VMs | |
*/ | |
@Test | |
public void testSuperStaticInitOfChild() throws Exception { | |
Class<?> parent = weavingLoader.loadClass(ProxyTestClassStaticInitOfChildParent.class.getName()); | |
parent.getMethod("doStuff").invoke(null); | |
} | |
@Override | |
protected Object getProxyInstance(Class<?> proxyClass) { | |
try { | |
if(proxyClass.getName().equals(ProxyTestClassAbstract.class.getName())) { | |
Collection<Class<?>> coll = new ArrayList<Class<?>>(); | |
coll.add(proxyClass); | |
return new AsmProxyManager().createNewProxy(null, coll, new Callable() { | |
public Object call() throws Exception { | |
return null; | |
}}, null); | |
} | |
return proxyClass.newInstance(); | |
} catch (Exception e) { | |
return null; | |
} | |
} | |
@Override | |
protected Class<?> getProxyClass(Class<?> clazz) { | |
try { | |
return weavingLoader.loadClass(clazz.getName()); | |
} catch (ClassNotFoundException e) { | |
return null; | |
} | |
} | |
@Override | |
protected Object setDelegate(Object proxy, Callable<Object> dispatcher) { | |
return ((WovenProxy) proxy). | |
org_apache_aries_proxy_weaving_WovenProxy_createNewProxyInstance( | |
dispatcher, null); | |
} | |
@Override | |
protected Object getProxyInstance(Class<?> proxyClass, | |
InvocationListener listener) { | |
WovenProxy proxy = (WovenProxy) getProxyInstance(proxyClass); | |
proxy = proxy.org_apache_aries_proxy_weaving_WovenProxy_createNewProxyInstance( | |
new SingleInstanceDispatcher(proxy), listener); | |
return proxy; | |
} | |
protected Object getP3() { | |
return getProxyInstance(getProxyClass(getTestClass())); | |
} | |
/** | |
* This tests that the Synthesizer ran correctly and the packaged | |
* WovenProxy class has been modified with the synthetic modifier | |
*/ | |
@Test | |
public void testWovenProxyIsSynthetic(){ | |
assertTrue(WovenProxy.class.isSynthetic()); | |
} | |
/** | |
* This test checks that we can add interfaces to classes that don't implement | |
* them using dynamic subclassing. This is a little odd, but it came for | |
* free with support for proxying abstract classes! | |
* @throws Exception | |
*/ | |
@Test | |
public void testWovenClassPlusInterfaces() throws Exception { | |
Bundle b = (Bundle) Skeleton.newMock(new Class<?>[] {Bundle.class, ClassLoaderProxy.class}); | |
BundleWiring bw = (BundleWiring) Skeleton.newMock(BundleWiring.class); | |
Skeleton.getSkeleton(b).setReturnValue(new MethodCall( | |
ClassLoaderProxy.class, "getClassLoader"), weavingLoader); | |
Skeleton.getSkeleton(b).setReturnValue(new MethodCall( | |
ClassLoaderProxy.class, "adapt", BundleWiring.class), bw); | |
Object toCall = new AsmProxyManager().createDelegatingProxy(b, Arrays.asList( | |
getProxyClass(ProxyTestClassAbstract.class), Callable.class), new Callable() { | |
public Object call() throws Exception { | |
return weavingLoader.loadClass(ProxyTestClassChildOfAbstract.class.getName()).newInstance(); | |
} | |
}, null); | |
//Should proxy the abstract method on the class | |
Method m = getProxyClass(ProxyTestClassAbstract.class).getMethod("getMessage"); | |
assertEquals("Working", m.invoke(toCall)); | |
//Should be a callable too! | |
assertEquals("Callable Works too!", ((Callable)toCall).call()); | |
} | |
} | |