diff --git a/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisorTests.java b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisorTests.java new file mode 100644 index 00000000000..2c6fb3d81dc --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisorTests.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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. + */ + +package org.springframework.dao.annotation; + +import javax.persistence.PersistenceException; + +import junit.framework.TestCase; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.DataAccessUtilsTests.MapPersistenceExceptionTranslator; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.stereotype.Repository; + +/** + * Tests for PersistenceExceptionTranslationAdvisor's exception translation, as applied by + * PersistenceExceptionTranslationPostProcessor. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class PersistenceExceptionTranslationAdvisorTests extends TestCase { + + private RuntimeException doNotTranslate = new RuntimeException(); + + private PersistenceException persistenceException1 = new PersistenceException(); + + protected RepositoryInterface createProxy(RepositoryInterfaceImpl target) { + MapPersistenceExceptionTranslator mpet = new MapPersistenceExceptionTranslator(); + mpet.addTranslation(persistenceException1, new InvalidDataAccessApiUsageException("", persistenceException1)); + ProxyFactory pf = new ProxyFactory(target); + pf.addInterface(RepositoryInterface.class); + addPersistenceExceptionTranslation(pf, mpet); + return (RepositoryInterface) pf.getProxy(); + } + + protected void addPersistenceExceptionTranslation(ProxyFactory pf, PersistenceExceptionTranslator pet) { + pf.addAdvisor(new PersistenceExceptionTranslationAdvisor(pet, Repository.class)); + } + + public void testNoTranslationNeeded() { + RepositoryInterfaceImpl target = new RepositoryInterfaceImpl(); + RepositoryInterface ri = createProxy(target); + + ri.noThrowsClause(); + ri.throwsPersistenceException(); + + target.setBehavior(persistenceException1); + try { + ri.noThrowsClause(); + fail(); + } + catch (RuntimeException ex) { + assertSame(persistenceException1, ex); + } + try { + ri.throwsPersistenceException(); + fail(); + } + catch (RuntimeException ex) { + assertSame(persistenceException1, ex); + } + } + + public void testTranslationNotNeededForTheseExceptions() { + RepositoryInterfaceImpl target = new StereotypedRepositoryInterfaceImpl(); + RepositoryInterface ri = createProxy(target); + + ri.noThrowsClause(); + ri.throwsPersistenceException(); + + target.setBehavior(doNotTranslate); + try { + ri.noThrowsClause(); + fail(); + } + catch (RuntimeException ex) { + assertSame(doNotTranslate, ex); + } + try { + ri.throwsPersistenceException(); + fail(); + } + catch (RuntimeException ex) { + assertSame(doNotTranslate, ex); + } + } + + public void testTranslationNeededForTheseExceptions() { + doTestTranslationNeededForTheseExceptions(new StereotypedRepositoryInterfaceImpl()); + } + + public void testTranslationNeededForTheseExceptionsOnSuperclass() { + doTestTranslationNeededForTheseExceptions(new MyStereotypedRepositoryInterfaceImpl()); + } + + public void testTranslationNeededForTheseExceptionsOnInterface() { + doTestTranslationNeededForTheseExceptions(new MyInterfaceStereotypedRepositoryInterfaceImpl()); + } + + public void testTranslationNeededForTheseExceptionsOnInheritedInterface() { + doTestTranslationNeededForTheseExceptions(new MyInterfaceInheritedStereotypedRepositoryInterfaceImpl()); + } + + private void doTestTranslationNeededForTheseExceptions(RepositoryInterfaceImpl target) { + RepositoryInterface ri = createProxy(target); + + target.setBehavior(persistenceException1); + try { + ri.noThrowsClause(); + fail(); + } + catch (DataAccessException ex) { + // Expected + assertSame(persistenceException1, ex.getCause()); + } + catch (PersistenceException ex) { + fail("Should have been translated"); + } + + try { + ri.throwsPersistenceException(); + fail(); + } + catch (PersistenceException ex) { + assertSame(persistenceException1, ex); + } + } + + public interface RepositoryInterface { + + void noThrowsClause(); + + void throwsPersistenceException() throws PersistenceException; + } + + public static class RepositoryInterfaceImpl implements RepositoryInterface { + + private RuntimeException runtimeException; + + public void setBehavior(RuntimeException rex) { + this.runtimeException = rex; + } + + public void noThrowsClause() { + if (runtimeException != null) { + throw runtimeException; + } + } + + public void throwsPersistenceException() throws PersistenceException { + if (runtimeException != null) { + throw runtimeException; + } + } + } + + @Repository + public static class StereotypedRepositoryInterfaceImpl extends RepositoryInterfaceImpl { + // Extends above class just to add repository annotation + } + + public static class MyStereotypedRepositoryInterfaceImpl extends StereotypedRepositoryInterfaceImpl { + + } + + @Repository + public interface StereotypedInterface { + + } + + public static class MyInterfaceStereotypedRepositoryInterfaceImpl extends RepositoryInterfaceImpl + implements StereotypedInterface { + + } + + public interface StereotypedInheritingInterface extends StereotypedInterface { + + } + + public static class MyInterfaceInheritedStereotypedRepositoryInterfaceImpl extends RepositoryInterfaceImpl + implements StereotypedInheritingInterface { + + } + +} diff --git a/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java new file mode 100644 index 00000000000..cfa7d6ce824 --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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. + */ + +package org.springframework.dao.annotation; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.stereotype.Repository; + +/** + * Tests for standalone usage of a PersistenceExceptionTranslationInterceptor, as explicit advice bean in a BeanFactory + * rather than applied as part of a PersistenceExceptionTranslationAdvisor. + * + * @author Juergen Hoeller + */ +public class PersistenceExceptionTranslationInterceptorTests extends PersistenceExceptionTranslationAdvisorTests { + + @Override + protected void addPersistenceExceptionTranslation(ProxyFactory pf, PersistenceExceptionTranslator pet) { + if (AnnotationUtils.findAnnotation(pf.getTargetClass(), Repository.class) != null) { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("peti", new RootBeanDefinition(PersistenceExceptionTranslationInterceptor.class)); + bf.registerSingleton("pet", pet); + pf.addAdvice((PersistenceExceptionTranslationInterceptor) bf.getBean("peti")); + } + } + +} diff --git a/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java new file mode 100644 index 00000000000..3b759466a57 --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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. + */ + +package org.springframework.dao.annotation; + +import junit.framework.TestCase; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisorTests.RepositoryInterface; +import org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisorTests.RepositoryInterfaceImpl; +import org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisorTests.StereotypedRepositoryInterfaceImpl; +import org.springframework.dao.support.ChainedPersistenceExceptionTranslator; +import org.springframework.stereotype.Repository; + +/** + * Unit tests for PersistenceExceptionTranslationPostProcessor. Does not test translation; there are separate unit tests + * for the Spring AOP Advisor. Just checks whether proxying occurs correctly, as a unit test should. + * + * @author Rod Johnson + */ +public class PersistenceExceptionTranslationPostProcessorTests extends TestCase { + + public void testFailsWithNoPersistenceExceptionTranslators() { + GenericApplicationContext gac = new GenericApplicationContext(); + gac.registerBeanDefinition("translator", + new RootBeanDefinition(PersistenceExceptionTranslationPostProcessor.class)); + gac.registerBeanDefinition("proxied", new RootBeanDefinition(StereotypedRepositoryInterfaceImpl.class)); + try { + gac.refresh(); + fail("Should fail with no translators"); + } + catch (BeansException ex) { + // Ok + } + } + + public void testProxiesCorrectly() { + GenericApplicationContext gac = new GenericApplicationContext(); + gac.registerBeanDefinition("translator", + new RootBeanDefinition(PersistenceExceptionTranslationPostProcessor.class)); + gac.registerBeanDefinition("notProxied", new RootBeanDefinition(RepositoryInterfaceImpl.class)); + gac.registerBeanDefinition("proxied", new RootBeanDefinition(StereotypedRepositoryInterfaceImpl.class)); + gac.registerBeanDefinition("classProxied", new RootBeanDefinition(RepositoryWithoutInterface.class)); + gac.registerBeanDefinition("classProxiedAndAdvised", + new RootBeanDefinition(RepositoryWithoutInterfaceAndOtherwiseAdvised.class)); + gac.registerBeanDefinition("chainedTranslator", + new RootBeanDefinition(ChainedPersistenceExceptionTranslator.class)); + gac.registerBeanDefinition("proxyCreator", + BeanDefinitionBuilder.rootBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class). + addPropertyValue("order", 50).getBeanDefinition()); + gac.registerBeanDefinition("logger", new RootBeanDefinition(LogAllAspect.class)); + gac.refresh(); + + RepositoryInterface shouldNotBeProxied = (RepositoryInterface) gac.getBean("notProxied"); + assertFalse(AopUtils.isAopProxy(shouldNotBeProxied)); + RepositoryInterface shouldBeProxied = (RepositoryInterface) gac.getBean("proxied"); + assertTrue(AopUtils.isAopProxy(shouldBeProxied)); + RepositoryWithoutInterface rwi = (RepositoryWithoutInterface) gac.getBean("classProxied"); + assertTrue(AopUtils.isAopProxy(rwi)); + checkWillTranslateExceptions(rwi); + + Additional rwi2 = (Additional) gac.getBean("classProxiedAndAdvised"); + assertTrue(AopUtils.isAopProxy(rwi2)); + rwi2.additionalMethod(); + checkWillTranslateExceptions(rwi2); + } + + protected void checkWillTranslateExceptions(Object o) { + assertTrue(o instanceof Advised); + Advised a = (Advised) o; + for (Advisor advisor : a.getAdvisors()) { + if (advisor instanceof PersistenceExceptionTranslationAdvisor) { + return; + } + } + fail("No translation"); + } + + @Repository + public static class RepositoryWithoutInterface { + + public void nameDoesntMatter() { + } + } + + public interface Additional { + + void additionalMethod(); + } + + public static class RepositoryWithoutInterfaceAndOtherwiseAdvised extends StereotypedRepositoryInterfaceImpl + implements Additional { + + public void additionalMethod() { + } + } + + @Aspect + public static class LogAllAspect { + + //@Before("execution(* *())") + @Before("execution(void *.additionalMethod())") + public void log(JoinPoint jp) { + System.out.println("Before " + jp.getSignature().getName()); + } + } + +}