From c114c4fa14030120d4f9c95788138bc899e84bf9 Mon Sep 17 00:00:00 2001 From: "Lincoln Baxter, III" Date: Sat, 17 Aug 2013 10:45:18 -0400 Subject: [PATCH] Implemented FORGE-1109 - CLAC should convert exception types (preserving stack traces) wherever possible. --- .../mock/exceptions/ExceptionFactory.java | 21 +++++ .../mock/exceptions/MockException.java | 31 +++++++ ...lassLoaderAdapterEnumTranslationTest.java} | 6 +- .../ClassLoaderAdapterExceptionProxyTest.java | 92 +++++++++++++++++++ .../proxy/ClassLoaderAdapterCallback.java | 68 +++++++++++++- .../arquillian/ForgeDeploymentPackager.java | 33 +++---- 6 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/ExceptionFactory.java create mode 100644 proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/MockException.java rename proxy-tests/src/test/java/org/jboss/forge/classloader/{ClassLoaderAdapterEnumCollisionsTest.java => ClassLoaderAdapterEnumTranslationTest.java} (92%) create mode 100644 proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterExceptionProxyTest.java diff --git a/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/ExceptionFactory.java b/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/ExceptionFactory.java new file mode 100644 index 00000000..e5a01c23 --- /dev/null +++ b/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/ExceptionFactory.java @@ -0,0 +1,21 @@ +/* + * Copyright 2013 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Eclipse Public License version 1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.jboss.forge.classloader.mock.exceptions; + +/** + * @author Lincoln Baxter, III + * + */ +public class ExceptionFactory +{ + + public void throwException() throws MockException + { + throw new MockException("A message."); + } + +} diff --git a/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/MockException.java b/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/MockException.java new file mode 100644 index 00000000..89d6822a --- /dev/null +++ b/proxy-tests/src/main/java/org/jboss/forge/classloader/mock/exceptions/MockException.java @@ -0,0 +1,31 @@ +package org.jboss.forge.classloader.mock.exceptions; + +/** + * @author Lincoln Baxter, III + * + */ +public class MockException extends RuntimeException +{ + private static final long serialVersionUID = 5266075954460779189L; + + public MockException() + { + super(); + } + + public MockException(String message, Throwable cause) + { + super(message, cause); + } + + public MockException(String message) + { + super(message); + } + + public MockException(Throwable cause) + { + super(cause); + } + +} diff --git a/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumCollisionsTest.java b/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumTranslationTest.java similarity index 92% rename from proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumCollisionsTest.java rename to proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumTranslationTest.java index 546fcad8..5d2557c7 100644 --- a/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumCollisionsTest.java +++ b/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterEnumTranslationTest.java @@ -23,7 +23,7 @@ import org.junit.runner.RunWith; @RunWith(Arquillian.class) -public class ClassLoaderAdapterEnumCollisionsTest +public class ClassLoaderAdapterEnumTranslationTest { @Deployment(order = 3) public static ForgeArchive getDeployment() @@ -32,7 +32,7 @@ public static ForgeArchive getDeployment() .create(ForgeArchive.class) .addBeansXML() .addClasses(SimpleEnum.class, SimpleEnumFactory.class) - .addAsLocalServices(ClassLoaderAdapterEnumCollisionsTest.class); + .addAsLocalServices(ClassLoaderAdapterEnumTranslationTest.class); return archive; } @@ -52,7 +52,7 @@ public void testSimpleEnumCollision() throws Exception { AddonRegistry registry = LocalServices.getFurnace(getClass().getClassLoader()) .getAddonRegistry(); - ClassLoader thisLoader = ClassLoaderAdapterEnumCollisionsTest.class.getClassLoader(); + ClassLoader thisLoader = ClassLoaderAdapterEnumTranslationTest.class.getClassLoader(); ClassLoader dep1Loader = registry.getAddon(AddonId.from("dep", "1")).getClassLoader(); Class foreignType = dep1Loader.loadClass(SimpleEnumFactory.class.getName()); diff --git a/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterExceptionProxyTest.java b/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterExceptionProxyTest.java new file mode 100644 index 00000000..fdde1f48 --- /dev/null +++ b/proxy-tests/src/test/java/org/jboss/forge/classloader/ClassLoaderAdapterExceptionProxyTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Eclipse Public License version 1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.jboss.forge.classloader; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.forge.arquillian.archive.ForgeArchive; +import org.jboss.forge.arquillian.services.LocalServices; +import org.jboss.forge.classloader.mock.exceptions.ExceptionFactory; +import org.jboss.forge.classloader.mock.exceptions.MockException; +import org.jboss.forge.furnace.addons.AddonId; +import org.jboss.forge.furnace.addons.AddonRegistry; +import org.jboss.forge.furnace.proxy.ClassLoaderAdapterBuilder; +import org.jboss.forge.furnace.proxy.Proxies; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class ClassLoaderAdapterExceptionProxyTest +{ + @Deployment(order = 3) + public static ForgeArchive getDeployment() + { + ForgeArchive archive = ShrinkWrap + .create(ForgeArchive.class) + .addClasses(MockException.class, ExceptionFactory.class) + .addAsLocalServices(ClassLoaderAdapterExceptionProxyTest.class); + + return archive; + } + + @Deployment(name = "dep,1", testable = false, order = 2) + public static ForgeArchive getDeploymentDep1() + { + ForgeArchive archive = ShrinkWrap.create(ForgeArchive.class) + .addClasses(MockException.class, ExceptionFactory.class) + .addBeansXML(); + + return archive; + } + + @Test + public void testSharedImplementationTypeIncludedInProxy() throws Exception + { + AddonRegistry registry = LocalServices.getFurnace(getClass().getClassLoader()) + .getAddonRegistry(); + ClassLoader thisLoader = ClassLoaderAdapterExceptionProxyTest.class.getClassLoader(); + ClassLoader dep1Loader = registry.getAddon(AddonId.from("dep", "1")).getClassLoader(); + + Class foreignType = dep1Loader.loadClass(ExceptionFactory.class.getName()); + try + { + ExceptionFactory factory = (ExceptionFactory) foreignType.newInstance(); + + Assert.fail("Should have received a " + ClassCastException.class.getName() + " but got a real object [" + + factory + "]"); + } + catch (ClassCastException e) + { + } + catch (Exception e) + { + Assert.fail("Should have received a " + ClassCastException.class.getName() + " but was: " + e); + } + + Object delegate = foreignType.newInstance(); + ExceptionFactory enhancedFactory = (ExceptionFactory) ClassLoaderAdapterBuilder.callingLoader(thisLoader) + .delegateLoader(dep1Loader).enhance(delegate); + + Assert.assertTrue(Proxies.isForgeProxy(enhancedFactory)); + + try + { + enhancedFactory.throwException(); + } + catch (MockException e) + { + Assert.assertTrue(Proxies.isForgeProxy(e)); + } + catch (Exception e) + { + Assert.fail("Exception was not of proper type."); + } + } +} diff --git a/proxy/src/main/java/org/jboss/forge/furnace/proxy/ClassLoaderAdapterCallback.java b/proxy/src/main/java/org/jboss/forge/furnace/proxy/ClassLoaderAdapterCallback.java index a943905a..7e2542ac 100644 --- a/proxy/src/main/java/org/jboss/forge/furnace/proxy/ClassLoaderAdapterCallback.java +++ b/proxy/src/main/java/org/jboss/forge/furnace/proxy/ClassLoaderAdapterCallback.java @@ -87,8 +87,8 @@ public Object call() throws Exception catch (InvocationTargetException e) { if (e.getCause() instanceof Exception) - throw (Exception) e.getCause(); - throw e; + throw enhanceException(delegateMethod, (Exception) e.getCause()); + throw enhanceException(delegateMethod, e); } } @@ -197,6 +197,47 @@ private Object enhanceResult(final Method method, Object result) return result; } + private Exception enhanceException(final Method method, Exception exception) + { + try + { + if (exception != null) + { + Exception unwrappedException = Proxies.unwrap(exception); + Class unwrappedExceptionType = unwrappedException.getClass(); + + ClassLoader exceptionLoader = delegateLoader; + if (!ClassLoaders.containsClass(delegateLoader, unwrappedExceptionType)) + { + exceptionLoader = Proxies.unwrapProxyTypes(unwrappedExceptionType, callingLoader, delegateLoader, + unwrappedExceptionType.getClassLoader()).getClassLoader(); + if (exceptionLoader == null) + { + exceptionLoader = getClass().getClassLoader(); + } + } + + if (exceptionNeedsEnhancement(exception)) + { + Class[] exceptionHierarchy = ProxyTypeInspector.getCompatibleClassHierarchy(callingLoader, + Proxies.unwrapProxyTypes(exception.getClass(), callingLoader, delegateLoader, exceptionLoader)); + + if (!Modifier.isFinal(unwrappedExceptionType.getModifiers())) + { + exception = enhance(callingLoader, exceptionLoader, method, exception, exceptionHierarchy); + } + } + } + } + catch (Exception e) + { + throw new ContainerException("Failed to enhance exception type: [" + exception.getClass().getName() + + "] during proxied invocation of [" + method.getDeclaringClass().getName() + "." + method.getName() + + "]. \n\nOriginal exception was: ", e); + } + return exception; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private Object enhanceEnum(ClassLoader loader, Object instance) { @@ -238,6 +279,29 @@ else if (left.length == 0 || left[0].isInterface()) return left; } + @SuppressWarnings("unchecked") + private boolean exceptionNeedsEnhancement(Exception exception) + { + Class exceptionType = exception.getClass(); + Class unwrappedExceptionType = + (Class) Proxies.unwrap(exception).getClass(); + + if (Proxies.isPassthroughType(unwrappedExceptionType)) + { + return false; + } + + if (unwrappedExceptionType.getClassLoader() != null + && !exceptionType.getClassLoader().equals(callingLoader)) + { + if (ClassLoaders.containsClass(callingLoader, exceptionType)) + { + return false; + } + } + return true; + } + private boolean returnTypeNeedsEnhancement(Class methodReturnType, Object returnValue, Class unwrappedReturnValueType) { diff --git a/test-harness/arquillian/core/src/main/java/org/jboss/forge/arquillian/ForgeDeploymentPackager.java b/test-harness/arquillian/core/src/main/java/org/jboss/forge/arquillian/ForgeDeploymentPackager.java index 29875b89..cb448f2c 100644 --- a/test-harness/arquillian/core/src/main/java/org/jboss/forge/arquillian/ForgeDeploymentPackager.java +++ b/test-harness/arquillian/core/src/main/java/org/jboss/forge/arquillian/ForgeDeploymentPackager.java @@ -26,25 +26,20 @@ public Archive generateDeployment(TestDeployment testDeployment, Collection

> auxiliaryArchives = testDeployment.getAuxiliaryArchives(); for (Archive archive : auxiliaryArchives) { - Map content = archive.getContent(new Filter() - { - @Override - public boolean include(ArchivePath path) - { - return path.toString().matches("org/jboss/shrinkwrap/descriptor/api/.*"); - } - }); - - System.out.println(archive.toString(true)); - - System.out.println(archive.getName() + ": " + content.size()); - - for (Entry entry : content.entrySet()) - { - ArchivePath key = entry.getKey(); - System.out.println("Deleting from " + archive.getName() + ": " + key); - archive.delete(key); - } +// Map content = archive.getContent(new Filter() +// { +// @Override +// public boolean include(ArchivePath path) +// { +// return path.toString().matches("org/jboss/shrinkwrap/descriptor/api/.*"); +// } +// }); +// +// for (Entry entry : content.entrySet()) +// { +// ArchivePath key = entry.getKey(); +// archive.delete(key); +// } deployment.addAsLibrary(archive); }