/*
* 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.transaction.aspectj;
import java.lang.reflect.Method;
import junit.framework.AssertionFailedError;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import org.springframework.transaction.CallCountingTransactionManager;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.interceptor.TransactionAttribute;
/**
* @author Rod Johnson
*/
public class TransactionAspectTests extends AbstractDependencyInjectionSpringContextTests {
private TransactionAspectSupport transactionAspect;
private CallCountingTransactionManager txManager;
private TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface;
private ClassWithProtectedAnnotatedMember beanWithAnnotatedProtectedMethod;
private ClassWithPrivateAnnotatedMember beanWithAnnotatedPrivateMethod;
private MethodAnnotationOnClassWithNoInterface methodAnnotationOnly = new MethodAnnotationOnClassWithNoInterface();
public void setAnnotationOnlyOnClassWithNoInterface(
TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface) {
this.annotationOnlyOnClassWithNoInterface = annotationOnlyOnClassWithNoInterface;
}
public void setClassWithAnnotatedProtectedMethod(ClassWithProtectedAnnotatedMember aBean) {
this.beanWithAnnotatedProtectedMethod = aBean;
}
public void setClassWithAnnotatedPrivateMethod(ClassWithPrivateAnnotatedMember aBean) {
this.beanWithAnnotatedPrivateMethod = aBean;
}
public void setTransactionAspect(TransactionAspectSupport transactionAspect) {
this.transactionAspect = transactionAspect;
this.txManager = (CallCountingTransactionManager) transactionAspect.getTransactionManager();
}
public TransactionAspectSupport getTransactionAspect() {
return this.transactionAspect;
}
@Override
protected String getConfigPath() {
return "txtests.xml";
}
public void testCommitOnAnnotatedClass() throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
annotationOnlyOnClassWithNoInterface.echo(null);
assertEquals(1, txManager.commits);
}
public void testCommitOnAnnotatedProtectedMethod() throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
beanWithAnnotatedProtectedMethod.doInTransaction();
assertEquals(1, txManager.commits);
}
public void testCommitOnAnnotatedPrivateMethod() throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
beanWithAnnotatedPrivateMethod.doSomething();
assertEquals(1, txManager.commits);
}
public void testNoCommitOnNonAnnotatedNonPublicMethodInTransactionalType() throws Throwable {
txManager.clear();
assertEquals(0,txManager.begun);
annotationOnlyOnClassWithNoInterface.nonTransactionalMethod();
assertEquals(0,txManager.begun);
}
public void testCommitOnAnnotatedMethod() throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
methodAnnotationOnly.echo(null);
assertEquals(1, txManager.commits);
}
public static class NotTransactional {
public void noop() {
}
}
public void testNotTransactional() throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
new NotTransactional().noop();
assertEquals(0, txManager.begun);
}
public void testDefaultCommitOnAnnotatedClass() throws Throwable {
testRollback(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return annotationOnlyOnClassWithNoInterface.echo(new Exception());
}
}, false);
}
public void testDefaultRollbackOnAnnotatedClass() throws Throwable {
testRollback(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return annotationOnlyOnClassWithNoInterface.echo(new RuntimeException());
}
}, true);
}
public static class SubclassOfClassWithTransactionalAnnotation extends TransactionalAnnotationOnlyOnClassWithNoInterface {
}
public void testDefaultCommitOnSubclassOfAnnotatedClass() throws Throwable {
testRollback(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return new SubclassOfClassWithTransactionalAnnotation().echo(new Exception());
}
}, false);
}
public static class SubclassOfClassWithTransactionalMethodAnnotation extends MethodAnnotationOnClassWithNoInterface {
}
public void testDefaultCommitOnSubclassOfClassWithTransactionalMethodAnnotated() throws Throwable {
testRollback(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return new SubclassOfClassWithTransactionalMethodAnnotation().echo(new Exception());
}
}, false);
}
public static class ImplementsAnnotatedInterface implements ITransactional {
public Object echo(Throwable t) throws Throwable {
if (t != null) {
throw t;
}
return t;
}
}
public void testDefaultCommitOnImplementationOfAnnotatedInterface() throws Throwable {
// testRollback(new TransactionOperationCallback() {
// public Object performTransactionalOperation() throws Throwable {
// return new ImplementsAnnotatedInterface().echo(new Exception());
// }
// }, false);
final Exception ex = new Exception();
testNotTransactional(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return new ImplementsAnnotatedInterface().echo(ex);
}
}, ex);
}
/**
* Note: resolution does not occur. Thus we can't make a class transactional if
* it implements a transactionally annotated interface. This behaviour could only
* be changed in AbstractFallbackTransactionAttributeSource in Spring proper.
* @throws SecurityException
* @throws NoSuchMethodException
*/
public void testDoesNotResolveTxAnnotationOnMethodFromClassImplementingAnnotatedInterface() throws SecurityException, NoSuchMethodException {
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
Method m = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class);
TransactionAttribute ta = atas.getTransactionAttribute(m, ImplementsAnnotatedInterface.class);
assertNull(ta);
}
public void testDefaultRollbackOnImplementationOfAnnotatedInterface() throws Throwable {
// testRollback(new TransactionOperationCallback() {
// public Object performTransactionalOperation() throws Throwable {
// return new ImplementsAnnotatedInterface().echo(new RuntimeException());
// }
// }, true);
final Exception rollbackProvokingException = new RuntimeException();
testNotTransactional(new TransactionOperationCallback() {
public Object performTransactionalOperation() throws Throwable {
return new ImplementsAnnotatedInterface().echo(rollbackProvokingException);
}
}, rollbackProvokingException);
}
protected void testRollback(TransactionOperationCallback toc, boolean rollback) throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
try {
toc.performTransactionalOperation();
assertEquals(1, txManager.commits);
}
catch (Throwable caught) {
if (caught instanceof AssertionFailedError) {
return;
}
}
if (rollback) {
assertEquals(1, txManager.rollbacks);
}
assertEquals(1, txManager.begun);
}
protected void testNotTransactional(TransactionOperationCallback toc, Throwable expected) throws Throwable {
txManager.clear();
assertEquals(0, txManager.begun);
try {
toc.performTransactionalOperation();
}
catch (Throwable t) {
if (expected == null) {
fail("Expected " + expected);
}
assertSame(expected, t);
}
finally {
assertEquals(0, txManager.begun);
}
}
private interface TransactionOperationCallback {
Object performTransactionalOperation() throws Throwable;
}
}