From 164e64bb15c99aa1db6f37d25ef5e2ce305cc4ce Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 17 Jul 2015 09:43:24 +0200 Subject: [PATCH] DATACMNS-715 - Introduced support for JTA 2.1 @Transactional. Refreshed the copy of AnnotationTransactionAttributeSource to pull in the JTA 2.1 support introduced in the Spring Framework class. Opened up a ticket [0] to improve Spring Framework to eventually be able to ditch the copies to prevent such scenarios in the future (the missing feature introduced that is). --- pom.xml | 7 ++ ...sactionalRepositoryProxyPostProcessor.java | 75 ++++++++++++++----- ...RepositoryProxyPostProcessorUnitTests.java | 31 ++++++-- 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index df2f361cd..a8b3db4d7 100644 --- a/pom.xml +++ b/pom.xml @@ -190,6 +190,13 @@ test + + javax.transaction + javax.transaction-api + 1.2 + test + + diff --git a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor.java b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor.java index 700d52c72..bff3e6faa 100644 --- a/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor.java +++ b/src/main/java/org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor.java @@ -34,6 +34,7 @@ import org.springframework.core.BridgeMethodResolver; import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.transaction.annotation.Ejb3TransactionAnnotationParser; +import org.springframework.transaction.annotation.JtaTransactionAnnotationParser; import org.springframework.transaction.annotation.SpringTransactionAnnotationParser; import org.springframework.transaction.annotation.TransactionAnnotationParser; import org.springframework.transaction.annotation.Transactional; @@ -110,10 +111,10 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr * working with transaction metadata in JDK 1.5+ annotation format. *

* This class reads Spring's JDK 1.5+ {@link Transactional} annotation and exposes corresponding transaction - * attributes to Spring's transaction infrastructure. Also supports EJB3's {@link javax.ejb.TransactionAttribute} - * annotation (if present). This class may also serve as base class for a custom TransactionAttributeSource, or get - * customized through {@link TransactionAnnotationParser} strategies. - * + * attributes to Spring's transaction infrastructure. Also supports JTA 1.2's and EJB3's + * {@link javax.ejb.TransactionAttribute} annotation (if present). This class may also serve as base class for a + * custom TransactionAttributeSource, or get customized through {@link TransactionAnnotationParser} strategies. + * * @author Colin Sampaleanu * @author Juergen Hoeller * @since 1.2 @@ -124,43 +125,50 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr * @see org.springframework.transaction.interceptor.TransactionInterceptor#setTransactionAttributeSource * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean#setTransactionAttributeSource */ - static class CustomAnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements - Serializable { + @SuppressWarnings("serial") + static class CustomAnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource + implements Serializable { + + private static final boolean jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", + CustomAnnotationTransactionAttributeSource.class.getClassLoader()); - private static final long serialVersionUID = 4841944452113159864L; private static final boolean ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute", CustomAnnotationTransactionAttributeSource.class.getClassLoader()); private final boolean publicMethodsOnly; + private final Set annotationParsers; /** - * Create a default AnnotationTransactionAttributeSource, supporting public methods that carry the - * Transactional annotation or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. + * Create a default CustomAnnotationTransactionAttributeSource, supporting public methods that carry the + * {@code Transactional} annotation or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. */ public CustomAnnotationTransactionAttributeSource() { this(true); } /** - * Create a custom AnnotationTransactionAttributeSource, supporting public methods that carry the - * Transactional annotation or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. + * Create a custom CustomAnnotationTransactionAttributeSource, supporting public methods that carry the + * {@code Transactional} annotation or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. * - * @param publicMethodsOnly whether to support public methods that carry the Transactional annotation - * only (typically for use with proxy-based AOP), or protected/private methods as well (typically used with + * @param publicMethodsOnly whether to support public methods that carry the {@code Transactional} annotation only + * (typically for use with proxy-based AOP), or protected/private methods as well (typically used with * AspectJ class weaving) */ public CustomAnnotationTransactionAttributeSource(boolean publicMethodsOnly) { this.publicMethodsOnly = publicMethodsOnly; this.annotationParsers = new LinkedHashSet(2); this.annotationParsers.add(new SpringTransactionAnnotationParser()); + if (jta12Present) { + this.annotationParsers.add(new JtaTransactionAnnotationParser()); + } if (ejb3Present) { this.annotationParsers.add(new Ejb3TransactionAnnotationParser()); } } /** - * Create a custom AnnotationTransactionAttributeSource. + * Create a custom CustomAnnotationTransactionAttributeSource. * * @param annotationParser the TransactionAnnotationParser to use */ @@ -171,7 +179,21 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr } /** - * Create a custom AnnotationTransactionAttributeSource. + * Create a custom CustomAnnotationTransactionAttributeSource. + * + * @param annotationParsers the TransactionAnnotationParsers to use + */ + public CustomAnnotationTransactionAttributeSource(TransactionAnnotationParser... annotationParsers) { + this.publicMethodsOnly = true; + Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified"); + Set parsers = new LinkedHashSet( + annotationParsers.length); + Collections.addAll(parsers, annotationParsers); + this.annotationParsers = parsers; + } + + /** + * Create a custom CustomAnnotationTransactionAttributeSource. * * @param annotationParsers the TransactionAnnotationParsers to use */ @@ -195,13 +217,12 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr * Determine the transaction attribute for the given method or class. *

* This implementation delegates to configured {@link TransactionAnnotationParser TransactionAnnotationParsers} for - * parsing known annotations into Spring's metadata attribute class. Returns null if it's not - * transactional. + * parsing known annotations into Spring's metadata attribute class. Returns {@code null} if it's not transactional. *

* Can be overridden to support custom annotations that carry transaction metadata. * * @param ae the annotated method or class - * @return TransactionAttribute the configured transaction attribute, or null if none was found + * @return TransactionAttribute the configured transaction attribute, or {@code null} if none was found */ protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) { for (TransactionAnnotationParser annotationParser : this.annotationParsers) { @@ -220,6 +241,24 @@ class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostPr protected boolean allowPublicMethodsOnly() { return this.publicMethodsOnly; } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof CustomAnnotationTransactionAttributeSource)) { + return false; + } + CustomAnnotationTransactionAttributeSource otherTas = (CustomAnnotationTransactionAttributeSource) other; + return (this.annotationParsers.equals(otherTas.annotationParsers) + && this.publicMethodsOnly == otherTas.publicMethodsOnly); + } + + @Override + public int hashCode() { + return this.annotationParsers.hashCode(); + } } /** diff --git a/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryProxyPostProcessorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryProxyPostProcessorUnitTests.java index 3f89ad5ac..194a1912f 100644 --- a/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryProxyPostProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/TransactionRepositoryProxyPostProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2015 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 @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.core.support; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; @@ -24,11 +25,11 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.ListableBeanFactory; @@ -38,6 +39,7 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor.CustomAnnotationTransactionAttributeSource; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionInterceptor; /** @@ -57,8 +59,8 @@ public class TransactionRepositoryProxyPostProcessorUnitTests { Map beans = new HashMap(); beans.put("foo", mock(PersistenceExceptionTranslator.class)); - when(beanFactory.getBeansOfType(eq(PersistenceExceptionTranslator.class), anyBoolean(), anyBoolean())).thenReturn( - beans); + when(beanFactory.getBeansOfType(eq(PersistenceExceptionTranslator.class), anyBoolean(), anyBoolean())) + .thenReturn(beans); } @Test(expected = IllegalArgumentException.class) @@ -78,7 +80,7 @@ public class TransactionRepositoryProxyPostProcessorUnitTests { true); postProcessor.postProcess(proxyFactory, repositoryInformation); - verify(proxyFactory).addAdvice(isA(TransactionInterceptor.class)); + verify(proxyFactory).addAdvice(Mockito.any(TransactionInterceptor.class)); } /** @@ -97,6 +99,20 @@ public class TransactionRepositoryProxyPostProcessorUnitTests { assertTransactionAttributeFor(SampleImplementationWithClassAnnotation.class); } + /** + * @see DATACMNS-732 + */ + @Test + public void considersJtaTransactional() throws Exception { + + Method method = SampleRepository.class.getMethod("methodWithJtaOneDotTwoAtTransactional"); + + TransactionAttributeSource attributeSource = new CustomAnnotationTransactionAttributeSource(); + TransactionAttribute attribute = attributeSource.getTransactionAttribute(method, SampleRepository.class); + + assertThat(attribute, is(notNullValue())); + } + private void assertTransactionAttributeFor(Class implementationClass) throws Exception { Method repositorySaveMethod = SampleRepository.class.getMethod("save", Sample.class); @@ -110,7 +126,7 @@ public class TransactionRepositoryProxyPostProcessorUnitTests { TransactionAttribute attribute = attributeSource.getTransactionAttribute(repositorySaveMethod, SampleImplementation.class); - assertThat(attribute, Matchers.is(Matchers.notNullValue())); + assertThat(attribute, is(notNullValue())); } static class Sample {} @@ -118,6 +134,9 @@ public class TransactionRepositoryProxyPostProcessorUnitTests { interface SampleRepository extends Repository { Sample save(Sample object); + + @javax.transaction.Transactional + void methodWithJtaOneDotTwoAtTransactional(); } static class SampleImplementation {