diff --git a/org.springframework.transaction/build.xml b/org.springframework.transaction/build.xml new file mode 100644 index 00000000000..0e16faffdb8 --- /dev/null +++ b/org.springframework.transaction/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/org.springframework.transaction/ivy.xml b/org.springframework.transaction/ivy.xml new file mode 100644 index 00000000000..49dd821b24e --- /dev/null +++ b/org.springframework.transaction/ivy.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.transaction/pom.xml b/org.springframework.transaction/pom.xml new file mode 100644 index 00000000000..b4cee14adcb --- /dev/null +++ b/org.springframework.transaction/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + org.springframework + org.springframework.core + jar + Spring Core Abstractions and Utilities + 3.0.0.M1 + + + com.springsource.repository.bundles.external + SpringSource Enterprise Bundle Repository - External Bundle Releases + http://repository.springsource.com/maven/bundles/external + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + + + org.apache.commons + com.springsource.org.apache.commons.logging + 1.1.1 + + + org.apache.log4j + com.springsource.org.apache.log4j + 1.2.15 + true + + + org.apache.commons + com.springsource.org.apache.commons.collections + 3.2.0 + true + + + org.aspectj + com.springsource.org.aspectj.weaver + 1.6.2.RELEASE + true + + + org.objectweb.asm + com.springsource.org.objectweb.asm.commons + 2.2.3 + true + + + \ No newline at end of file diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/CannotAcquireLockException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/CannotAcquireLockException.java new file mode 100644 index 00000000000..53079733181 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/CannotAcquireLockException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on failure to aquire a lock during an update, + * for example during a "select for update" statement. + * + * @author Rod Johnson + */ +public class CannotAcquireLockException extends PessimisticLockingFailureException { + + /** + * Constructor for CannotAcquireLockException. + * @param msg the detail message + */ + public CannotAcquireLockException(String msg) { + super(msg); + } + + /** + * Constructor for CannotAcquireLockException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public CannotAcquireLockException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java new file mode 100644 index 00000000000..fcfd3ebc289 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on failure to complete a transaction in serialized mode + * due to update conflicts. + * + * @author Rod Johnson + */ +public class CannotSerializeTransactionException extends PessimisticLockingFailureException { + + /** + * Constructor for CannotSerializeTransactionException. + * @param msg the detail message + */ + public CannotSerializeTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for CannotSerializeTransactionException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public CannotSerializeTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java new file mode 100644 index 00000000000..bff2b23978e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when we couldn't cleanup after a data access operation, + * but the actual operation went OK. + * + *

For example, this exception or a subclass might be thrown if a JDBC + * Connection couldn't be closed after it had been used successfully. + * + *

Note that data access code might perform resources cleanup in a + * finally block and therefore log cleanup failure rather than rethrow it, + * to keep the original data access exception, if any. + * + * @author Rod Johnson + */ +public class CleanupFailureDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for CleanupFailureDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying data access API, + * such as JDBC + */ + public CleanupFailureDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/ConcurrencyFailureException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/ConcurrencyFailureException.java new file mode 100644 index 00000000000..f2cbb67030b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/ConcurrencyFailureException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on concurrency failure. + * + *

This exception should be subclassed to indicate the type of failure: + * optimistic locking, failure to acquire lock, etc. + * + * @author Thomas Risberg + * @since 1.1 + * @see OptimisticLockingFailureException + * @see PessimisticLockingFailureException + * @see CannotAcquireLockException + * @see DeadlockLoserDataAccessException + */ +public class ConcurrencyFailureException extends TransientDataAccessException { + + /** + * Constructor for ConcurrencyFailureException. + * @param msg the detail message + */ + public ConcurrencyFailureException(String msg) { + super(msg); + } + + /** + * Constructor for ConcurrencyFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public ConcurrencyFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessException.java new file mode 100644 index 00000000000..5c04df5d632 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessException.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2006 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; + +import org.springframework.core.NestedRuntimeException; + +/** + * Root of the hierarchy of data access exceptions discussed in + * Expert One-On-One J2EE Design and Development. + * Please see Chapter 9 of this book for detailed discussion of the + * motivation for this package. + * + *

This exception hierarchy aims to let user code find and handle the + * kind of error encountered without knowing the details of the particular + * data access API in use (e.g. JDBC). Thus it is possible to react to an + * optimistic locking failure without knowing that JDBC is being used. + * + *

As this class is a runtime exception, there is no need for user code + * to catch it or subclasses if any error is to be considered fatal + * (the usual case). + * + * @author Rod Johnson + */ +public abstract class DataAccessException extends NestedRuntimeException { + + /** + * Constructor for DataAccessException. + * @param msg the detail message + */ + public DataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public DataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java new file mode 100644 index 00000000000..87c1f759294 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Data access exception thrown when a resource fails completely: + * for example, if we can't connect to a database using JDBC. + * + * @author Rod Johnson + * @author Thomas Risberg + */ +public class DataAccessResourceFailureException extends NonTransientDataAccessResourceException { + + /** + * Constructor for DataAccessResourceFailureException. + * @param msg the detail message + */ + public DataAccessResourceFailureException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessResourceFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataAccessResourceFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/DataIntegrityViolationException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/DataIntegrityViolationException.java new file mode 100644 index 00000000000..ce99a6980c8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/DataIntegrityViolationException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when an attempt to insert or update data + * results in violation of an integrity constraint. Note that this + * is not purely a relational concept; unique primary keys are + * required by most database types. + * + * @author Rod Johnson + */ +public class DataIntegrityViolationException extends NonTransientDataAccessException { + + /** + * Constructor for DataIntegrityViolationException. + * @param msg the detail message + */ + public DataIntegrityViolationException(String msg) { + super(msg); + } + + /** + * Constructor for DataIntegrityViolationException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataIntegrityViolationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/DataRetrievalFailureException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/DataRetrievalFailureException.java new file mode 100644 index 00000000000..68467df26a2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/DataRetrievalFailureException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown if certain expected data could not be retrieved, e.g. + * when looking up specific data via a known identifier. This exception + * will be thrown either by O/R mapping tools or by DAO implementations. + * + * @author Juergen Hoeller + * @since 13.10.2003 + */ +public class DataRetrievalFailureException extends NonTransientDataAccessException { + + /** + * Constructor for DataRetrievalFailureException. + * @param msg the detail message + */ + public DataRetrievalFailureException(String msg) { + super(msg); + } + + /** + * Constructor for DataRetrievalFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DataRetrievalFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java new file mode 100644 index 00000000000..6ba9b553625 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2006 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; + +/** + * Generic exception thrown when the current process was + * a deadlock loser, and its transaction rolled back. + * + * @author Rod Johnson + */ +public class DeadlockLoserDataAccessException extends PessimisticLockingFailureException { + + /** + * Constructor for DeadlockLoserDataAccessException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public DeadlockLoserDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java new file mode 100644 index 00000000000..f10e84e3d98 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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; + +/** + * Data access exception thrown when a result was expected to have at least + * one row (or element) but zero rows (or elements) were actually returned. + * + * @author Juergen Hoeller + * @since 2.0 + * @see IncorrectResultSizeDataAccessException + */ +public class EmptyResultDataAccessException extends IncorrectResultSizeDataAccessException { + + /** + * Constructor for EmptyResultDataAccessException. + * @param expectedSize the expected result size + */ + public EmptyResultDataAccessException(int expectedSize) { + super(expectedSize, 0); + } + + /** + * Constructor for EmptyResultDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + */ + public EmptyResultDataAccessException(String msg, int expectedSize) { + super(msg, expectedSize, 0); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java new file mode 100644 index 00000000000..8452d4eecac --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2006 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; + +/** + * Data access exception thrown when a result was not of the expected size, + * for example when expecting a single row but getting 0 or more than 1 rows. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see EmptyResultDataAccessException + */ +public class IncorrectResultSizeDataAccessException extends DataRetrievalFailureException { + + private int expectedSize; + + private int actualSize; + + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param expectedSize the expected result size + */ + public IncorrectResultSizeDataAccessException(int expectedSize) { + super("Incorrect result size: expected " + expectedSize); + this.expectedSize = expectedSize; + this.actualSize = -1; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + */ + public IncorrectResultSizeDataAccessException(int expectedSize, int actualSize) { + super("Incorrect result size: expected " + expectedSize + ", actual " + actualSize); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize) { + super(msg); + this.expectedSize = expectedSize; + this.actualSize = -1; + } + + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize, int actualSize) { + super(msg); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + + + /** + * Return the expected result size. + */ + public int getExpectedSize() { + return expectedSize; + } + + /** + * Return the actual result size (or -1 if unknown). + */ + public int getActualSize() { + return actualSize; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java new file mode 100644 index 00000000000..d4a2d1957be --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/IncorrectUpdateSemanticsDataAccessException.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2006 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; + +/** + * Data access exception thrown when something unintended appears to have + * happened with an update, but the transaction hasn't already been rolled back. + * Thrown, for example, when we wanted to update 1 row in an RDBMS but actually + * updated 3. + * + * @author Rod Johnson + */ +public class IncorrectUpdateSemanticsDataAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for IncorrectUpdateSemanticsDataAccessException. + * @param msg the detail message + */ + public IncorrectUpdateSemanticsDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for IncorrectUpdateSemanticsDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying API, such as JDBC + */ + public IncorrectUpdateSemanticsDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Return whether data was updated. + * If this method returns false, there's nothing to roll back. + *

The default implementation always returns true. + * This can be overridden in subclasses. + */ + public boolean wasDataUpdated() { + return true; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessApiUsageException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessApiUsageException.java new file mode 100644 index 00000000000..9a442632cf9 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessApiUsageException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on incorrect usage of the API, such as failing to + * "compile" a query object that needed compilation before execution. + * + *

This represents a problem in our Java data access framework, + * not the underlying data access infrastructure. + * + * @author Rod Johnson + */ +public class InvalidDataAccessApiUsageException extends NonTransientDataAccessException { + + /** + * Constructor for InvalidDataAccessApiUsageException. + * @param msg the detail message + */ + public InvalidDataAccessApiUsageException(String msg) { + super(msg); + } + + /** + * Constructor for InvalidDataAccessApiUsageException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public InvalidDataAccessApiUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessResourceUsageException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessResourceUsageException.java new file mode 100644 index 00000000000..0537b27dbed --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/InvalidDataAccessResourceUsageException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Root for exceptions thrown when we use a data access resource incorrectly. + * Thrown for example on specifying bad SQL when using a RDBMS. + * Resource-specific subclasses are supplied by concrete data access packages. + * + * @author Rod Johnson + */ +public class InvalidDataAccessResourceUsageException extends NonTransientDataAccessException { + + /** + * Constructor for InvalidDataAccessResourceUsageException. + * @param msg the detail message + */ + public InvalidDataAccessResourceUsageException(String msg) { + super(msg); + } + + /** + * Constructor for InvalidDataAccessResourceUsageException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public InvalidDataAccessResourceUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessException.java new file mode 100644 index 00000000000..480b7701fa8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2008 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; + +/** + * Root of the hierarchy of data access exceptions that are considered non-transient - + * where a retry of the same operation would fail unless the cause of the Exception + * is corrected. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLNonTransientException + */ +public abstract class NonTransientDataAccessException extends DataAccessException { + + /** + * Constructor for NonTransientDataAccessException. + * @param msg the detail message + */ + public NonTransientDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for NonTransientDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public NonTransientDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessResourceException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessResourceException.java new file mode 100644 index 00000000000..45eb06f9e27 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/NonTransientDataAccessResourceException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2008 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; + +/** + * Data access exception thrown when a resource fails completely and the failure is permanent. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLNonTransientConnectionException + */ +public class NonTransientDataAccessResourceException extends NonTransientDataAccessException { + + /** + * Constructor for NonTransientDataAccessResourceException. + * @param msg the detail message + */ + public NonTransientDataAccessResourceException(String msg) { + super(msg); + } + + /** + * Constructor for NonTransientDataAccessResourceException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public NonTransientDataAccessResourceException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/OptimisticLockingFailureException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/OptimisticLockingFailureException.java new file mode 100644 index 00000000000..ae036293ddb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/OptimisticLockingFailureException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on an optimistic locking violation. + * + *

This exception will be thrown either by O/R mapping tools + * or by custom DAO implementations. Optimistic locking failure + * is typically not detected by the database itself. + * + * @author Rod Johnson + * @see PessimisticLockingFailureException + */ +public class OptimisticLockingFailureException extends ConcurrencyFailureException { + + /** + * Constructor for OptimisticLockingFailureException. + * @param msg the detail message + */ + public OptimisticLockingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for OptimisticLockingFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public OptimisticLockingFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/PermissionDeniedDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/PermissionDeniedDataAccessException.java new file mode 100644 index 00000000000..71529ae477b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/PermissionDeniedDataAccessException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when the underlying resource denied a permission + * to access a specific element, such as a specific database table. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class PermissionDeniedDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for PermissionDeniedDataAccessException. + * @param msg the detail message + * @param cause the root cause from the underlying data access API, + * such as JDBC + */ + public PermissionDeniedDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java new file mode 100644 index 00000000000..f022b3dc2e6 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on a pessimistic locking violation. + * Thrown by Spring's SQLException translation mechanism + * if a corresponding database error is encountered. + * + *

Serves as superclass for more specific exceptions, like + * CannotAcquireLockException and DeadlockLoserDataAccessException. + * + * @author Thomas Risberg + * @since 1.2 + * @see CannotAcquireLockException + * @see DeadlockLoserDataAccessException + * @see OptimisticLockingFailureException + */ +public class PessimisticLockingFailureException extends ConcurrencyFailureException { + + /** + * Constructor for PessimisticLockingFailureException. + * @param msg the detail message + */ + public PessimisticLockingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for PessimisticLockingFailureException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public PessimisticLockingFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/RecoverableDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/RecoverableDataAccessException.java new file mode 100644 index 00000000000..973feb2fd4e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/RecoverableDataAccessException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2008 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; + +/** + * Data access exception thrown when a previously failed operation might be able + * to succeed if the application performs some recovery steps and retries the entire + * transaction or in the case of a distributed transaction, the transaction branch. + * At a minimum, the recovery operation must include closing the current connection + * and getting a new connection. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLRecoverableException + */ +public class RecoverableDataAccessException extends DataAccessException { + + /** + * Constructor for RecoverableDataAccessException. + * @param msg the detail message + */ + public RecoverableDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for RecoverableDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public RecoverableDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessException.java new file mode 100644 index 00000000000..9c3b2397ee0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2008 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; + +/** + * Root of the hierarchy of data access exceptions that are considered transient - + * where a previously failed operation might be able to succeed when the operation + * is retried without any intervention by application-level functionality. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLTransientException + */ +public abstract class TransientDataAccessException extends DataAccessException { + + /** + * Constructor for TransientDataAccessException. + * @param msg the detail message + */ + public TransientDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for TransientDataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public TransientDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessResourceException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessResourceException.java new file mode 100644 index 00000000000..c2c37d004f3 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/TransientDataAccessResourceException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2008 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; + +/** + * Data access exception thrown when a resource fails temporarily + * and the operation can be retried. + * + * @author Thomas Risberg + * @since 2.5 + * @see java.sql.SQLTransientConnectionException + */ +public class TransientDataAccessResourceException extends TransientDataAccessException { + + /** + * Constructor for TransientDataAccessResourceException. + * @param msg the detail message + */ + public TransientDataAccessResourceException(String msg) { + super(msg); + } + + /** + * Constructor for TransientDataAccessResourceException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public TransientDataAccessResourceException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/TypeMismatchDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/TypeMismatchDataAccessException.java new file mode 100644 index 00000000000..073bf188a71 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/TypeMismatchDataAccessException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on mismatch between Java type and database type: + * for example on an attempt to set an object of the wrong type + * in an RDBMS column. + * + * @author Rod Johnson + */ +public class TypeMismatchDataAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for TypeMismatchDataAccessException. + * @param msg the detail message + */ + public TypeMismatchDataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for TypeMismatchDataAccessException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public TypeMismatchDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/UncategorizedDataAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/dao/UncategorizedDataAccessException.java new file mode 100644 index 00000000000..9ae9674ca68 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/UncategorizedDataAccessException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006 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; + +/** + * Normal superclass when we can't distinguish anything more specific + * than "something went wrong with the underlying resource": for example, + * a SQLException from JDBC we can't pinpoint more precisely. + * + * @author Rod Johnson + */ +public abstract class UncategorizedDataAccessException extends NonTransientDataAccessException { + + /** + * Constructor for UncategorizedDataAccessException. + * @param msg the detail message + * @param cause the exception thrown by underlying data access API + */ + public UncategorizedDataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java new file mode 100644 index 00000000000..90c35fc6cc5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationAdvisor.java @@ -0,0 +1,83 @@ +/* + * 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 java.lang.annotation.Annotation; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +/** + * Spring AOP exception translation aspect for use at Repository or DAO layer level. + * Translates native persistence exceptions into Spring's DataAccessException hierarchy, + * based on a given PersistenceExceptionTranslator. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.dao.DataAccessException + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ +public class PersistenceExceptionTranslationAdvisor extends AbstractPointcutAdvisor { + + private final PersistenceExceptionTranslationInterceptor advice; + + private final AnnotationMatchingPointcut pointcut; + + + /** + * Create a new PersistenceExceptionTranslationAdvisor. + * @param persistenceExceptionTranslator the PersistenceExceptionTranslator to use + * @param repositoryAnnotationType the annotation type to check for + */ + public PersistenceExceptionTranslationAdvisor( + PersistenceExceptionTranslator persistenceExceptionTranslator, + Class repositoryAnnotationType) { + + this.advice = new PersistenceExceptionTranslationInterceptor(persistenceExceptionTranslator); + this.pointcut = new AnnotationMatchingPointcut(repositoryAnnotationType, true); + } + + /** + * Create a new PersistenceExceptionTranslationAdvisor. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + * @param repositoryAnnotationType the annotation type to check for + */ + PersistenceExceptionTranslationAdvisor( + ListableBeanFactory beanFactory, Class repositoryAnnotationType) { + + this.advice = new PersistenceExceptionTranslationInterceptor(beanFactory); + this.pointcut = new AnnotationMatchingPointcut(repositoryAnnotationType, true); + } + + + public Advice getAdvice() { + return this.advice; + } + + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java new file mode 100644 index 00000000000..d832a386a17 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2008 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 java.lang.annotation.Annotation; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.ProxyConfig; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Repository; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Bean post-processor that automatically applies persistence exception + * translation to any bean that carries the + * {@link org.springframework.stereotype.Repository} annotation, + * adding a corresponding {@link PersistenceExceptionTranslationAdvisor} + * to the exposed proxy (either an existing AOP proxy or a newly generated + * proxy that implements all of the target's interfaces). + * + *

Translates native resource exceptions to Spring's + * {@link org.springframework.dao.DataAccessException} hierarchy. + * Autodetects beans that implement the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} + * interface, which are subsequently asked to translate candidate exceptions. + * + *

All of Spring's applicable resource factories implement the + * PersistenceExceptionTranslator interface out of the box. + * As a consequence, all that is usually needed to enable automatic exception + * translation is marking all affected beans (such as DAOs) with the + * Repository annotation, along with defining this post-processor + * as bean in the application context. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see PersistenceExceptionTranslationAdvisor + * @see org.springframework.stereotype.Repository + * @see org.springframework.dao.DataAccessException + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ +public class PersistenceExceptionTranslationPostProcessor extends ProxyConfig + implements BeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, Ordered { + + private Class repositoryAnnotationType = Repository.class; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private PersistenceExceptionTranslationAdvisor persistenceExceptionTranslationAdvisor; + + + /** + * Set the 'repository' annotation type. + * The default required annotation type is the {@link Repository} annotation. + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a class has a + * repository role. + * @param repositoryAnnotationType the desired annotation type + */ + public void setRepositoryAnnotationType(Class repositoryAnnotationType) { + Assert.notNull(repositoryAnnotationType, "'requiredAnnotationType' must not be null"); + this.repositoryAnnotationType = repositoryAnnotationType; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalArgumentException( + "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); + } + this.persistenceExceptionTranslationAdvisor = new PersistenceExceptionTranslationAdvisor( + (ListableBeanFactory) beanFactory, this.repositoryAnnotationType); + } + + public int getOrder() { + // This should run after all other post-processors, so that it can just add + // an advisor to existing proxies rather than double-proxy. + return LOWEST_PRECEDENCE; + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Class targetClass = + (bean instanceof Advised ? ((Advised) bean).getTargetSource().getTargetClass() : bean.getClass()); + if (targetClass == null) { + // Can't do much here + return bean; + } + + if (AopUtils.canApply(this.persistenceExceptionTranslationAdvisor, targetClass)) { + if (bean instanceof Advised) { + ((Advised) bean).addAdvisor(this.persistenceExceptionTranslationAdvisor); + return bean; + } + else { + ProxyFactory proxyFactory = new ProxyFactory(bean); + // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. + proxyFactory.copyFrom(this); + proxyFactory.addAdvisor(this.persistenceExceptionTranslationAdvisor); + return proxyFactory.getProxy(this.beanClassLoader); + } + } + else { + // This is not a repository. + return bean; + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/package.html b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/package.html new file mode 100644 index 00000000000..19fe504aa06 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/annotation/package.html @@ -0,0 +1,8 @@ + + + +Annotation support for DAOs. Contains a bean post-processor for translating +persistence exceptions based on a repository stereotype annotation. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/package.html b/org.springframework.transaction/src/main/java/org/springframework/dao/package.html new file mode 100644 index 00000000000..43d20d8bbcc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/package.html @@ -0,0 +1,19 @@ + + + +Exception hierarchy enabling sophisticated error handling independent +of the data access approach in use. For example, when DAOs and data +access frameworks use the exceptions in this package (and custom +subclasses), calling code can detect and handle common problems such +as deadlocks without being tied to a particular data access strategy, +such as JDBC. + +

All these exceptions are unchecked, meaning that calling code can +leave them uncaught and treat all data access exceptions as fatal. + +

The classes in this package are discussed in Chapter 9 of +Expert One-On-One J2EE Design and Development +by Rod Johnson (Wrox, 2002). + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/support/ChainedPersistenceExceptionTranslator.java b/org.springframework.transaction/src/main/java/org/springframework/dao/support/ChainedPersistenceExceptionTranslator.java new file mode 100644 index 00000000000..705e0a82136 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/support/ChainedPersistenceExceptionTranslator.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2006 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.support; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.util.Assert; + +/** + * Implementation of PersistenceExceptionTranslator that supports chaining, + * allowing the addition of PersistenceExceptionTranslator instances in order. + * Returns non-null on the first (if any) match. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class ChainedPersistenceExceptionTranslator implements PersistenceExceptionTranslator { + + /** List of PersistenceExceptionTranslators */ + private final List delegates = new ArrayList(4); + + + /** + * Add a PersistenceExceptionTranslator to the chained delegate list. + */ + public final void addDelegate(PersistenceExceptionTranslator pet) { + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + this.delegates.add(pet); + } + + /** + * Return all registered PersistenceExceptionTranslator delegates (as array). + */ + public final PersistenceExceptionTranslator[] getDelegates() { + return (PersistenceExceptionTranslator[]) + this.delegates.toArray(new PersistenceExceptionTranslator[this.delegates.size()]); + } + + + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + DataAccessException translatedDex = null; + for (Iterator it = this.delegates.iterator(); translatedDex == null && it.hasNext(); ) { + PersistenceExceptionTranslator pet = (PersistenceExceptionTranslator) it.next(); + translatedDex = pet.translateExceptionIfPossible(ex); + } + return translatedDex; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/support/DaoSupport.java b/org.springframework.transaction/src/main/java/org/springframework/dao/support/DaoSupport.java new file mode 100644 index 00000000000..ab1f17d8ff8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/support/DaoSupport.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2006 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; + +/** + * Generic base class for DAOs, defining template methods for DAO initialization. + * + *

Extended by Spring's specific DAO support classes, such as: + * JdbcDaoSupport, JdoDaoSupport, etc. + * + * @author Juergen Hoeller + * @since 1.2.2 + * @see org.springframework.jdbc.core.support.JdbcDaoSupport + * @see org.springframework.orm.jdo.support.JdoDaoSupport + */ +public abstract class DaoSupport implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { + // Let abstract subclasses check their configuration. + checkDaoConfig(); + + // Let concrete implementations initialize themselves. + try { + initDao(); + } + catch (Exception ex) { + throw new BeanInitializationException("Initialization of DAO failed", ex); + } + } + + /** + * Abstract subclasses must override this to check their configuration. + *

Implementors should be marked as finalReturns null if 0 result objects found; + * throws an exception if more than 1 element found. + * @param results the result Collection (can be null) + * @return the single result object, or null if none + * @throws IncorrectResultSizeDataAccessException if more than one + * element has been found in the given Collection + */ + public static Object singleResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + return null; + } + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a single result object from the given Collection. + *

Throws an exception if 0 or more than 1 element found. + * @param results the result Collection (can be null) + * @return the single result object + * @throws IncorrectResultSizeDataAccessException if more than one + * element has been found in the given Collection + * @throws EmptyResultDataAccessException if no element at all + * has been found in the given Collection + */ + public static Object requiredSingleResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + throw new EmptyResultDataAccessException(1); + } + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + *

Returns null if 0 result objects found; + * throws an exception if more than 1 instance found. + * @param results the result Collection (can be null) + * @return the unique result object, or null if none + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @see org.springframework.util.CollectionUtils#hasUniqueObject + */ + public static Object uniqueResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + return null; + } + if (!CollectionUtils.hasUniqueObject(results)) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + *

Throws an exception if 0 or more than 1 instance found. + * @param results the result Collection (can be null) + * @return the unique result object + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object at all + * has been found in the given Collection + * @see org.springframework.util.CollectionUtils#hasUniqueObject + */ + public static Object requiredUniqueResult(Collection results) throws IncorrectResultSizeDataAccessException { + int size = (results != null ? results.size() : 0); + if (size == 0) { + throw new EmptyResultDataAccessException(1); + } + if (!CollectionUtils.hasUniqueObject(results)) { + throw new IncorrectResultSizeDataAccessException(1, size); + } + return results.iterator().next(); + } + + /** + * Return a unique result object from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to the + * specified required type. + * @param results the result Collection (can be null) + * @return the unique result object + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object does + * not match the specified required type + */ + public static Object objectResult(Collection results, Class requiredType) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + Object result = requiredUniqueResult(results); + if (requiredType != null && !requiredType.isInstance(result)) { + if (String.class.equals(requiredType)) { + result = result.toString(); + } + else if (Number.class.isAssignableFrom(requiredType) && Number.class.isInstance(result)) { + try { + result = NumberUtils.convertNumberToTargetClass(((Number) result), requiredType); + } + catch (IllegalArgumentException ex) { + throw new TypeMismatchDataAccessException(ex.getMessage()); + } + } + else { + throw new TypeMismatchDataAccessException( + "Result object is of type [" + result.getClass().getName() + + "] and could not be converted to required type [" + requiredType.getName() + "]"); + } + } + return result; + } + + /** + * Return a unique int result from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to an int. + * @param results the result Collection (can be null) + * @return the unique int result + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object + * in the collection is not convertable to an int + */ + public static int intResult(Collection results) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + return ((Number) objectResult(results, Number.class)).intValue(); + } + + /** + * Return a unique long result from the given Collection. + * Throws an exception if 0 or more than 1 result objects found, + * of if the unique result object is not convertable to a long. + * @param results the result Collection (can be null) + * @return the unique long result + * @throws IncorrectResultSizeDataAccessException if more than one + * result object has been found in the given Collection + * @throws EmptyResultDataAccessException if no result object + * at all has been found in the given Collection + * @throws TypeMismatchDataAccessException if the unique object + * in the collection is not convertable to a long + */ + public static long longResult(Collection results) + throws IncorrectResultSizeDataAccessException, TypeMismatchDataAccessException { + + return ((Number) objectResult(results, Number.class)).longValue(); + } + + + /** + * Return a translated exception if this is appropriate, + * otherwise return the input exception. + * @param rawException exception we may wish to translate + * @param pet PersistenceExceptionTranslator to use to perform the translation + * @return a translated exception if translation is possible, or + * the raw exception if it is not + */ + public static RuntimeException translateIfNecessary( + RuntimeException rawException, PersistenceExceptionTranslator pet) { + + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + DataAccessException dex = pet.translateExceptionIfPossible(rawException); + return (dex != null ? dex : rawException); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java b/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java new file mode 100644 index 00000000000..60dfc197d2e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java @@ -0,0 +1,151 @@ +/* + * 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.support; + +import java.util.Iterator; +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * AOP Alliance MethodInterceptor that provides persistence exception translation + * based on a given PersistenceExceptionTranslator. + * + *

Delegates to the given {@link PersistenceExceptionTranslator} to translate + * a RuntimeException thrown into Spring's DataAccessException hierarchy + * (if appropriate). If the RuntimeException in question is declared on the + * target method, it is always propagated as-is (with no translation applied). + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see PersistenceExceptionTranslator + */ +public class PersistenceExceptionTranslationInterceptor + implements MethodInterceptor, BeanFactoryAware, InitializingBean { + + private PersistenceExceptionTranslator persistenceExceptionTranslator; + + + /** + * Create a new PersistenceExceptionTranslationInterceptor. + * Needs to be configured with a PersistenceExceptionTranslator afterwards. + * @see #setPersistenceExceptionTranslator + */ + public PersistenceExceptionTranslationInterceptor() { + } + + /** + * Create a new PersistenceExceptionTranslationInterceptor + * for the given PersistenceExceptionTranslator. + * @param persistenceExceptionTranslator the PersistenceExceptionTranslator to use + */ + public PersistenceExceptionTranslationInterceptor(PersistenceExceptionTranslator persistenceExceptionTranslator) { + setPersistenceExceptionTranslator(persistenceExceptionTranslator); + } + + /** + * Create a new PersistenceExceptionTranslationInterceptor, autodetecting + * PersistenceExceptionTranslators in the given BeanFactory. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + */ + public PersistenceExceptionTranslationInterceptor(ListableBeanFactory beanFactory) { + this.persistenceExceptionTranslator = detectPersistenceExceptionTranslators(beanFactory); + } + + + /** + * Specify the PersistenceExceptionTranslator to use. + *

Default is to autodetect all PersistenceExceptionTranslators + * in the containing BeanFactory, using them in a chain. + * @see #detectPersistenceExceptionTranslators + */ + public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator pet) { + Assert.notNull(pet, "PersistenceExceptionTranslator must not be null"); + this.persistenceExceptionTranslator = pet; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (this.persistenceExceptionTranslator == null) { + // No explicit exception translator specified - perform autodetection. + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalArgumentException( + "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); + } + this.persistenceExceptionTranslator = + detectPersistenceExceptionTranslators((ListableBeanFactory) beanFactory); + } + } + + public void afterPropertiesSet() { + if (this.persistenceExceptionTranslator == null) { + throw new IllegalArgumentException("Property 'persistenceExceptionTranslator' is required"); + } + } + + + /** + * Detect all PersistenceExceptionTranslators in the given BeanFactory. + * @param beanFactory the ListableBeanFactory to obtaining all + * PersistenceExceptionTranslators from + * @return a chained PersistenceExceptionTranslator, combining all + * PersistenceExceptionTranslators found in the factory + * @see ChainedPersistenceExceptionTranslator + */ + protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory beanFactory) { + // Find all translators, being careful not to activate FactoryBeans. + Map pets = BeanFactoryUtils.beansOfTypeIncludingAncestors( + beanFactory, PersistenceExceptionTranslator.class, false, false); + if (pets.isEmpty()) { + throw new IllegalStateException( + "No persistence exception translators found in bean factory. Cannot perform exception translation."); + } + ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator(); + for (Iterator it = pets.values().iterator(); it.hasNext();) { + cpet.addDelegate((PersistenceExceptionTranslator) it.next()); + } + return cpet; + } + + + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (RuntimeException ex) { + // Let it throw raw if the type of the exception is on the throws clause of the method. + if (ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) { + throw ex; + } + else { + throw DataAccessUtils.translateIfNecessary(ex, this.persistenceExceptionTranslator); + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslator.java b/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslator.java new file mode 100644 index 00000000000..e17732f11b9 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslator.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2006 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.support; + +import org.springframework.dao.DataAccessException; + +/** + * Interface implemented by Spring integrations with data access technologies + * that throw runtime exceptions, such as JPA, TopLink, JDO and Hibernate. + * + *

This allows consistent usage of combined exception translation functionality, + * without forcing a single translator to understand every single possible type + * of exception. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public interface PersistenceExceptionTranslator { + + /** + * Translate the given runtime exception thrown by a persistence framework to a + * corresponding exception from Spring's generic DataAccessException hierarchy, + * if possible. + *

Do not translate exceptions that are not understand by this translator: + * for example, if coming from another persistence framework, or resulting + * from user code and unrelated to persistence. + *

Of particular importance is the correct translation to + * DataIntegrityViolationException, for example on constraint violation. + * Implementations may use Spring JDBC's sophisticated exception translation + * to provide further information in the event of SQLException as a root cause. + * @param ex a RuntimeException thrown + * @return the corresponding DataAccessException (or null if the + * exception could not be translated, as in this case it may result from + * user code rather than an actual persistence problem) + * @see org.springframework.dao.DataIntegrityViolationException + * @see org.springframework.jdbc.support.SQLExceptionTranslator + */ + DataAccessException translateExceptionIfPossible(RuntimeException ex); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/dao/support/package.html b/org.springframework.transaction/src/main/java/org/springframework/dao/support/package.html new file mode 100644 index 00000000000..483def5c3c9 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/dao/support/package.html @@ -0,0 +1,8 @@ + + + +Support classes for DAO implementations, +providing miscellaneous utility methods. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotCreateRecordException.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotCreateRecordException.java new file mode 100644 index 00000000000..7ddd2b03f87 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotCreateRecordException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.jca.cci; + +import javax.resource.ResourceException; + +import org.springframework.dao.DataAccessResourceFailureException; + +/** + * Exception thrown when the creating of a CCI Record failed + * for connector-internal reasons. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class CannotCreateRecordException extends DataAccessResourceFailureException { + + /** + * Constructor for CannotCreateRecordException. + * @param msg message + * @param ex ResourceException root cause + */ + public CannotCreateRecordException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotGetCciConnectionException.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotGetCciConnectionException.java new file mode 100644 index 00000000000..e2f5761a634 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CannotGetCciConnectionException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.jca.cci; + +import javax.resource.ResourceException; + +import org.springframework.dao.DataAccessResourceFailureException; + +/** + * Fatal exception thrown when we can't connect to an EIS using CCI. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + */ +public class CannotGetCciConnectionException extends DataAccessResourceFailureException { + + /** + * Constructor for CannotGetCciConnectionException. + * @param msg message + * @param ex ResourceException root cause + */ + public CannotGetCciConnectionException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CciOperationNotSupportedException.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CciOperationNotSupportedException.java new file mode 100644 index 00000000000..b9e668d4142 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/CciOperationNotSupportedException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2005 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.jca.cci; + +import javax.resource.ResourceException; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when the connector doesn't support a specific CCI operation. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class CciOperationNotSupportedException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for CciOperationNotSupportedException. + * @param msg message + * @param ex ResourceException root cause + */ + public CciOperationNotSupportedException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/InvalidResultSetAccessException.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/InvalidResultSetAccessException.java new file mode 100644 index 00000000000..6492bc72174 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/InvalidResultSetAccessException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2005 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.jca.cci; + +import java.sql.SQLException; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when a ResultSet has been accessed in an invalid fashion. + * Such exceptions always have a java.sql.SQLException root cause. + * + *

This typically happens when an invalid ResultSet column index or name + * has been specified. + * + * @author Juergen Hoeller + * @since 1.2 + * @see javax.resource.cci.ResultSet + */ +public class InvalidResultSetAccessException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for InvalidResultSetAccessException. + * @param msg message + * @param ex the root cause + */ + public InvalidResultSetAccessException(String msg, SQLException ex) { + super(ex.getMessage(), ex); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/RecordTypeNotSupportedException.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/RecordTypeNotSupportedException.java new file mode 100644 index 00000000000..19dd0fc660c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/RecordTypeNotSupportedException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.jca.cci; + +import javax.resource.ResourceException; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when the creating of a CCI Record failed because + * the connector doesn't support the desired CCI Record type. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class RecordTypeNotSupportedException extends InvalidDataAccessResourceUsageException { + + /** + * Constructor for RecordTypeNotSupportedException. + * @param msg message + * @param ex ResourceException root cause + */ + public RecordTypeNotSupportedException(String msg, ResourceException ex) { + super(msg, ex); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/CciLocalTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/CciLocalTransactionManager.java new file mode 100644 index 00000000000..5da8793734a --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/CciLocalTransactionManager.java @@ -0,0 +1,264 @@ +/* + * 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.jca.cci.connection; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.spi.LocalTransactionException; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.support.ResourceTransactionManager; + +/** + * {@link org.springframework.transaction.PlatformTransactionManager} implementation + * that manages local transactions for a single CCI ConnectionFactory. + * Binds a CCI Connection from the specified ConnectionFactory to the thread, + * potentially allowing for one thread-bound Connection per ConnectionFactory. + * + *

Application code is required to retrieve the CCI Connection via + * {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} instead of a standard + * J2EE-style {@link ConnectionFactory#getConnection()} call. Spring classes such as + * {@link org.springframework.jca.cci.core.CciTemplate} use this strategy implicitly. + * If not used in combination with this transaction manager, the + * {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native + * DataSource lookup; it can thus be used in a portable fashion. + * + *

Alternatively, you can allow application code to work with the standard + * J2EE lookup pattern {@link ConnectionFactory#getConnection()}, for example + * for legacy code that is not aware of Spring at all. In that case, define a + * {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory, + * which will automatically participate in Spring-managed transactions. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory) + * @see ConnectionFactoryUtils#releaseConnection + * @see TransactionAwareConnectionFactoryProxy + * @see org.springframework.jca.cci.core.CciTemplate + */ +public class CciLocalTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, InitializingBean { + + private ConnectionFactory connectionFactory; + + + /** + * Create a new CciLocalTransactionManager instance. + * A ConnectionFactory has to be set to be able to use it. + * @see #setConnectionFactory + */ + public CciLocalTransactionManager() { + } + + /** + * Create a new CciLocalTransactionManager instance. + * @param connectionFactory CCI ConnectionFactory to manage local transactions for + */ + public CciLocalTransactionManager(ConnectionFactory connectionFactory) { + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + + /** + * Set the CCI ConnectionFactory that this instance should manage local + * transactions for. + */ + public void setConnectionFactory(ConnectionFactory cf) { + if (cf instanceof TransactionAwareConnectionFactoryProxy) { + // If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions + // for its underlying target ConnectionFactory, else JMS access code won't see + // properly exposed transactions (i.e. transactions for the target ConnectionFactory). + this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory(); + } + else { + this.connectionFactory = cf; + } + } + + /** + * Return the CCI ConnectionFactory that this instance manages local + * transactions for. + */ + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + public void afterPropertiesSet() { + if (getConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'connectionFactory' is required"); + } + } + + + public Object getResourceFactory() { + return getConnectionFactory(); + } + + protected Object doGetTransaction() { + CciLocalTransactionObject txObject = new CciLocalTransactionObject(); + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()); + txObject.setConnectionHolder(conHolder); + return txObject; + } + + protected boolean isExistingTransaction(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + // Consider a pre-bound connection as transaction. + return (txObject.getConnectionHolder() != null); + } + + protected void doBegin(Object transaction, TransactionDefinition definition) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + + Connection con = null; + + try { + con = getConnectionFactory().getConnection(); + if (logger.isDebugEnabled()) { + logger.debug("Acquired Connection [" + con + "] for local CCI transaction"); + } + + txObject.setConnectionHolder(new ConnectionHolder(con)); + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); + + con.getLocalTransaction().begin(); + int timeout = determineTimeout(definition); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getConnectionHolder().setTimeoutInSeconds(timeout); + } + TransactionSynchronizationManager.bindResource(getConnectionFactory(), txObject.getConnectionHolder()); + } + + catch (NotSupportedException ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new CannotCreateTransactionException("CCI Connection does not support local transactions", ex); + } + catch (LocalTransactionException ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new CannotCreateTransactionException("Could not begin local CCI transaction", ex); + } + catch (ResourceException ex) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + throw new TransactionSystemException("Unexpected failure on begin of CCI local transaction", ex); + } + } + + protected Object doSuspend(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + txObject.setConnectionHolder(null); + return TransactionSynchronizationManager.unbindResource(getConnectionFactory()); + } + + protected void doResume(Object transaction, Object suspendedResources) { + ConnectionHolder conHolder = (ConnectionHolder) suspendedResources; + TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder); + } + + protected boolean isRollbackOnly(Object transaction) throws TransactionException { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + return txObject.getConnectionHolder().isRollbackOnly(); + } + + protected void doCommit(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Committing CCI local transaction on Connection [" + con + "]"); + } + try { + con.getLocalTransaction().commit(); + } + catch (LocalTransactionException ex) { + throw new TransactionSystemException("Could not commit CCI local transaction", ex); + } + catch (ResourceException ex) { + throw new TransactionSystemException("Unexpected failure on commit of CCI local transaction", ex); + } + } + + protected void doRollback(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Rolling back CCI local transaction on Connection [" + con + "]"); + } + try { + con.getLocalTransaction().rollback(); + } + catch (LocalTransactionException ex) { + throw new TransactionSystemException("Could not roll back CCI local transaction", ex); + } + catch (ResourceException ex) { + throw new TransactionSystemException("Unexpected failure on rollback of CCI local transaction", ex); + } + } + + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting CCI local transaction [" + txObject.getConnectionHolder().getConnection() + + "] rollback-only"); + } + txObject.getConnectionHolder().setRollbackOnly(); + } + + protected void doCleanupAfterCompletion(Object transaction) { + CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction; + + // Remove the connection holder from the thread. + TransactionSynchronizationManager.unbindResource(getConnectionFactory()); + txObject.getConnectionHolder().clear(); + + Connection con = txObject.getConnectionHolder().getConnection(); + if (logger.isDebugEnabled()) { + logger.debug("Releasing CCI Connection [" + con + "] after transaction"); + } + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + + + /** + * CCI local transaction object, representing a ConnectionHolder. + * Used as transaction object by CciLocalTransactionManager. + * @see ConnectionHolder + */ + private static class CciLocalTransactionObject { + + private ConnectionHolder connectionHolder; + + public void setConnectionHolder(ConnectionHolder connectionHolder) { + this.connectionHolder = connectionHolder; + } + + public ConnectionHolder getConnectionHolder() { + return connectionHolder; + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionFactoryUtils.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionFactoryUtils.java new file mode 100644 index 00000000000..22922486661 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionFactoryUtils.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2008 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.jca.cci.connection; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.jca.cci.CannotGetCciConnectionException; +import org.springframework.transaction.support.ResourceHolder; +import org.springframework.transaction.support.ResourceHolderSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + * Helper class that provides static methods for obtaining CCI Connections + * from a {@link javax.resource.cci.ConnectionFactory}. Includes special + * support for Spring-managed transactional Connections, e.g. managed + * by {@link CciLocalTransactionManager} or + * {@link org.springframework.transaction.jta.JtaTransactionManager}. + * + *

Used internally by {@link org.springframework.jca.cci.core.CciTemplate}, + * Spring's CCI operation objects and the {@link CciLocalTransactionManager}. + * Can also be used directly in application code. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + * @see #releaseConnection + * @see CciLocalTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.transaction.support.TransactionSynchronizationManager + */ +public abstract class ConnectionFactoryUtils { + + private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class); + + + /** + * Obtain a Connection from the given ConnectionFactory. Translates ResourceExceptions + * into the Spring hierarchy of unchecked generic data access exceptions, simplifying + * calling code and making any exception that is thrown more meaningful. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + * @param cf the ConnectionFactory to obtain Connection from + * @return a CCI Connection from the given ConnectionFactory + * @throws org.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see #releaseConnection + */ + public static Connection getConnection(ConnectionFactory cf) throws CannotGetCciConnectionException { + return getConnection(cf, null); + } + + /** + * Obtain a Connection from the given ConnectionFactory. Translates ResourceExceptions + * into the Spring hierarchy of unchecked generic data access exceptions, simplifying + * calling code and making any exception that is thrown more meaningful. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + * @param cf the ConnectionFactory to obtain Connection from + * @param spec the ConnectionSpec for the desired Connection (may be null). + * Note: If this is specified, a new Connection will be obtained for every call, + * without participating in a shared transactional Connection. + * @return a CCI Connection from the given ConnectionFactory + * @throws org.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see #releaseConnection + */ + public static Connection getConnection(ConnectionFactory cf, ConnectionSpec spec) + throws CannotGetCciConnectionException { + try { + if (spec != null) { + Assert.notNull(cf, "No ConnectionFactory specified"); + return cf.getConnection(spec); + } + else { + return doGetConnection(cf); + } + } + catch (ResourceException ex) { + throw new CannotGetCciConnectionException("Could not get CCI Connection", ex); + } + } + + /** + * Actually obtain a CCI Connection from the given ConnectionFactory. + * Same as {@link #getConnection}, but throwing the original ResourceException. + *

Is aware of a corresponding Connection bound to the current thread, for example + * when using {@link CciLocalTransactionManager}. Will bind a Connection to the thread + * if transaction synchronization is active (e.g. if in a JTA transaction). + *

Directly accessed by {@link TransactionAwareConnectionFactoryProxy}. + * @param cf the ConnectionFactory to obtain Connection from + * @return a CCI Connection from the given ConnectionFactory + * @throws ResourceException if thrown by CCI API methods + * @see #doReleaseConnection + */ + public static Connection doGetConnection(ConnectionFactory cf) throws ResourceException { + Assert.notNull(cf, "No ConnectionFactory specified"); + + ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(cf); + if (conHolder != null) { + return conHolder.getConnection(); + } + + logger.debug("Opening CCI Connection"); + Connection con = cf.getConnection(); + + if (TransactionSynchronizationManager.isSynchronizationActive()) { + logger.debug("Registering transaction synchronization for CCI Connection"); + conHolder = new ConnectionHolder(con); + conHolder.setSynchronizedWithTransaction(true); + TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(conHolder, cf)); + TransactionSynchronizationManager.bindResource(cf, conHolder); + } + + return con; + } + + /** + * Determine whether the given JCA CCI Connection is transactional, that is, + * bound to the current thread by Spring's transaction facilities. + * @param con the Connection to check + * @param cf the ConnectionFactory that the Connection was obtained from + * (may be null) + * @return whether the Connection is transactional + */ + public static boolean isConnectionTransactional(Connection con, ConnectionFactory cf) { + if (cf == null) { + return false; + } + ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(cf); + return (conHolder != null && conHolder.getConnection() == con); + } + + /** + * Close the given Connection, obtained from the given ConnectionFactory, + * if it is not managed externally (that is, not bound to the thread). + * @param con the Connection to close if necessary + * (if this is null, the call will be ignored) + * @param cf the ConnectionFactory that the Connection was obtained from + * (can be null) + * @see #getConnection + */ + public static void releaseConnection(Connection con, ConnectionFactory cf) { + try { + doReleaseConnection(con, cf); + } + catch (ResourceException ex) { + logger.debug("Could not close CCI Connection", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.debug("Unexpected exception on closing CCI Connection", ex); + } + } + + /** + * Actually close the given Connection, obtained from the given ConnectionFactory. + * Same as {@link #releaseConnection}, but throwing the original ResourceException. + *

Directly accessed by {@link TransactionAwareConnectionFactoryProxy}. + * @param con the Connection to close if necessary + * (if this is null, the call will be ignored) + * @param cf the ConnectionFactory that the Connection was obtained from + * (can be null) + * @throws ResourceException if thrown by JCA CCI methods + * @see #doGetConnection + */ + public static void doReleaseConnection(Connection con, ConnectionFactory cf) throws ResourceException { + if (con == null || isConnectionTransactional(con, cf)) { + return; + } + con.close(); + } + + + /** + * Callback for resource cleanup at the end of a non-native CCI transaction + * (e.g. when participating in a JTA transaction). + */ + private static class ConnectionSynchronization extends ResourceHolderSynchronization { + + public ConnectionSynchronization(ConnectionHolder connectionHolder, ConnectionFactory connectionFactory) { + super(connectionHolder, connectionFactory); + } + + protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) { + releaseConnection(((ConnectionHolder) resourceHolder).getConnection(), (ConnectionFactory) resourceKey); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionHolder.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionHolder.java new file mode 100644 index 00000000000..86410786397 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionHolder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2005 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.jca.cci.connection; + +import javax.resource.cci.Connection; + +import org.springframework.transaction.support.ResourceHolderSupport; + +/** + * Connection holder, wrapping a CCI Connection. + * + *

CciLocalTransactionManager binds instances of this class + * to the thread, for a given ConnectionFactory. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciLocalTransactionManager + * @see ConnectionFactoryUtils + */ +public class ConnectionHolder extends ResourceHolderSupport { + + private final Connection connection; + + public ConnectionHolder(Connection connection) { + this.connection = connection; + } + + public Connection getConnection() { + return this.connection; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java new file mode 100644 index 00000000000..3a083efbdec --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/ConnectionSpecConnectionFactoryAdapter.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2008 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.jca.cci.connection; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionSpec; + +import org.springframework.core.NamedThreadLocal; + +/** + * An adapter for a target CCI {@link javax.resource.cci.ConnectionFactory}, + * applying the given ConnectionSpec to every standard getConnection() + * call, that is, implicitly invoking getConnection(ConnectionSpec) + * on the target. All other methods simply delegate to the corresponding methods + * of the target ConnectionFactory. + * + *

Can be used to proxy a target JNDI ConnectionFactory that does not have a + * ConnectionSpec configured. Client code can work with the ConnectionFactory + * without passing in a ConnectionSpec on every getConnection() call. + * + *

In the following example, client code can simply transparently work with + * the preconfigured "myConnectionFactory", implicitly accessing + * "myTargetConnectionFactory" with the specified user credentials. + * + *

+ * <bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
+ *   <property name="jndiName" value="java:comp/env/cci/mycf"/>
+ * </bean>
+ *
+ * <bean id="myConnectionFactory" class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
+ *   <property name="targetConnectionFactory" ref="myTargetConnectionFactory"/>
+ *   <property name="connectionSpec">
+ *     <bean class="your.resource.adapter.ConnectionSpecImpl">
+ *       <property name="username" value="myusername"/>
+ *       <property name="password" value="mypassword"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + *

If the "connectionSpec" is empty, this proxy will simply delegate to the + * standard getConnection() method of the target ConnectionFactory. + * This can be used to keep a UserCredentialsConnectionFactoryAdapter bean definition + * just for the option of implicitly passing in a ConnectionSpec if the + * particular target ConnectionFactory requires it. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + */ +public class ConnectionSpecConnectionFactoryAdapter extends DelegatingConnectionFactory { + + private ConnectionSpec connectionSpec; + + private final ThreadLocal threadBoundSpec = new NamedThreadLocal("Current CCI ConnectionSpec"); + + + /** + * Set the ConnectionSpec that this adapter should use for retrieving Connections. + * Default is none. + */ + public void setConnectionSpec(ConnectionSpec connectionSpec) { + this.connectionSpec = connectionSpec; + } + + /** + * Set a ConnectionSpec for this proxy and the current thread. + * The given ConnectionSpec will be applied to all subsequent + * getConnection() calls on this ConnectionFactory proxy. + *

This will override any statically specified "connectionSpec" property. + * @param spec the ConnectionSpec to apply + * @see #removeConnectionSpecFromCurrentThread + */ + public void setConnectionSpecForCurrentThread(ConnectionSpec spec) { + this.threadBoundSpec.set(spec); + } + + /** + * Remove any ConnectionSpec for this proxy from the current thread. + * A statically specified ConnectionSpec applies again afterwards. + * @see #setConnectionSpecForCurrentThread + */ + public void removeConnectionSpecFromCurrentThread() { + this.threadBoundSpec.set(null); + } + + + /** + * Determine whether there is currently a thread-bound ConnectionSpec, + * using it if available, falling back to the statically specified + * "connectionSpec" property else. + * @see #doGetConnection + */ + public final Connection getConnection() throws ResourceException { + ConnectionSpec threadSpec = (ConnectionSpec) this.threadBoundSpec.get(); + if (threadSpec != null) { + return doGetConnection(threadSpec); + } + else { + return doGetConnection(this.connectionSpec); + } + } + + /** + * This implementation delegates to the getConnection(ConnectionSpec) + * method of the target ConnectionFactory, passing in the specified user credentials. + * If the specified username is empty, it will simply delegate to the standard + * getConnection() method of the target ConnectionFactory. + * @param spec the ConnectionSpec to apply + * @return the Connection + * @see javax.resource.cci.ConnectionFactory#getConnection(javax.resource.cci.ConnectionSpec) + * @see javax.resource.cci.ConnectionFactory#getConnection() + */ + protected Connection doGetConnection(ConnectionSpec spec) throws ResourceException { + if (getTargetConnectionFactory() == null) { + throw new IllegalStateException("targetConnectionFactory is required"); + } + if (spec != null) { + return getTargetConnectionFactory().getConnection(spec); + } + else { + return getTargetConnectionFactory().getConnection(); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/DelegatingConnectionFactory.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/DelegatingConnectionFactory.java new file mode 100644 index 00000000000..b202f829027 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/DelegatingConnectionFactory.java @@ -0,0 +1,93 @@ +/* + * 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.jca.cci.connection; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; +import javax.resource.cci.RecordFactory; +import javax.resource.cci.ResourceAdapterMetaData; + +import org.springframework.beans.factory.InitializingBean; + +/** + * CCI {@link ConnectionFactory} implementation that delegates all calls + * to a given target {@link ConnectionFactory}. + * + *

This class is meant to be subclassed, with subclasses overriding only + * those methods (such as {@link #getConnection()}) that should not simply + * delegate to the target {@link ConnectionFactory}. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection + */ +public class DelegatingConnectionFactory implements ConnectionFactory, InitializingBean { + + private ConnectionFactory targetConnectionFactory; + + + /** + * Set the target ConnectionFactory that this ConnectionFactory should delegate to. + */ + public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) { + this.targetConnectionFactory = targetConnectionFactory; + } + + /** + * Return the target ConnectionFactory that this ConnectionFactory should delegate to. + */ + public ConnectionFactory getTargetConnectionFactory() { + return this.targetConnectionFactory; + } + + + public void afterPropertiesSet() { + if (getTargetConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'targetConnectionFactory' is required"); + } + } + + + public Connection getConnection() throws ResourceException { + return getTargetConnectionFactory().getConnection(); + } + + public Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException { + return getTargetConnectionFactory().getConnection(connectionSpec); + } + + public RecordFactory getRecordFactory() throws ResourceException { + return getTargetConnectionFactory().getRecordFactory(); + } + + public ResourceAdapterMetaData getMetaData() throws ResourceException { + return getTargetConnectionFactory().getMetaData(); + } + + public Reference getReference() throws NamingException { + return getTargetConnectionFactory().getReference(); + } + + public void setReference(Reference reference) { + getTargetConnectionFactory().setReference(reference); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/NotSupportedRecordFactory.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/NotSupportedRecordFactory.java new file mode 100644 index 00000000000..8b45b99f3c4 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/NotSupportedRecordFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2005 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.jca.cci.connection; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.IndexedRecord; +import javax.resource.cci.MappedRecord; +import javax.resource.cci.RecordFactory; + +/** + * Implementation of the CCI RecordFactory interface that always throws + * NotSupportedException. + * + *

Useful as a placeholder for a RecordFactory argument (for example as + * defined by the RecordCreator callback), in particular when the connector's + * ConnectionFactory.getRecordFactory() implementation happens to + * throw NotSupportedException early rather than throwing the exception from + * RecordFactory's methods. + * + * @author Juergen Hoeller + * @since 1.2.4 + * @see org.springframework.jca.cci.core.RecordCreator#createRecord(javax.resource.cci.RecordFactory) + * @see org.springframework.jca.cci.core.CciTemplate#getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.NotSupportedException + */ +public class NotSupportedRecordFactory implements RecordFactory { + + public MappedRecord createMappedRecord(String name) throws ResourceException { + throw new NotSupportedException("The RecordFactory facility is not supported by the connector"); + } + + public IndexedRecord createIndexedRecord(String name) throws ResourceException { + throw new NotSupportedException("The RecordFactory facility is not supported by the connector"); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/SingleConnectionFactory.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/SingleConnectionFactory.java new file mode 100644 index 00000000000..459982f3707 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/SingleConnectionFactory.java @@ -0,0 +1,249 @@ +/* + * Copyright 2002-2008 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.jca.cci.connection; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.Assert; + +/** + * A CCI ConnectionFactory adapter that returns the same Connection on all + * getConnection calls, and ignores calls to + * Connection.close(). + * + *

Useful for testing and standalone environments, to keep using the same + * Connection for multiple CciTemplate calls, without having a pooling + * ConnectionFactory, also spanning any number of transactions. + * + *

You can either pass in a CCI Connection directly, or let this + * factory lazily create a Connection via a given target ConnectionFactory. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #getConnection() + * @see javax.resource.cci.Connection#close() + * @see org.springframework.jca.cci.core.CciTemplate + */ +public class SingleConnectionFactory extends DelegatingConnectionFactory implements DisposableBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + /** Wrapped Connection */ + private Connection target; + + /** Proxy Connection */ + private Connection connection; + + /** Synchronization monitor for the shared Connection */ + private final Object connectionMonitor = new Object(); + + + /** + * Create a new SingleConnectionFactory for bean-style usage. + * @see #setTargetConnectionFactory + */ + public SingleConnectionFactory() { + } + + /** + * Create a new SingleConnectionFactory that always returns the + * given Connection. + * @param target the single Connection + */ + public SingleConnectionFactory(Connection target) { + Assert.notNull(target, "Target Connection must not be null"); + this.target = target; + this.connection = getCloseSuppressingConnectionProxy(target); + } + + /** + * Create a new SingleConnectionFactory that always returns a single + * Connection which it will lazily create via the given target + * ConnectionFactory. + * @param targetConnectionFactory the target ConnectionFactory + */ + public SingleConnectionFactory(ConnectionFactory targetConnectionFactory) { + Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null"); + setTargetConnectionFactory(targetConnectionFactory); + } + + + /** + * Make sure a Connection or ConnectionFactory has been set. + */ + public void afterPropertiesSet() { + if (this.connection == null && getTargetConnectionFactory() == null) { + throw new IllegalArgumentException("Connection or 'targetConnectionFactory' is required"); + } + } + + + public Connection getConnection() throws ResourceException { + synchronized (this.connectionMonitor) { + if (this.connection == null) { + initConnection(); + } + return this.connection; + } + } + + public Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException { + throw new NotSupportedException( + "SingleConnectionFactory does not support custom ConnectionSpec"); + } + + /** + * Close the underlying Connection. + * The provider of this ConnectionFactory needs to care for proper shutdown. + *

As this bean implements DisposableBean, a bean factory will + * automatically invoke this on destruction of its cached singletons. + */ + public void destroy() { + resetConnection(); + } + + + /** + * Initialize the single underlying Connection. + *

Closes and reinitializes the Connection if an underlying + * Connection is present already. + * @throws javax.resource.ResourceException if thrown by CCI API methods + */ + public void initConnection() throws ResourceException { + if (getTargetConnectionFactory() == null) { + throw new IllegalStateException( + "'targetConnectionFactory' is required for lazily initializing a Connection"); + } + synchronized (this.connectionMonitor) { + if (this.target != null) { + closeConnection(this.target); + } + this.target = doCreateConnection(); + prepareConnection(this.target); + if (logger.isInfoEnabled()) { + logger.info("Established shared CCI Connection: " + this.target); + } + this.connection = getCloseSuppressingConnectionProxy(this.target); + } + } + + /** + * Reset the underlying shared Connection, to be reinitialized on next access. + */ + public void resetConnection() { + synchronized (this.connectionMonitor) { + if (this.target != null) { + closeConnection(this.target); + } + this.target = null; + this.connection = null; + } + } + + /** + * Create a CCI Connection via this template's ConnectionFactory. + * @return the new CCI Connection + * @throws javax.resource.ResourceException if thrown by CCI API methods + */ + protected Connection doCreateConnection() throws ResourceException { + return getTargetConnectionFactory().getConnection(); + } + + /** + * Prepare the given Connection before it is exposed. + *

The default implementation is empty. Can be overridden in subclasses. + * @param con the Connection to prepare + */ + protected void prepareConnection(Connection con) throws ResourceException { + } + + /** + * Close the given Connection. + * @param con the Connection to close + */ + protected void closeConnection(Connection con) { + try { + con.close(); + } + catch (Throwable ex) { + logger.warn("Could not close shared CCI Connection", ex); + } + } + + /** + * Wrap the given Connection with a proxy that delegates every method call to it + * but suppresses close calls. This is useful for allowing application code to + * handle a special framework Connection just like an ordinary Connection from a + * CCI ConnectionFactory. + * @param target the original Connection to wrap + * @return the wrapped Connection + */ + protected Connection getCloseSuppressingConnectionProxy(Connection target) { + return (Connection) Proxy.newProxyInstance( + Connection.class.getClassLoader(), + new Class[] {Connection.class}, + new CloseSuppressingInvocationHandler(target)); + } + + + /** + * Invocation handler that suppresses close calls on CCI Connections. + */ + private static class CloseSuppressingInvocationHandler implements InvocationHandler { + + private final Connection target; + + private CloseSuppressingInvocationHandler(Connection target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of Connection proxy. + return new Integer(System.identityHashCode(proxy)); + } + else if (method.getName().equals("close")) { + // Handle close method: don't pass the call on. + return null; + } + try { + return method.invoke(this.target, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java new file mode 100644 index 00000000000..cb5ec291ee0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/TransactionAwareConnectionFactoryProxy.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-2008 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.jca.cci.connection; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; + +/** + * Proxy for a target CCI {@link javax.resource.cci.ConnectionFactory}, adding + * awareness of Spring-managed transactions. Similar to a transactional JNDI + * ConnectionFactory as provided by a J2EE server. + * + *

Data access code that should remain unaware of Spring's data access support + * can work with this proxy to seamlessly participate in Spring-managed transactions. + * Note that the transaction manager, for example the {@link CciLocalTransactionManager}, + * still needs to work with underlying ConnectionFactory, not with this proxy. + * + *

Make sure that TransactionAwareConnectionFactoryProxy is the outermost + * ConnectionFactory of a chain of ConnectionFactory proxies/adapters. + * TransactionAwareConnectionFactoryProxy can delegate either directly to the + * target connection pool or to some intermediate proxy/adapter like + * {@link ConnectionSpecConnectionFactoryAdapter}. + * + *

Delegates to {@link ConnectionFactoryUtils} for automatically participating in + * thread-bound transactions, for example managed by {@link CciLocalTransactionManager}. + * getConnection calls and close calls on returned Connections + * will behave properly within a transaction, i.e. always operate on the transactional + * Connection. If not within a transaction, normal ConnectionFactory behavior applies. + * + *

This proxy allows data access code to work with the plain JCA CCI API and still + * participate in Spring-managed transactions, similar to CCI code in a J2EE/JTA + * environment. However, if possible, use Spring's ConnectionFactoryUtils, CciTemplate or + * CCI operation objects to get transaction participation even without a proxy for + * the target ConnectionFactory, avoiding the need to define such a proxy in the first place. + * + *

NOTE: This ConnectionFactory proxy needs to return wrapped Connections + * in order to handle close calls properly. Therefore, the returned Connections cannot + * be cast to a native CCI Connection type or to a connection pool implementation type. + * + * @author Juergen Hoeller + * @since 1.2 + * @see javax.resource.cci.ConnectionFactory#getConnection + * @see javax.resource.cci.Connection#close + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#doGetConnection + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#doReleaseConnection + */ +public class TransactionAwareConnectionFactoryProxy extends DelegatingConnectionFactory { + + /** + * Create a new TransactionAwareConnectionFactoryProxy. + * @see #setTargetConnectionFactory + */ + public TransactionAwareConnectionFactoryProxy() { + } + + /** + * Create a new TransactionAwareConnectionFactoryProxy. + * @param targetConnectionFactory the target ConnectionFactory + */ + public TransactionAwareConnectionFactoryProxy(ConnectionFactory targetConnectionFactory) { + setTargetConnectionFactory(targetConnectionFactory); + afterPropertiesSet(); + } + + + /** + * Delegate to ConnectionFactoryUtils for automatically participating in Spring-managed + * transactions. Throws the original ResourceException, if any. + * @return a transactional Connection if any, a new one else + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#doGetConnection + */ + public Connection getConnection() throws ResourceException { + Connection con = ConnectionFactoryUtils.doGetConnection(getTargetConnectionFactory()); + return getTransactionAwareConnectionProxy(con, getTargetConnectionFactory()); + } + + /** + * Wrap the given Connection with a proxy that delegates every method call to it + * but delegates close calls to ConnectionFactoryUtils. + * @param target the original Connection to wrap + * @param cf ConnectionFactory that the Connection came from + * @return the wrapped Connection + * @see javax.resource.cci.Connection#close() + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#doReleaseConnection + */ + protected Connection getTransactionAwareConnectionProxy(Connection target, ConnectionFactory cf) { + return (Connection) Proxy.newProxyInstance( + Connection.class.getClassLoader(), + new Class[] {Connection.class}, + new TransactionAwareInvocationHandler(target, cf)); + } + + + /** + * Invocation handler that delegates close calls on CCI Connections + * to ConnectionFactoryUtils for being aware of thread-bound transactions. + */ + private static class TransactionAwareInvocationHandler implements InvocationHandler { + + private final Connection target; + + private final ConnectionFactory connectionFactory; + + public TransactionAwareInvocationHandler(Connection target, ConnectionFactory cf) { + this.target = target; + this.connectionFactory = cf; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // Invocation on Connection interface coming in... + + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of Connection proxy. + return new Integer(System.identityHashCode(proxy)); + } + else if (method.getName().equals("getLocalTransaction")) { + if (ConnectionFactoryUtils.isConnectionTransactional(this.target, this.connectionFactory)) { + throw new javax.resource.spi.IllegalStateException( + "Local transaction handling not allowed within a managed transaction"); + } + } + else if (method.getName().equals("close")) { + // Handle close method: only close if not within a transaction. + ConnectionFactoryUtils.doReleaseConnection(this.target, this.connectionFactory); + return null; + } + + // Invoke method on target Connection. + try { + return method.invoke(this.target, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/package.html new file mode 100644 index 00000000000..e72be14be49 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/connection/package.html @@ -0,0 +1,9 @@ + + + +Provides a utility class for easy ConnectionFactory access, +a PlatformTransactionManager for local CCI transactions, +and various simple ConnectionFactory proxies/adapters. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciOperations.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciOperations.java new file mode 100644 index 00000000000..55e638d1a0f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciOperations.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2005 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.jca.cci.core; + +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; + +import org.springframework.dao.DataAccessException; + +/** + * Interface that specifies a basic set of CCI operations on an EIS. + * Implemented by CciTemplate. Not often used, but a useful option + * to enhance testability, as it can easily be mocked or stubbed. + * + *

Alternatively, the standard CCI infrastructure can be mocked. + * However, mocking this interface constitutes significantly less work. + * + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate + */ +public interface CciOperations { + + /** + * Execute a request on an EIS with CCI, implemented as callback action + * working on a CCI Connection. This allows for implementing arbitrary + * data access operations, within Spring's managed CCI environment: + * that is, participating in Spring-managed transactions and converting + * JCA ResourceExceptions into Spring's DataAccessException hierarchy. + *

The callback action can return a result object, for example a + * domain object or a collection of domain objects. + * @param action the callback object that specifies the action + * @return the result object returned by the action, if any + * @throws DataAccessException if there is any problem + */ + Object execute(ConnectionCallback action) throws DataAccessException; + + /** + * Execute a request on an EIS with CCI, implemented as callback action + * working on a CCI Interaction. This allows for implementing arbitrary + * data access operations on a single Interaction, within Spring's managed + * CCI environment: that is, participating in Spring-managed transactions + * and converting JCA ResourceExceptions into Spring's DataAccessException + * hierarchy. + *

The callback action can return a result object, for example a + * domain object or a collection of domain objects. + * @param action the callback object that specifies the action + * @return the result object returned by the action, if any + * @throws DataAccessException if there is any problem + */ + Object execute(InteractionCallback action) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @return the output record + * @throws DataAccessException if there is any problem + */ + Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputRecord the output record + * @throws DataAccessException if there is any problem + */ + void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputCreator object that creates the input record to use + * @return the output record + * @throws DataAccessException if there is any problem + */ + Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + Object execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor) + throws DataAccessException; + + /** + * Execute the specified interaction on an EIS with CCI. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputCreator object that creates the input record to use + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + Object execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor outputExtractor) + throws DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciTemplate.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciTemplate.java new file mode 100644 index 00000000000..d04bedb7682 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/CciTemplate.java @@ -0,0 +1,431 @@ +/* + * Copyright 2002-2008 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.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; +import javax.resource.cci.IndexedRecord; +import javax.resource.cci.Interaction; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.MappedRecord; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; +import javax.resource.cci.ResultSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jca.cci.CannotCreateRecordException; +import org.springframework.jca.cci.CciOperationNotSupportedException; +import org.springframework.jca.cci.InvalidResultSetAccessException; +import org.springframework.jca.cci.RecordTypeNotSupportedException; +import org.springframework.jca.cci.connection.ConnectionFactoryUtils; +import org.springframework.jca.cci.connection.NotSupportedRecordFactory; +import org.springframework.util.Assert; + +/** + * This is the central class in the CCI core package. + * It simplifies the use of CCI and helps to avoid common errors. + * It executes core CCI workflow, leaving application code to provide parameters + * to CCI and extract results. This class executes EIS queries or updates, + * catching ResourceExceptions and translating them to the generic exception + * hierarchy defined in the org.springframework.dao package. + * + *

Code using this class can pass in and receive {@link javax.resource.cci.Record} + * instances, or alternatively implement callback interfaces for creating input + * Records and extracting result objects from output Records (or CCI ResultSets). + * + *

Can be used within a service implementation via direct instantiation + * with a ConnectionFactory reference, or get prepared in an application context + * and given to services as bean reference. Note: The ConnectionFactory should + * always be configured as a bean in the application context, in the first case + * given to the service directly, in the second case to the prepared template. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see RecordCreator + * @see RecordExtractor + */ +public class CciTemplate implements CciOperations { + + private final Log logger = LogFactory.getLog(getClass()); + + private ConnectionFactory connectionFactory; + + private ConnectionSpec connectionSpec; + + private RecordCreator outputRecordCreator; + + + /** + * Construct a new CciTemplate for bean usage. + *

Note: The ConnectionFactory has to be set before using the instance. + * @see #setConnectionFactory + */ + public CciTemplate() { + } + + /** + * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from. + * Note: This will trigger eager initialization of the exception translator. + * @param connectionFactory JCA ConnectionFactory to obtain Connections from + */ + public CciTemplate(ConnectionFactory connectionFactory) { + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + /** + * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from. + * Note: This will trigger eager initialization of the exception translator. + * @param connectionFactory JCA ConnectionFactory to obtain Connections from + * @param connectionSpec the CCI ConnectionSpec to obtain Connections for + * (may be null) + */ + public CciTemplate(ConnectionFactory connectionFactory, ConnectionSpec connectionSpec) { + setConnectionFactory(connectionFactory); + setConnectionSpec(connectionSpec); + afterPropertiesSet(); + } + + + /** + * Set the CCI ConnectionFactory to obtain Connections from. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Return the CCI ConnectionFactory used by this template. + */ + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + /** + * Set the CCI ConnectionSpec that this template instance is + * supposed to obtain Connections for. + */ + public void setConnectionSpec(ConnectionSpec connectionSpec) { + this.connectionSpec = connectionSpec; + } + + /** + * Return the CCI ConnectionSpec used by this template, if any. + */ + public ConnectionSpec getConnectionSpec() { + return this.connectionSpec; + } + + /** + * Set a RecordCreator that should be used for creating default output Records. + *

Default is none: When no explicit output Record gets passed into an + * execute method, CCI's Interaction.execute variant + * that returns an output Record will be called. + *

Specify a RecordCreator here if you always need to call CCI's + * Interaction.execute variant with a passed-in output Record. + * Unless there is an explicitly specified output Record, CciTemplate will + * then invoke this RecordCreator to create a default output Record instance. + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + */ + public void setOutputRecordCreator(RecordCreator creator) { + this.outputRecordCreator = creator; + } + + /** + * Return a RecordCreator that should be used for creating default output Records. + */ + public RecordCreator getOutputRecordCreator() { + return this.outputRecordCreator; + } + + public void afterPropertiesSet() { + if (getConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'connectionFactory' is required"); + } + } + + + /** + * Create a template derived from this template instance, + * inheriting the ConnectionFactory and other settings but + * overriding the ConnectionSpec used for obtaining Connections. + * @param connectionSpec the CCI ConnectionSpec that the derived template + * instance is supposed to obtain Connections for + * @return the derived template instance + * @see #setConnectionSpec + */ + public CciTemplate getDerivedTemplate(ConnectionSpec connectionSpec) { + CciTemplate derived = new CciTemplate(); + derived.setConnectionFactory(getConnectionFactory()); + derived.setConnectionSpec(connectionSpec); + derived.setOutputRecordCreator(getOutputRecordCreator()); + return derived; + } + + + public Object execute(ConnectionCallback action) throws DataAccessException { + Assert.notNull(action, "Callback object must not be null"); + + Connection con = ConnectionFactoryUtils.getConnection(getConnectionFactory(), getConnectionSpec()); + try { + return action.doInConnection(con, getConnectionFactory()); + } + catch (NotSupportedException ex) { + throw new CciOperationNotSupportedException("CCI operation not supported by connector", ex); + } + catch (ResourceException ex) { + throw new DataAccessResourceFailureException("CCI operation failed", ex); + } + catch (SQLException ex) { + throw new InvalidResultSetAccessException("Parsing of CCI ResultSet failed", ex); + } + finally { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + } + + public Object execute(final InteractionCallback action) throws DataAccessException { + Assert.notNull(action, "Callback object must not be null"); + + return execute(new ConnectionCallback() { + public Object doInConnection(Connection connection, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException { + + Interaction interaction = connection.createInteraction(); + try { + return action.doInInteraction(interaction, connectionFactory); + } + finally { + closeInteraction(interaction); + } + } + }); + } + + public Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException { + return (Record) doExecute(spec, inputRecord, null, null); + } + + public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException { + doExecute(spec, inputRecord, outputRecord, null); + } + + public Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException { + return (Record) doExecute(spec, createRecord(inputCreator), null, null); + } + + public Object execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor) + throws DataAccessException { + + return doExecute(spec, inputRecord, null, outputExtractor); + } + + public Object execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor outputExtractor) + throws DataAccessException { + + return doExecute(spec, createRecord(inputCreator), null, outputExtractor); + } + + /** + * Execute the specified interaction on an EIS with CCI. + * All other interaction execution methods go through this. + * @param spec the CCI InteractionSpec instance that defines + * the interaction (connector-specific) + * @param inputRecord the input record + * @param outputRecord output record (can be null) + * @param outputExtractor object to convert the output record to a result object + * @return the output data extracted with the RecordExtractor object + * @throws DataAccessException if there is any problem + */ + protected Object doExecute( + final InteractionSpec spec, final Record inputRecord, final Record outputRecord, + final RecordExtractor outputExtractor) throws DataAccessException { + + return execute(new InteractionCallback() { + public Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException { + + Record outputRecordToUse = outputRecord; + try { + if (outputRecord != null || getOutputRecordCreator() != null) { + // Use the CCI execute method with output record as parameter. + if (outputRecord == null) { + RecordFactory recordFactory = getRecordFactory(connectionFactory); + outputRecordToUse = getOutputRecordCreator().createRecord(recordFactory); + } + interaction.execute(spec, inputRecord, outputRecordToUse); + } + else { + outputRecordToUse = interaction.execute(spec, inputRecord); + } + if (outputExtractor != null) { + return outputExtractor.extractData(outputRecordToUse); + } + else { + return outputRecordToUse; + } + } + finally { + if (outputRecordToUse instanceof ResultSet) { + closeResultSet((ResultSet) outputRecordToUse); + } + } + } + }); + } + + + /** + * Create an indexed Record through the ConnectionFactory's RecordFactory. + * @param name the name of the record + * @return the Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.RecordFactory#createIndexedRecord(String) + */ + public IndexedRecord createIndexedRecord(String name) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordFactory.createIndexedRecord(name); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException("Creation of indexed Record not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of indexed Record failed", ex); + } + } + + /** + * Create a mapped Record from the ConnectionFactory's RecordFactory. + * @param name record name + * @return the Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see javax.resource.cci.RecordFactory#createMappedRecord(String) + */ + public MappedRecord createMappedRecord(String name) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordFactory.createMappedRecord(name); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException("Creation of mapped Record not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of mapped Record failed", ex); + } + } + + /** + * Invoke the given RecordCreator, converting JCA ResourceExceptions + * to Spring's DataAccessException hierarchy. + * @param recordCreator the RecordCreator to invoke + * @return the created Record + * @throws DataAccessException if creation of the Record failed + * @see #getRecordFactory(javax.resource.cci.ConnectionFactory) + * @see RecordCreator#createRecord(javax.resource.cci.RecordFactory) + */ + protected Record createRecord(RecordCreator recordCreator) throws DataAccessException { + try { + RecordFactory recordFactory = getRecordFactory(getConnectionFactory()); + return recordCreator.createRecord(recordFactory); + } + catch (NotSupportedException ex) { + throw new RecordTypeNotSupportedException( + "Creation of the desired Record type not supported by connector", ex); + } + catch (ResourceException ex) { + throw new CannotCreateRecordException("Creation of the desired Record failed", ex); + } + } + + /** + * Return a RecordFactory for the given ConnectionFactory. + *

Default implementation returns the connector's RecordFactory if + * available, falling back to a NotSupportedRecordFactory placeholder. + * This allows to invoke a RecordCreator callback with a non-null + * RecordFactory reference in any case. + * @param connectionFactory the CCI ConnectionFactory + * @return the CCI RecordFactory for the ConnectionFactory + * @throws ResourceException if thrown by CCI methods + * @see org.springframework.jca.cci.connection.NotSupportedRecordFactory + */ + protected RecordFactory getRecordFactory(ConnectionFactory connectionFactory) throws ResourceException { + try { + return getConnectionFactory().getRecordFactory(); + } + catch (NotSupportedException ex) { + return new NotSupportedRecordFactory(); + } + } + + + /** + * Close the given CCI Interaction and ignore any thrown exception. + * This is useful for typical finally blocks in manual CCI code. + * @param interaction the CCI Interaction to close + * @see javax.resource.cci.Interaction#close() + */ + private void closeInteraction(Interaction interaction) { + if (interaction != null) { + try { + interaction.close(); + } + catch (ResourceException ex) { + logger.trace("Could not close CCI Interaction", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.trace("Unexpected exception on closing CCI Interaction", ex); + } + } + } + + /** + * Close the given CCI ResultSet and ignore any thrown exception. + * This is useful for typical finally blocks in manual CCI code. + * @param resultSet the CCI ResultSet to close + * @see javax.resource.cci.ResultSet#close() + */ + private void closeResultSet(ResultSet resultSet) { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException ex) { + logger.trace("Could not close CCI ResultSet", ex); + } + catch (Throwable ex) { + // We don't trust the CCI driver: It might throw RuntimeException or Error. + logger.trace("Unexpected exception on closing CCI ResultSet", ex); + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/ConnectionCallback.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/ConnectionCallback.java new file mode 100644 index 00000000000..ad69d7e57a5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/ConnectionCallback.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2005 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.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; + +import org.springframework.dao.DataAccessException; + +/** + * Generic callback interface for code that operates on a CCI Connection. + * Allows to execute any number of operations on a single Connection, + * using any type and number of Interaction. + * + *

This is particularly useful for delegating to existing data access code + * that expects a Connection to work on and throws ResourceException. For newly + * written code, it is strongly recommended to use CciTemplate's more specific + * execute variants. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(ConnectionCallback) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ +public interface ConnectionCallback { + + /** + * Gets called by CciTemplate.execute with an active CCI Connection. + * Does not need to care about activating or closing the Connection, or handling + * transactions. + * + *

If called without a thread-bound CCI transaction (initiated by + * CciLocalTransactionManager), the code will simply get executed on the CCI + * Connection with its transactional semantics. If CciTemplate is configured + * to use a JTA-aware ConnectionFactory, the CCI Connection and thus the callback + * code will be transactional if a JTA transaction is active. + * + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see the CciTemplate.execute + * variants. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * + * @param connection active CCI Connection + * @param connectionFactory the CCI ConnectionFactory that the Connection was + * created with (gives access to RecordFactory and ResourceAdapterMetaData) + * @return a result object, or null if none + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.cci.ConnectionFactory#getMetaData() + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ + Object doInConnection(Connection connection, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/InteractionCallback.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/InteractionCallback.java new file mode 100644 index 00000000000..24cf3558bf8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/InteractionCallback.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2005 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.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.Interaction; + +import org.springframework.dao.DataAccessException; + +/** + * Generic callback interface for code that operates on a CCI Interaction. + * Allows to execute any number of operations on a single Interaction, for + * example a single execute call or repeated execute calls with varying + * parameters. + * + *

This is particularly useful for delegating to existing data access code + * that expects an Interaction to work on and throws ResourceException. For newly + * written code, it is strongly recommended to use CciTemplate's more specific + * execute variants. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(InteractionCallback) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ +public interface InteractionCallback { + + /** + * Gets called by CciTemplate.execute with an active CCI Interaction. + * Does not need to care about activating or closing the Interaction, or + * handling transactions. + * + *

If called without a thread-bound CCI transaction (initiated by + * CciLocalTransactionManager), the code will simply get executed on the CCI + * Interaction with its transactional semantics. If CciTemplate is configured + * to use a JTA-aware ConnectionFactory, the CCI Interaction and thus the callback + * code will be transactional if a JTA transaction is active. + * + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see the CciTemplate.execute + * variants. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * + * @param interaction active CCI Interaction + * @param connectionFactory the CCI ConnectionFactory that the Connection was + * created with (gives access to RecordFactory and ResourceAdapterMetaData) + * @return a result object, or null if none + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ConnectionFactory#getRecordFactory() + * @see javax.resource.cci.ConnectionFactory#getMetaData() + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + */ + Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory) + throws ResourceException, SQLException, DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordCreator.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordCreator.java new file mode 100644 index 00000000000..4458dfb9bfa --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordCreator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2005 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.jca.cci.core; + +import javax.resource.ResourceException; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import org.springframework.dao.DataAccessException; + +/** + * Callback interface for creating a CCI Record instance, + * usually based on the passed-in CCI RecordFactory. + * + *

Used for input Record creation in CciTemplate. Alternatively, + * Record instances can be passed into CciTemplate's corresponding + * execute methods directly, either instantiated manually + * or created through CciTemplate's Record factory methods. + * + *

Also used for creating default output Records in CciTemplate. + * This is useful when the JCA connector needs an explicit output Record + * instance, but no output Records should be passed into CciTemplate's + * execute methods. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + * @see CciTemplate#createIndexedRecord(String) + * @see CciTemplate#createMappedRecord(String) + * @see CciTemplate#setOutputRecordCreator(RecordCreator) + */ +public interface RecordCreator { + + /** + * Create a CCI Record instance, usually based on the passed-in CCI RecordFactory. + *

For use as input creator with CciTemplate's execute methods, + * this method should create a populated Record instance. For use as + * output Record creator, it should return an empty Record instance. + * @param recordFactory the CCI RecordFactory (never null, but not guaranteed to be + * supported by the connector: its create methods might throw NotSupportedException) + * @return the Record instance + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + */ + Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordExtractor.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordExtractor.java new file mode 100644 index 00000000000..325f7c9f9c1 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/RecordExtractor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2005 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.jca.cci.core; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.Record; + +import org.springframework.dao.DataAccessException; + +/** + * Callback interface for extracting a result object from a CCI Record instance. + * + *

Used for output object creation in CciTemplate. Alternatively, output + * Records can also be returned to client code as-is. In case of a CCI ResultSet + * as execution result, you will almost always want to implement a RecordExtractor, + * to be able to read the ResultSet in a managed fashion, with the CCI Connection + * still open while reading the ResultSet. + * + *

Implementations of this interface perform the actual work of extracting + * results, but don't need to worry about exception handling. ResourceExceptions + * will be caught and handled correctly by the CciTemplate class. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, Record, RecordExtractor) + * @see CciTemplate#execute(javax.resource.cci.InteractionSpec, RecordCreator, RecordExtractor) + * @see javax.resource.cci.ResultSet + */ +public interface RecordExtractor { + + /** + * Process the data in the given Record, creating a corresponding result object. + * @param record the Record to extract data from + * (possibly a CCI ResultSet) + * @return an arbitrary result object, or null if none + * (the extractor will typically be stateful in the latter case) + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @throws SQLException if thrown by a ResultSet method, to be auto-converted + * to a DataAccessException + * @throws DataAccessException in case of custom exceptions + * @see javax.resource.cci.ResultSet + */ + Object extractData(Record record) throws ResourceException, SQLException, DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/package.html new file mode 100644 index 00000000000..a1644712669 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/package.html @@ -0,0 +1,8 @@ + + + +Provides the core JCA CCI support, based on CciTemplate +and its associated callback interfaces. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CciDaoSupport.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CciDaoSupport.java new file mode 100644 index 00000000000..ebee0842278 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CciDaoSupport.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2008 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.jca.cci.core.support; + +import javax.resource.cci.Connection; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.ConnectionSpec; + +import org.springframework.dao.support.DaoSupport; +import org.springframework.jca.cci.CannotGetCciConnectionException; +import org.springframework.jca.cci.connection.ConnectionFactoryUtils; +import org.springframework.jca.cci.core.CciTemplate; + +/** + * Convenient super class for CCI-based data access objects. + * + *

Requires a {@link javax.resource.cci.ConnectionFactory} to be set, + * providing a {@link org.springframework.jca.cci.core.CciTemplate} based + * on it to subclasses through the {@link #getCciTemplate()} method. + * + *

This base class is mainly intended for CciTemplate usage but can + * also be used when working with a Connection directly or when using + * org.springframework.jca.cci.object classes. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #setConnectionFactory + * @see #getCciTemplate + * @see org.springframework.jca.cci.core.CciTemplate + */ +public abstract class CciDaoSupport extends DaoSupport { + + private CciTemplate cciTemplate; + + + /** + * Set the ConnectionFactory to be used by this DAO. + */ + public final void setConnectionFactory(ConnectionFactory connectionFactory) { + if (this.cciTemplate == null || connectionFactory != this.cciTemplate.getConnectionFactory()) { + this.cciTemplate = createCciTemplate(connectionFactory); + } + } + + /** + * Create a CciTemplate for the given ConnectionFactory. + * Only invoked if populating the DAO with a ConnectionFactory reference! + *

Can be overridden in subclasses to provide a CciTemplate instance + * with different configuration, or a custom CciTemplate subclass. + * @param connectionFactory the CCI ConnectionFactory to create a CciTemplate for + * @return the new CciTemplate instance + * @see #setConnectionFactory(javax.resource.cci.ConnectionFactory) + */ + protected CciTemplate createCciTemplate(ConnectionFactory connectionFactory) { + return new CciTemplate(connectionFactory); + } + + /** + * Return the ConnectionFactory used by this DAO. + */ + public final ConnectionFactory getConnectionFactory() { + return this.cciTemplate.getConnectionFactory(); + } + + /** + * Set the CciTemplate for this DAO explicitly, + * as an alternative to specifying a ConnectionFactory. + */ + public final void setCciTemplate(CciTemplate cciTemplate) { + this.cciTemplate = cciTemplate; + } + + /** + * Return the CciTemplate for this DAO, + * pre-initialized with the ConnectionFactory or set explicitly. + */ + public final CciTemplate getCciTemplate() { + return this.cciTemplate; + } + + protected final void checkDaoConfig() { + if (this.cciTemplate == null) { + throw new IllegalArgumentException("'connectionFactory' or 'cciTemplate' is required"); + } + } + + + /** + * Obtain a CciTemplate derived from the main template instance, + * inheriting the ConnectionFactory and other settings but + * overriding the ConnectionSpec used for obtaining Connections. + * @param connectionSpec the CCI ConnectionSpec that the returned + * template instance is supposed to obtain Connections for + * @return the derived template instance + * @see org.springframework.jca.cci.core.CciTemplate#getDerivedTemplate(javax.resource.cci.ConnectionSpec) + */ + protected final CciTemplate getCciTemplate(ConnectionSpec connectionSpec) { + return getCciTemplate().getDerivedTemplate(connectionSpec); + } + + /** + * Get a CCI Connection, either from the current transaction or a new one. + * @return the CCI Connection + * @throws org.springframework.jca.cci.CannotGetCciConnectionException + * if the attempt to get a Connection failed + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory) + */ + protected final Connection getConnection() throws CannotGetCciConnectionException { + return ConnectionFactoryUtils.getConnection(getConnectionFactory()); + } + + /** + * Close the given CCI Connection, created via this bean's ConnectionFactory, + * if it isn't bound to the thread. + * @param con Connection to close + * @see org.springframework.jca.cci.connection.ConnectionFactoryUtils#releaseConnection + */ + protected final void releaseConnection(Connection con) { + ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CommAreaRecord.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CommAreaRecord.java new file mode 100644 index 00000000000..cf2e1edf76b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/CommAreaRecord.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2005 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.jca.cci.core.support; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.resource.cci.Record; +import javax.resource.cci.Streamable; + +import org.springframework.util.FileCopyUtils; + +/** + * CCI Record implementation for a COMMAREA, holding a byte array. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.jca.cci.object.MappingCommAreaOperation + */ +public class CommAreaRecord implements Record, Streamable { + + private byte[] bytes; + + private String recordName; + + private String recordShortDescription; + + + /** + * Create a new CommAreaRecord. + * @see #read(java.io.InputStream) + */ + public CommAreaRecord() { + } + + /** + * Create a new CommAreaRecord. + * @param bytes the bytes to fill the record with + */ + public CommAreaRecord(byte[] bytes) { + this.bytes = bytes; + } + + + public void setRecordName(String recordName) { + this.recordName=recordName; + } + + public String getRecordName() { + return recordName; + } + + public void setRecordShortDescription(String recordShortDescription) { + this.recordShortDescription=recordShortDescription; + } + + public String getRecordShortDescription() { + return recordShortDescription; + } + + + public void read(InputStream in) throws IOException { + this.bytes = FileCopyUtils.copyToByteArray(in); + } + + public void write(OutputStream out) throws IOException { + out.write(this.bytes); + out.flush(); + } + + public byte[] toByteArray() { + return this.bytes; + } + + + public Object clone() { + return new CommAreaRecord(this.bytes); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/package.html new file mode 100644 index 00000000000..740f2e944c3 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/core/support/package.html @@ -0,0 +1,8 @@ + + + +Classes supporting the org.springframework.jca.cci.core package. +Contains a DAO base class for CciTemplate usage. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/EisOperation.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/EisOperation.java new file mode 100644 index 00000000000..9602e9a6256 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/EisOperation.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2005 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.jca.cci.object; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jca.cci.core.CciTemplate; + +/** + * Base class for EIS operation objects that work with the CCI API. + * Encapsulates a CCI ConnectionFactory and a CCI InteractionSpec. + * + *

Works with a CciTemplate instance underneath. EIS operation objects + * are an alternative to working with a CciTemplate directly. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setConnectionFactory + * @see #setInteractionSpec + */ +public abstract class EisOperation implements InitializingBean { + + private CciTemplate cciTemplate = new CciTemplate(); + + private InteractionSpec interactionSpec; + + + /** + * Set the CciTemplate to be used by this operation. + * Alternatively, specify a CCI ConnectionFactory. + * @see #setConnectionFactory + */ + public void setCciTemplate(CciTemplate cciTemplate) { + if (cciTemplate == null) { + throw new IllegalArgumentException("cciTemplate must not be null"); + } + this.cciTemplate = cciTemplate; + } + + /** + * Return the CciTemplate used by this operation. + */ + public CciTemplate getCciTemplate() { + return this.cciTemplate; + } + + /** + * Set the CCI ConnectionFactory to be used by this operation. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.cciTemplate.setConnectionFactory(connectionFactory); + } + + /** + * Set the CCI InteractionSpec for this operation. + */ + public void setInteractionSpec(InteractionSpec interactionSpec) { + this.interactionSpec = interactionSpec; + } + + /** + * Return the CCI InteractionSpec for this operation. + */ + public InteractionSpec getInteractionSpec() { + return this.interactionSpec; + } + + + public void afterPropertiesSet() { + this.cciTemplate.afterPropertiesSet(); + + if (this.interactionSpec == null) { + throw new IllegalArgumentException("interactionSpec is required"); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingCommAreaOperation.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingCommAreaOperation.java new file mode 100644 index 00000000000..85eda5644f5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingCommAreaOperation.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2005 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.jca.cci.object; + +import java.io.IOException; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.jca.cci.core.support.CommAreaRecord; + +/** + * EIS operation object for access to COMMAREA records. + * Subclass of the generic MappingRecordOperation class. + * + * @author Thierry Templier + * @since 1.2 + */ +public abstract class MappingCommAreaOperation extends MappingRecordOperation { + + /** + * Create a new MappingCommAreaQuery. + * @see #setConnectionFactory + * @see #setInteractionSpec + */ + public MappingCommAreaOperation() { + } + + /** + * Create a new MappingCommAreaQuery. + * @param connectionFactory ConnectionFactory to use to obtain connections + * @param interactionSpec specification to configure the interaction + */ + public MappingCommAreaOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + super(connectionFactory, interactionSpec); + } + + + protected final Record createInputRecord(RecordFactory recordFactory, Object inObject) { + try { + return new CommAreaRecord(objectToBytes(inObject)); + } + catch (IOException ex) { + throw new DataRetrievalFailureException("I/O exception during bytes conversion", ex); + } + } + + protected final Object extractOutputData(Record record) throws DataAccessException { + CommAreaRecord commAreaRecord = (CommAreaRecord) record; + try { + return bytesToObject(commAreaRecord.toByteArray()); + } + catch (IOException ex) { + throw new DataRetrievalFailureException("I/O exception during bytes conversion", ex); + } + } + + + /** + * Method used to convert an object into COMMAREA bytes. + * @param inObject the input data + * @return the COMMAREA's bytes + * @throws IOException if thrown by I/O methods + * @throws DataAccessException if conversion failed + */ + protected abstract byte[] objectToBytes(Object inObject) throws IOException, DataAccessException; + + /** + * Method used to convert the COMMAREA's bytes to an object. + * @param bytes the COMMAREA's bytes + * @return the output data + * @throws IOException if thrown by I/O methods + * @throws DataAccessException if conversion failed + */ + protected abstract Object bytesToObject(byte[] bytes) throws IOException, DataAccessException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingRecordOperation.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingRecordOperation.java new file mode 100644 index 00000000000..8c9915b796f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/MappingRecordOperation.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2005 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.jca.cci.object; + +import java.sql.SQLException; + +import javax.resource.ResourceException; +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; +import javax.resource.cci.RecordFactory; + +import org.springframework.dao.DataAccessException; +import org.springframework.jca.cci.core.RecordCreator; +import org.springframework.jca.cci.core.RecordExtractor; + +/** + * EIS operation object that expects mapped input and output objects, + * converting to and from CCI Records. + * + *

Concrete subclasses must implement the abstract + * createInputRecord(RecordFactory, Object) and + * extractOutputData(Record) methods, to create an input + * Record from an object and to convert an output Record into an object, + * respectively. + * + * @author Thierry Templier + * @author Juergen Hoeller + * @since 1.2 + * @see #createInputRecord(javax.resource.cci.RecordFactory, Object) + * @see #extractOutputData(javax.resource.cci.Record) + */ +public abstract class MappingRecordOperation extends EisOperation { + + /** + * Constructor that allows use as a JavaBean. + */ + public MappingRecordOperation() { + } + + /** + * Convenient constructor with ConnectionFactory and specifications + * (connection and interaction). + * @param connectionFactory ConnectionFactory to use to obtain connections + */ + public MappingRecordOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + getCciTemplate().setConnectionFactory(connectionFactory); + setInteractionSpec(interactionSpec); + } + + /** + * Set a RecordCreator that should be used for creating default output Records. + *

Default is none: CCI's Interaction.execute variant + * that returns an output Record will be called. + *

Specify a RecordCreator here if you always need to call CCI's + * Interaction.execute variant with a passed-in output Record. + * This RecordCreator will then be invoked to create a default output Record instance. + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + * @see org.springframework.jca.cci.core.CciTemplate#setOutputRecordCreator + */ + public void setOutputRecordCreator(RecordCreator creator) { + getCciTemplate().setOutputRecordCreator(creator); + } + + /** + * Execute the interaction encapsulated by this operation object. + * @param inputObject the input data, to be converted to a Record + * by the createInputRecord method + * @return the output data extracted with the extractOutputData method + * @throws DataAccessException if there is any problem + * @see #createInputRecord + * @see #extractOutputData + */ + public Object execute(Object inputObject) throws DataAccessException { + return getCciTemplate().execute( + getInteractionSpec(), new RecordCreatorImpl(inputObject), new RecordExtractorImpl()); + } + + + /** + * Subclasses must implement this method to generate an input Record + * from an input object passed into the execute method. + * @param inputObject the passed-in input object + * @return the CCI input Record + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @see #execute(Object) + */ + protected abstract Record createInputRecord(RecordFactory recordFactory, Object inputObject) + throws ResourceException, DataAccessException; + + /** + * Subclasses must implement this method to convert the Record returned + * by CCI execution into a result object for the execute method. + * @param outputRecord the Record returned by CCI execution + * @return the result object + * @throws ResourceException if thrown by a CCI method, to be auto-converted + * to a DataAccessException + * @see #execute(Object) + */ + protected abstract Object extractOutputData(Record outputRecord) + throws ResourceException, SQLException, DataAccessException; + + + /** + * Implementation of RecordCreator that calls the enclosing + * class's createInputRecord method. + */ + protected class RecordCreatorImpl implements RecordCreator { + + private final Object inputObject; + + public RecordCreatorImpl(Object inObject) { + this.inputObject = inObject; + } + + public Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException { + return createInputRecord(recordFactory, this.inputObject); + } + } + + + /** + * Implementation of RecordExtractor that calls the enclosing + * class's extractOutputData method. + */ + protected class RecordExtractorImpl implements RecordExtractor { + + public Object extractData(Record record) throws ResourceException, SQLException, DataAccessException { + return extractOutputData(record); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/SimpleRecordOperation.java b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/SimpleRecordOperation.java new file mode 100644 index 00000000000..e5a2f9cd0ac --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/SimpleRecordOperation.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2005 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.jca.cci.object; + +import javax.resource.cci.ConnectionFactory; +import javax.resource.cci.InteractionSpec; +import javax.resource.cci.Record; + +import org.springframework.dao.DataAccessException; + +/** + * EIS operation object that accepts a passed-in CCI input Record + * and returns a corresponding CCI output Record. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class SimpleRecordOperation extends EisOperation { + + /** + * Constructor that allows use as a JavaBean. + */ + public SimpleRecordOperation() { + } + + /** + * Convenient constructor with ConnectionFactory and specifications + * (connection and interaction). + * @param connectionFactory ConnectionFactory to use to obtain connections + */ + public SimpleRecordOperation(ConnectionFactory connectionFactory, InteractionSpec interactionSpec) { + getCciTemplate().setConnectionFactory(connectionFactory); + setInteractionSpec(interactionSpec); + } + + /** + * Execute the CCI interaction encapsulated by this operation object. + *

This method will call CCI's Interaction.execute variant + * that returns an output Record. + * @param inputRecord the input record + * @return the output record + * @throws DataAccessException if there is any problem + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record) + */ + public Record execute(Record inputRecord) throws DataAccessException { + return getCciTemplate().execute(getInteractionSpec(), inputRecord); + } + + /** + * Execute the CCI interaction encapsulated by this operation object. + *

This method will call CCI's Interaction.execute variant + * with a passed-in output Record. + * @param inputRecord the input record + * @param outputRecord the output record + * @throws DataAccessException if there is any problem + * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record) + */ + public void execute(Record inputRecord, Record outputRecord) throws DataAccessException { + getCciTemplate().execute(getInteractionSpec(), inputRecord, outputRecord); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/package.html new file mode 100644 index 00000000000..45c9422bd79 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/object/package.html @@ -0,0 +1,11 @@ + + + +The classes in this package represent EIS operations as threadsafe, +reusable objects. This higher level of CCI abstraction depends on the +lower-level abstraction in the org.springframework.jca.cci.core package. +Exceptions thrown are as in the org.springframework.dao package, +meaning that code using this package does not need to worry about error handling. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/cci/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/package.html new file mode 100644 index 00000000000..7771016c2a3 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/cci/package.html @@ -0,0 +1,10 @@ + + + +This package contains Spring's support for the Common Client Interface (CCI), +as defined by the J2EE Connector Architecture. It is conceptually similar +to the org.springframework.jdbc package, providing the same +levels of data access abstraction. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAware.java b/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAware.java new file mode 100644 index 00000000000..93472df4d79 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAware.java @@ -0,0 +1,44 @@ +/* + * 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.jca.context; + +import javax.resource.spi.BootstrapContext; + +/** + * Interface to be implemented by any object that wishes to be + * notified of the BootstrapContext (typically determined by the + * {@link ResourceAdapterApplicationContext}) that it runs in. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.resource.spi.BootstrapContext + */ +public interface BootstrapContextAware { + + /** + * Set the BootstrapContext that this object runs in. + *

Invoked after population of normal bean properties but before an init + * callback like InitializingBean's afterPropertiesSet or a + * custom init-method. Invoked after ApplicationContextAware's + * setApplicationContext. + * @param bootstrapContext BootstrapContext object to be used by this object + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.context.ApplicationContextAware#setApplicationContext + */ + void setBootstrapContext(BootstrapContext bootstrapContext); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAwareProcessor.java b/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAwareProcessor.java new file mode 100644 index 00000000000..5fa12422f56 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/BootstrapContextAwareProcessor.java @@ -0,0 +1,60 @@ +/* + * 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.jca.context; + +import javax.resource.spi.BootstrapContext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} + * implementation that passes the BootstrapContext to beans that implement + * the {@link BootstrapContextAware} interface. + * + *

{@link ResourceAdapterApplicationContext} automatically registers + * this processor with its underlying bean factory. + * + * @author Juergen Hoeller + * @since 2.5 + * @see BootstrapContextAware + */ +class BootstrapContextAwareProcessor implements BeanPostProcessor { + + private final BootstrapContext bootstrapContext; + + + /** + * Create a new BootstrapContextAwareProcessor for the given context. + */ + public BootstrapContextAwareProcessor(BootstrapContext bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.bootstrapContext != null && bean instanceof BootstrapContextAware) { + ((BootstrapContextAware) bean).setBootstrapContext(this.bootstrapContext); + } + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/ResourceAdapterApplicationContext.java b/org.springframework.transaction/src/main/java/org/springframework/jca/context/ResourceAdapterApplicationContext.java new file mode 100644 index 00000000000..924dab088e9 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/ResourceAdapterApplicationContext.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2008 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.jca.context; + +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.work.WorkManager; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.context.ApplicationContext} implementation + * for a JCA ResourceAdapter. Needs to be initialized with the JCA + * {@link javax.resource.spi.BootstrapContext}, passing it on to + * Spring-managed beans that implement {@link BootstrapContextAware}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see SpringContextResourceAdapter + * @see BootstrapContextAware + */ +public class ResourceAdapterApplicationContext extends GenericApplicationContext { + + private final BootstrapContext bootstrapContext; + + + /** + * Create a new ResourceAdapterApplicationContext for the given BootstrapContext. + * @param bootstrapContext the JCA BootstrapContext that the ResourceAdapter + * has been started with + */ + public ResourceAdapterApplicationContext(BootstrapContext bootstrapContext) { + Assert.notNull(bootstrapContext, "BootstrapContext must not be null"); + this.bootstrapContext = bootstrapContext; + } + + + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + beanFactory.addBeanPostProcessor(new BootstrapContextAwareProcessor(this.bootstrapContext)); + beanFactory.ignoreDependencyInterface(BootstrapContextAware.class); + beanFactory.registerResolvableDependency(BootstrapContext.class, this.bootstrapContext); + + // JCA WorkManager resolved lazily - may not be available. + beanFactory.registerResolvableDependency(WorkManager.class, new ObjectFactory() { + public Object getObject() { + return bootstrapContext.getWorkManager(); + } + }); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java new file mode 100644 index 00000000000..ae041d3934e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java @@ -0,0 +1,221 @@ +/* + * Copyright 2002-2008 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.jca.context; + +import javax.resource.NotSupportedException; +import javax.resource.ResourceException; +import javax.resource.spi.ActivationSpec; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.ResourceAdapterInternalException; +import javax.resource.spi.endpoint.MessageEndpointFactory; +import javax.transaction.xa.XAResource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.StringUtils; + +/** + * JCA 1.5 {@link javax.resource.spi.ResourceAdapter} implementation + * that loads a Spring {@link org.springframework.context.ApplicationContext}, + * starting and stopping Spring-managed beans as part of the ResourceAdapter's + * lifecycle. + * + *

Ideal for application contexts that do not need any HTTP entry points + * but rather just consist of message endpoints and scheduled jobs etc. + * Beans in such a context may use application server resources such as the + * JTA transaction manager and JNDI-bound JDBC DataSources and JMS + * ConnectionFactory instances, and may also register with the platform's + * JMX server - all through Spring's standard transaction management and + * JNDI and JMX support facilities. + * + *

If the need for scheduling asynchronous work arises, consider using + * Spring's {@link org.springframework.jca.work.WorkManagerTaskExecutor} + * as a standard bean definition, to be injected into application beans + * through dependency injection. This WorkManagerTaskExecutor will automatically + * use the JCA WorkManager from the BootstrapContext that has been provided + * to this ResourceAdapter. + * + *

The JCA {@link javax.resource.spi.BootstrapContext} may also be + * accessed directly, through application components that implement the + * {@link BootstrapContextAware} interface. When deployed using this + * ResourceAdapter, the BootstrapContext is guaranteed to be passed on + * to such components. + * + *

This ResourceAdapter is to be defined in a "META-INF/ra.xml" file + * within a J2EE ".rar" deployment unit like as follows: + * + *

+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <connector xmlns="http://java.sun.com/xml/ns/j2ee"
+ *		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ *		 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd"
+ *		 version="1.5">
+ *	 <vendor-name>Spring Framework</vendor-name>
+ *	 <eis-type>Spring Connector</eis-type>
+ *	 <resourceadapter-version>1.0</resourceadapter-version>
+ *	 <resourceadapter>
+ *		 <resourceadapter-class>org.springframework.jca.context.SpringContextResourceAdapter</resourceadapter-class>
+ *		 <config-property>
+ *			 <config-property-name>ContextConfigLocation</config-property-name>
+ *			 <config-property-type>java.lang.String</config-property-type>
+ *			 <config-property-value>META-INF/applicationContext.xml</config-property-value>
+ *		 </config-property>
+ *	 </resourceadapter>
+ * </connector>
+ * + * Note that "META-INF/applicationContext.xml" is the default context config + * location, so it doesn't have to specified unless you intend to specify + * different/additional config files. So in the default case, you may remove + * the entire config-property section above. + * + *

For simple deployment needs, all you need to do is the following: + * Package all application classes into a RAR file (which is just a standard + * JAR file with a different file extension), add all required library jars + * into the root of the RAR archive, add a "META-INF/ra.xml" deployment + * descriptor as shown above as well as the corresponding Spring XML bean + * definition file(s) (typically "META-INF/applicationContext.xml"), + * and drop the resulting RAR file into your application server's + * deployment directory! + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setContextConfigLocation + * @see #loadBeanDefinitions + * @see ResourceAdapterApplicationContext + */ +public class SpringContextResourceAdapter implements ResourceAdapter { + + /** + * Any number of these characters are considered delimiters between + * multiple context config paths in a single String value. + * @see #setContextConfigLocation + */ + public static final String CONFIG_LOCATION_DELIMITERS = ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS; + + public static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "META-INF/applicationContext.xml"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION; + + private ConfigurableApplicationContext applicationContext; + + + /** + * Set the location of the context configuration files, within the + * resource adapter's deployment unit. This can be a delimited + * String that consists of multiple resource location, separated + * by commas, semicolons, whitespace, or line breaks. + *

This can be specified as "ContextConfigLocation" config + * property in the ra.xml deployment descriptor. + *

The default is "classpath:META-INF/applicationContext.xml". + */ + public void setContextConfigLocation(String contextConfigLocation) { + this.contextConfigLocation = contextConfigLocation; + } + + /** + * Return the specified context configuration files. + */ + protected String getContextConfigLocation() { + return this.contextConfigLocation; + } + + + /** + * This implementation loads a Spring ApplicationContext through the + * {@link #createApplicationContext} template method. + */ + public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException { + if (logger.isInfoEnabled()) { + logger.info("Starting SpringContextResourceAdapter with BootstrapContext: " + bootstrapContext); + } + this.applicationContext = createApplicationContext(bootstrapContext); + } + + /** + * Build a Spring ApplicationContext for the given JCA BootstrapContext. + *

The default implementation builds a {@link ResourceAdapterApplicationContext} + * and delegates to {@link #loadBeanDefinitions} for actually parsing the + * specified configuration files. + * @param bootstrapContext this ResourceAdapter's BootstrapContext + * @return the Spring ApplicationContext instance + */ + protected ConfigurableApplicationContext createApplicationContext(BootstrapContext bootstrapContext) { + ResourceAdapterApplicationContext applicationContext = + new ResourceAdapterApplicationContext(bootstrapContext); + // Set ResourceAdapter's ClassLoader as bean class loader. + applicationContext.setClassLoader(getClass().getClassLoader()); + // Extract individual config locations. + String[] configLocations = + StringUtils.tokenizeToStringArray(getContextConfigLocation(), CONFIG_LOCATION_DELIMITERS); + if (configLocations != null) { + loadBeanDefinitions(applicationContext, configLocations); + } + applicationContext.refresh(); + return applicationContext; + } + + /** + * Load the bean definitions into the given registry, + * based on the specified configuration files. + * @param registry the registry to load into + * @param configLocations the parsed config locations + * @see #setContextConfigLocation + */ + protected void loadBeanDefinitions(BeanDefinitionRegistry registry, String[] configLocations) { + new XmlBeanDefinitionReader(registry).loadBeanDefinitions(configLocations); + } + + /** + * This implementation closes the Spring ApplicationContext. + */ + public void stop() { + logger.info("Stopping SpringContextResourceAdapter"); + this.applicationContext.close(); + } + + + /** + * This implementation always throws a NotSupportedException. + */ + public void endpointActivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) + throws ResourceException { + + throw new NotSupportedException("SpringContextResourceAdapter does not support message endpoints"); + } + + /** + * This implementation does nothing. + */ + public void endpointDeactivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) { + } + + /** + * This implementation always returns null. + */ + public XAResource[] getXAResources(ActivationSpec[] activationSpecs) throws ResourceException { + return null; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/context/package.html new file mode 100644 index 00000000000..6cda2fed2a5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/package.html @@ -0,0 +1,8 @@ + + + +Integration package that allows for deploying a Spring application context +as a JCA 1.5 compliant RAR file. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/ra.xml b/org.springframework.transaction/src/main/java/org/springframework/jca/context/ra.xml new file mode 100644 index 00000000000..54f0b7a76d8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/ra.xml @@ -0,0 +1,17 @@ + + + Spring Framework + Spring Connector + 1.0 + + org.springframework.jca.context.SpringContextResourceAdapter + + ContextConfigLocation + java.lang.String + META-INF/applicationContext.xml + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java new file mode 100644 index 00000000000..1edef5098bd --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java @@ -0,0 +1,303 @@ +/* + * 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.jca.endpoint; + +import java.lang.reflect.Method; + +import javax.resource.ResourceException; +import javax.resource.spi.ApplicationServerInternalException; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.endpoint.MessageEndpoint; +import javax.resource.spi.endpoint.MessageEndpointFactory; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAResource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.transaction.jta.SimpleTransactionFactory; +import org.springframework.transaction.jta.TransactionFactory; + +/** + * Abstract base implementation of the JCA 1.5 + * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface, + * providing transaction management capabilities as well as ClassLoader + * exposure for endpoint invocations. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setTransactionManager + */ +public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private TransactionFactory transactionFactory; + + private String transactionName; + + private int transactionTimeout = -1; + + + /** + * Set the the XA transaction manager to use for wrapping endpoint + * invocations, enlisting the endpoint resource in each such transaction. + *

The passed-in object may be a transaction manager which implements + * Spring's {@link org.springframework.transaction.jta.TransactionFactory} + * interface, or a plain {@link javax.transaction.TransactionManager}. + *

If no transaction manager is specified, the endpoint invocation + * will simply not be wrapped in an XA transaction. Check out your + * resource provider's ActivationSpec documentation for local + * transaction options of your particular provider. + * @see #setTransactionName + * @see #setTransactionTimeout + */ + public void setTransactionManager(Object transactionManager) { + if (transactionManager instanceof TransactionFactory) { + this.transactionFactory = (TransactionFactory) transactionManager; + } + else if (transactionManager instanceof TransactionManager) { + this.transactionFactory = new SimpleTransactionFactory((TransactionManager) transactionManager); + } + else { + throw new IllegalArgumentException("Transaction manager [" + transactionManager + + "] is neither a [org.springframework.transaction.jta.TransactionFactory} nor a " + + "[javax.transaction.TransactionManager]"); + } + } + + /** + * Set the Spring TransactionFactory to use for wrapping endpoint + * invocations, enlisting the endpoint resource in each such transaction. + *

Alternatively, specify an appropriate transaction manager through + * the {@link #setTransactionManager "transactionManager"} property. + *

If no transaction factory is specified, the endpoint invocation + * will simply not be wrapped in an XA transaction. Check out your + * resource provider's ActivationSpec documentation for local + * transaction options of your particular provider. + * @see #setTransactionName + * @see #setTransactionTimeout + */ + public void setTransactionFactory(TransactionFactory transactionFactory) { + this.transactionFactory = transactionFactory; + } + + /** + * Specify the name of the transaction, if any. + *

Default is none. A specified name will be passed on to the transaction + * manager, allowing to identify the transaction in a transaction monitor. + */ + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + /** + * Specify the transaction timeout, if any. + *

Default is -1: rely on the transaction manager's default timeout. + * Specify a concrete timeout to restrict the maximum duration of each + * endpoint invocation. + */ + public void setTransactionTimeout(int transactionTimeout) { + this.transactionTimeout = transactionTimeout; + } + + + /** + * This implementation returns true if a transaction manager + * has been specified; false otherwise. + * @see #setTransactionManager + * @see #setTransactionFactory + */ + public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException { + return (this.transactionFactory != null); + } + + /** + * This implementation delegates to {@link #createEndpointInternal()}, + * initializing the endpoint's XAResource before the endpoint gets invoked. + */ + public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { + AbstractMessageEndpoint endpoint = createEndpointInternal(); + endpoint.initXAResource(xaResource); + return endpoint; + } + + /** + * Create the actual endpoint instance, as a subclass of the + * {@link AbstractMessageEndpoint} inner class of this factory. + * @return the actual endpoint instance (never null) + * @throws UnavailableException if no endpoint is available at present + */ + protected abstract AbstractMessageEndpoint createEndpointInternal() + throws UnavailableException; + + + /** + * Inner class for actual endpoint implementations, based on template + * method to allow for any kind of concrete endpoint implementation. + */ + protected abstract class AbstractMessageEndpoint implements MessageEndpoint { + + private TransactionDelegate transactionDelegate; + + private boolean beforeDeliveryCalled = false; + + private ClassLoader previousContextClassLoader; + + /** + * Initialize this endpoint's TransactionDelegate. + * @param xaResource the XAResource for this endpoint + */ + void initXAResource(XAResource xaResource) { + this.transactionDelegate = new TransactionDelegate(xaResource); + } + + /** + * This beforeDelivery implementation starts a transaction, + * if necessary, and exposes the endpoint ClassLoader as current + * thread context ClassLoader. + *

Note that the JCA 1.5 specification does not require a ResourceAdapter + * to call this method before invoking the concrete endpoint. If this method + * has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the + * concrete endpoint method should call beforeDelivery and its + * sibling {@link #afterDelivery()} explicitly, as part of its own processing. + */ + public void beforeDelivery(Method method) throws ResourceException { + this.beforeDeliveryCalled = true; + try { + this.transactionDelegate.beginTransaction(); + } + catch (Throwable ex) { + throw new ApplicationServerInternalException("Failed to begin transaction", ex); + } + Thread currentThread = Thread.currentThread(); + this.previousContextClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(getEndpointClassLoader()); + } + + /** + * Template method for exposing the endpoint's ClassLoader + * (typically the ClassLoader that the message listener class + * has been loaded with). + * @return the endpoint ClassLoader (never null) + */ + protected abstract ClassLoader getEndpointClassLoader(); + + /** + * Return whether the {@link #beforeDelivery} method of this endpoint + * has already been called. + */ + protected final boolean hasBeforeDeliveryBeenCalled() { + return this.beforeDeliveryCalled; + } + + /** + * Callback method for notifying the endpoint base class + * that the concrete endpoint invocation led to an exception. + *

To be invoked by subclasses in case of the concrete + * endpoint throwing an exception. + * @param ex the exception thrown from the concrete endpoint + */ + protected final void onEndpointException(Throwable ex) { + this.transactionDelegate.setRollbackOnly(); + } + + /** + * This afterDelivery implementation resets the thread context + * ClassLoader and completes the transaction, if any. + *

Note that the JCA 1.5 specification does not require a ResourceAdapter + * to call this method after invoking the concrete endpoint. See the + * explanation in {@link #beforeDelivery}'s javadoc. + */ + public void afterDelivery() throws ResourceException { + this.beforeDeliveryCalled = false; + Thread.currentThread().setContextClassLoader(this.previousContextClassLoader); + this.previousContextClassLoader = null; + try { + this.transactionDelegate.endTransaction(); + } + catch (Throwable ex) { + throw new ApplicationServerInternalException("Failed to complete transaction", ex); + } + } + + public void release() { + try { + this.transactionDelegate.setRollbackOnly(); + this.transactionDelegate.endTransaction(); + } + catch (Throwable ex) { + logger.error("Could not complete unfinished transaction on endpoint release", ex); + } + } + } + + + /** + * Private inner class that performs the actual transaction handling, + * including enlistment of the endpoint's XAResource. + */ + private class TransactionDelegate { + + private XAResource xaResource; + + private Transaction transaction; + + private boolean rollbackOnly; + + public TransactionDelegate(XAResource xaResource) { + if (transactionFactory != null && xaResource == null) { + throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " + + "transaction management. Check your ResourceAdapter's configuration."); + } + this.xaResource = xaResource; + } + + public void beginTransaction() throws Exception { + if (transactionFactory != null) { + this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout); + this.transaction.enlistResource(this.xaResource); + } + } + + public void setRollbackOnly() { + if (this.transaction != null) { + this.rollbackOnly = true; + } + } + + public void endTransaction() throws Exception { + if (this.transaction != null) { + try { + if (this.rollbackOnly) { + this.transaction.rollback(); + } + else { + this.transaction.commit(); + } + } + finally { + this.transaction = null; + this.rollbackOnly = false; + } + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java new file mode 100644 index 00000000000..9631c1533ff --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java @@ -0,0 +1,155 @@ +/* + * 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.jca.endpoint; + +import javax.resource.ResourceException; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.endpoint.MessageEndpoint; +import javax.transaction.xa.XAResource; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.util.ReflectionUtils; + +/** + * Generic implementation of the JCA 1.5 + * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface, + * providing transaction management capabilities for any kind of message + * listener object (e.g. {@link javax.jms.MessageListener} objects or + * {@link javax.resource.cci.MessageListener} objects. + * + *

Uses AOP proxies for concrete endpoint instances, simply wrapping + * the specified message listener object and exposing all of its implemented + * interfaces on the endpoint instance. + * + *

Typically used with Spring's {@link GenericMessageEndpointManager}, + * but not tied to it. As a consequence, this endpoint factory could + * also be used with programmatic endpoint management on a native + * {@link javax.resource.spi.ResourceAdapter} instance. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setMessageListener + * @see #setTransactionManager + * @see GenericMessageEndpointManager + */ +public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactory { + + private Object messageListener; + + + /** + * Specify the message listener object that the endpoint should expose + * (e.g. a {@link javax.jms.MessageListener} objects or + * {@link javax.resource.cci.MessageListener} implementation). + */ + public void setMessageListener(Object messageListener) { + this.messageListener = messageListener; + } + + /** + * Wrap each concrete endpoint instance with an AOP proxy, + * exposing the message listener's interfaces as well as the + * endpoint SPI through an AOP introduction. + */ + public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { + GenericMessageEndpoint endpoint = (GenericMessageEndpoint) super.createEndpoint(xaResource); + ProxyFactory proxyFactory = new ProxyFactory(this.messageListener); + DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(endpoint); + introduction.suppressInterface(MethodInterceptor.class); + proxyFactory.addAdvice(introduction); + return (MessageEndpoint) proxyFactory.getProxy(); + } + + /** + * Creates a concrete generic message endpoint, internal to this factory. + */ + protected AbstractMessageEndpoint createEndpointInternal() throws UnavailableException { + return new GenericMessageEndpoint(); + } + + + /** + * Private inner class that implements the concrete generic message endpoint, + * as an AOP Alliance MethodInterceptor that will be invoked by a proxy. + */ + private class GenericMessageEndpoint extends AbstractMessageEndpoint implements MethodInterceptor { + + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + boolean applyDeliveryCalls = !hasBeforeDeliveryBeenCalled(); + if (applyDeliveryCalls) { + try { + beforeDelivery(null); + } + catch (ResourceException ex) { + if (ReflectionUtils.declaresException(methodInvocation.getMethod(), ex.getClass())) { + throw ex; + } + else { + throw new InternalResourceException(ex); + } + } + } + try { + return methodInvocation.proceed(); + } + catch (Throwable ex) { + onEndpointException(ex); + throw ex; + } + finally { + if (applyDeliveryCalls) { + try { + afterDelivery(); + } + catch (ResourceException ex) { + if (ReflectionUtils.declaresException(methodInvocation.getMethod(), ex.getClass())) { + throw ex; + } + else { + throw new InternalResourceException(ex); + } + } + } + } + } + + protected ClassLoader getEndpointClassLoader() { + return messageListener.getClass().getClassLoader(); + } + } + + + /** + * Internal exception thrown when a ResourceExeption has been encountered + * during the endpoint invocation. + *

Will only be used if the ResourceAdapter does not invoke the + * endpoint's beforeDelivery and afterDelivery + * directly, leavng it up to the concrete endpoint to apply those - + * and to handle any ResourceExceptions thrown from them. + */ + public static class InternalResourceException extends RuntimeException { + + protected InternalResourceException(ResourceException cause) { + super(cause); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java new file mode 100644 index 00000000000..2938a7b03cc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java @@ -0,0 +1,298 @@ +/* + * 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.jca.endpoint; + +import javax.resource.ResourceException; +import javax.resource.spi.ActivationSpec; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.endpoint.MessageEndpointFactory; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.Lifecycle; + +/** + * Generic bean that manages JCA 1.5 message endpoints within a Spring + * application context, activating and deactivating the endpoint as part + * of the application context's lifecycle. + * + *

This class is completely generic in that it may work with any + * ResourceAdapter, any MessageEndpointFactory, and any ActivationSpec. + * It can be configured in standard bean style, for example through + * Spring's XML bean definition format, as follows: + * + *

+ * <bean class="org.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="org.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + * In this example, Spring's own {@link GenericMessageEndpointFactory} is used + * to point to a standard message listener object that happens to be supported + * by the specified target ResourceAdapter: in this case, a JMS + * {@link javax.jms.MessageListener} object as supported by the ActiveMQ + * message broker, defined as a Spring bean: + * + *
+ * <bean id="messageListener" class="com.myorg.messaging.myMessageListener">
+ *   ...
+ * </bean>
+ * + * The target ResourceAdapter may be configured as a local Spring bean as well + * (the typical case) or obtained from JNDI (e.g. on WebLogic). For the + * example above, a local ResourceAdapter bean could be defined as follows + * (matching the "resourceAdapter" bean reference above): + * + *
+ * <bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
+ *   <property name="resourceAdapter">
+ *     <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
+ *       <property name="serverUrl" value="tcp://localhost:61616"/>
+ *     </bean>
+ *   </property>
+ *   <property name="workManager">
+ *     <bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
+ *   </property>
+ * </bean>
+ * + * For a different target resource, the configuration would simply point to a + * different ResourceAdapter and a different ActivationSpec object (which are + * both specific to the resource provider), and possibly a different message + * listener (e.g. a CCI {@link javax.resource.cci.MessageListener} for a + * resource adapter which is based on the JCA Common Client Interface). + * + *

The asynchronous execution strategy can be customized through the + * "workManager" property on the ResourceAdapterFactoryBean (as shown above). + * Check out {@link org.springframework.jca.work.SimpleTaskWorkManager}'s + * javadoc for its configuration options; alternatively, any other + * JCA-compliant WorkManager can be used (e.g. Geronimo's). + * + *

Transactional execution is a responsibility of the concrete message endpoint, + * as built by the specified MessageEndpointFactory. {@link GenericMessageEndpointFactory} + * supports XA transaction participation through its "transactionManager" property, + * typically with a Spring {@link org.springframework.transaction.jta.JtaTransactionManager} + * or a plain {@link javax.transaction.TransactionManager} implementation specified there. + * + *

+ * <bean class="org.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="org.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *       <property name="transactionManager" ref="transactionManager"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ *
+ * <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
+ * + * Alternatively, check out your resource provider's ActivationSpec object, + * which should support local transactions through a provider-specific config flag, + * e.g. ActiveMQActivationSpec's "useRAManagedTransaction" bean property. + * + *
+ * <bean class="org.springframework.jca.endpoint.GenericMessageEndpointManager">
+ * 	 <property name="resourceAdapter" ref="resourceAdapter"/>
+ * 	 <property name="messageEndpointFactory">
+ *     <bean class="org.springframework.jca.endpoint.GenericMessageEndpointFactory">
+ *       <property name="messageListener" ref="messageListener"/>
+ *     </bean>
+ * 	 </property>
+ * 	 <property name="activationSpec">
+ *     <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
+ *       <property name="destination" value="myQueue"/>
+ *       <property name="destinationType" value="javax.jms.Queue"/>
+ *       <property name="useRAManagedTransaction" value="true"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.resource.spi.ResourceAdapter#endpointActivation + * @see javax.resource.spi.ResourceAdapter#endpointDeactivation + * @see javax.resource.spi.endpoint.MessageEndpointFactory + * @see javax.resource.spi.ActivationSpec + */ +public class GenericMessageEndpointManager implements InitializingBean, Lifecycle, DisposableBean { + + private ResourceAdapter resourceAdapter; + + private MessageEndpointFactory messageEndpointFactory; + + private ActivationSpec activationSpec; + + private boolean autoStartup = true; + + private boolean running = false; + + private final Object lifecycleMonitor = new Object(); + + + /** + * Set the JCA ResourceAdapter to manage endpoints for. + */ + public void setResourceAdapter(ResourceAdapter resourceAdapter) { + this.resourceAdapter = resourceAdapter; + } + + /** + * Return the JCA ResourceAdapter to manage endpoints for. + */ + public ResourceAdapter getResourceAdapter() { + return this.resourceAdapter; + } + + /** + * Set the JCA MessageEndpointFactory to activate, pointing to a + * MessageListener object that the endpoints will delegate to. + *

A MessageEndpointFactory instance may be shared across multiple + * endpoints (i.e. multiple GenericMessageEndpointManager instances), + * with different {@link #setActivationSpec ActivationSpec} objects applied. + * @see GenericMessageEndpointFactory#setMessageListener + */ + public void setMessageEndpointFactory(MessageEndpointFactory messageEndpointFactory) { + this.messageEndpointFactory = messageEndpointFactory; + } + + /** + * Return the JCA MessageEndpointFactory to activate. + */ + public MessageEndpointFactory getMessageEndpointFactory() { + return this.messageEndpointFactory; + } + + /** + * Set the JCA ActivationSpec to use for activating the endpoint. + *

Note that this ActivationSpec instance should not be shared + * across multiple ResourceAdapter instances. + */ + public void setActivationSpec(ActivationSpec activationSpec) { + this.activationSpec = activationSpec; + } + + /** + * Return the JCA ActivationSpec to use for activating the endpoint. + */ + public ActivationSpec getActivationSpec() { + return this.activationSpec; + } + + /** + * Set whether to auto-start the endpoint activation along with + * this endpoint manager's initialization. + *

Default is "true". Turn this flag off to defer the endpoint + * activation until an explicit {#start()} call. + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + + /** + * Prepares the message endpoint, and automatically activates it + * if the "autoStartup" flag is set to "true". + */ + public void afterPropertiesSet() throws ResourceException { + if (getResourceAdapter() == null) { + throw new IllegalArgumentException("Property 'resourceAdapter' is required"); + } + if (getMessageEndpointFactory() == null) { + throw new IllegalArgumentException("Property 'messageEndpointFactory' is required"); + } + ActivationSpec activationSpec = getActivationSpec(); + if (activationSpec == null) { + throw new IllegalArgumentException("Property 'activationSpec' is required"); + } + + if (activationSpec.getResourceAdapter() == null) { + activationSpec.setResourceAdapter(getResourceAdapter()); + } + else if (activationSpec.getResourceAdapter() != getResourceAdapter()) { + throw new IllegalArgumentException("ActivationSpec [" + activationSpec + + "] is associated with a different ResourceAdapter: " + activationSpec.getResourceAdapter()); + } + + if (this.autoStartup) { + start(); + } + } + + /** + * Activates the configured message endpoint. + */ + public void start() { + synchronized (this.lifecycleMonitor) { + if (!this.running) { + try { + getResourceAdapter().endpointActivation(getMessageEndpointFactory(), getActivationSpec()); + } + catch (ResourceException ex) { + IllegalStateException wrapped = new IllegalStateException("Could not activate message endpoint"); + wrapped.initCause(ex); + throw wrapped; + } + this.running = true; + } + } + } + + /** + * Deactivates the configured message endpoint. + */ + public void stop() { + synchronized (this.lifecycleMonitor) { + if (this.running) { + getResourceAdapter().endpointDeactivation(getMessageEndpointFactory(), getActivationSpec()); + this.running = false; + } + } + } + + /** + * Return whether the configured message endpoint is currently active. + */ + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.running; + } + } + + /** + * Deactivates the message endpoint, preparing it for shutdown. + */ + public void destroy() { + stop(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/package.html new file mode 100644 index 00000000000..52ed3c4f026 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/package.html @@ -0,0 +1,7 @@ + + + +This package provides a facility for generic JCA message endpoint management. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/support/LocalConnectionFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/jca/support/LocalConnectionFactoryBean.java new file mode 100644 index 00000000000..d9b4194f558 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/support/LocalConnectionFactoryBean.java @@ -0,0 +1,137 @@ +/* + * 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.jca.support; + +import javax.resource.ResourceException; +import javax.resource.spi.ConnectionManager; +import javax.resource.spi.ManagedConnectionFactory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that creates + * a local JCA connection factory in "non-managed" mode (as defined by the + * Java Connector Architecture specification). This is a direct alternative + * to a {@link org.springframework.jndi.JndiObjectFactoryBean} definition that + * obtains a connection factory handle from a J2EE server's naming environment. + * + *

The type of the connection factory is dependent on the actual connector: + * the connector can either expose its native API (such as a JDBC + * {@link javax.sql.DataSource} or a JMS {@link javax.jms.ConnectionFactory}) + * or follow the standard Common Client Interface (CCI), as defined by the JCA spec. + * The exposed interface in the CCI case is {@link javax.resource.cci.ConnectionFactory}. + * + *

In order to use this FactoryBean, you must specify the connector's + * {@link #setManagedConnectionFactory "managedConnectionFactory"} (usually + * configured as separate JavaBean), which will be used to create the actual + * connection factory reference as exposed to the application. Optionally, + * you can also specify a {@link #setConnectionManager "connectionManager"}, + * in order to use a custom ConnectionManager instead of the connector's default. + * + *

NOTE: In non-managed mode, a connector is not deployed on an + * application server, or more specificially not interacting with an application + * server. Consequently, it cannot use a J2EE server's system contracts: + * connection management, transaction management, and security management. + * A custom ConnectionManager implementation has to be used for applying those + * services in conjunction with a standalone transaction coordinator etc. + * + *

The connector will use a local ConnectionManager (included in the connector) + * by default, which cannot participate in global transactions due to the lack + * of XA enlistment. You need to specify an XA-capable ConnectionManager in + * order to make the connector interact with an XA transaction coordinator. + * Alternatively, simply use the native local transaction facilities of the + * exposed API (e.g. CCI local transactions), or use a corresponding + * implementation of Spring's PlatformTransactionManager SPI + * (e.g. {@link org.springframework.jca.cci.connection.CciLocalTransactionManager}) + * to drive local transactions. + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setManagedConnectionFactory + * @see #setConnectionManager + * @see javax.resource.cci.ConnectionFactory + * @see javax.resource.cci.Connection#getLocalTransaction + * @see org.springframework.jca.cci.connection.CciLocalTransactionManager + */ +public class LocalConnectionFactoryBean implements FactoryBean, InitializingBean { + + private ManagedConnectionFactory managedConnectionFactory; + + private ConnectionManager connectionManager; + + private Object connectionFactory; + + + /** + * Set the JCA ManagerConnectionFactory that should be used to create + * the desired connection factory. + *

The ManagerConnectionFactory will usually be set up as separate bean + * (potentially as inner bean), populated with JavaBean properties: + * a ManagerConnectionFactory is encouraged to follow the JavaBean pattern + * by the JCA specification, analogous to a JDBC DataSource and a JDO + * PersistenceManagerFactory. + *

Note that the ManagerConnectionFactory implementation might expect + * a reference to its JCA 1.5 ResourceAdapter, expressed through the + * {@link javax.resource.spi.ResourceAdapterAssociation} interface. + * Simply inject the corresponding ResourceAdapter instance into its + * "resourceAdapter" bean property in this case, before passing the + * ManagerConnectionFactory into this LocalConnectionFactoryBean. + * @see javax.resource.spi.ManagedConnectionFactory#createConnectionFactory() + */ + public void setManagedConnectionFactory(ManagedConnectionFactory managedConnectionFactory) { + this.managedConnectionFactory = managedConnectionFactory; + } + + /** + * Set the JCA ConnectionManager that should be used to create the + * desired connection factory. + *

A ConnectionManager implementation for local usage is often + * included with a JCA connector. Such an included ConnectionManager + * might be set as default, with no need to explicitly specify one. + * @see javax.resource.spi.ManagedConnectionFactory#createConnectionFactory(javax.resource.spi.ConnectionManager) + */ + public void setConnectionManager(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + public void afterPropertiesSet() throws ResourceException { + if (this.managedConnectionFactory == null) { + throw new IllegalArgumentException("Property 'managedConnectionFactory' is required"); + } + if (this.connectionManager != null) { + this.connectionFactory = this.managedConnectionFactory.createConnectionFactory(this.connectionManager); + } + else { + this.connectionFactory = this.managedConnectionFactory.createConnectionFactory(); + } + } + + + public Object getObject() { + return this.connectionFactory; + } + + public Class getObjectType() { + return (this.connectionFactory != null ? this.connectionFactory.getClass() : null); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/support/ResourceAdapterFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/jca/support/ResourceAdapterFactoryBean.java new file mode 100644 index 00000000000..e0a955e5d30 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/support/ResourceAdapterFactoryBean.java @@ -0,0 +1,148 @@ +/* + * 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.jca.support; + +import javax.resource.ResourceException; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.ResourceAdapter; +import javax.resource.spi.XATerminator; +import javax.resource.spi.work.WorkManager; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that bootstraps + * the specified JCA 1.5 {@link javax.resource.spi.ResourceAdapter}, + * starting it with a local {@link javax.resource.spi.BootstrapContext} + * and exposing it for bean references. It will also stop the ResourceAdapter + * on context shutdown. This corresponds to 'non-managed' bootstrap in a + * local environment, according to the JCA 1.5 specification. + * + *

This is essentially an adapter for bean-style bootstrapping of a + * JCA ResourceAdapter, allowing the BootstrapContext or its elements + * (such as the JCA WorkManager) to be specified through bean properties. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setResourceAdapter + * @see #setBootstrapContext + * @see #setWorkManager + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + * @see javax.resource.spi.ResourceAdapter#stop() + */ +public class ResourceAdapterFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + + private ResourceAdapter resourceAdapter; + + private BootstrapContext bootstrapContext; + + private WorkManager workManager; + + private XATerminator xaTerminator; + + + /** + * Specify the target JCA ResourceAdapter as class, to be instantiated + * with its default configuration. + *

Alternatively, specify a pre-configured ResourceAdapter instance + * through the "resourceAdapter" property. + * @see #setResourceAdapter + */ + public void setResourceAdapterClass(Class resourceAdapterClass) { + Assert.isAssignable(ResourceAdapter.class, resourceAdapterClass); + this.resourceAdapter = (ResourceAdapter) BeanUtils.instantiateClass(resourceAdapterClass); + } + + /** + * Specify the target JCA ResourceAdapter, passed in as configured instance + * which hasn't been started yet. This will typically happen as an + * inner bean definition, configuring the ResourceAdapter instance + * through its vendor-specific bean properties. + */ + public void setResourceAdapter(ResourceAdapter resourceAdapter) { + this.resourceAdapter = resourceAdapter; + } + + /** + * Specify the JCA BootstrapContext to use for starting the ResourceAdapter. + *

Alternatively, you can specify the individual parts (such as the + * JCA WorkManager) as individual references. + * @see #setWorkManager + * @see #setXaTerminator + */ + public void setBootstrapContext(BootstrapContext bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } + + /** + * Specify the JCA WorkManager to use for bootstrapping the ResourceAdapter. + * @see #setBootstrapContext + */ + public void setWorkManager(WorkManager workManager) { + this.workManager = workManager; + } + + /** + * Specify the JCA XATerminator to use for bootstrapping the ResourceAdapter. + * @see #setBootstrapContext + */ + public void setXaTerminator(XATerminator xaTerminator) { + this.xaTerminator = xaTerminator; + } + + + /** + * Builds the BootstrapContext and starts the ResourceAdapter with it. + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + */ + public void afterPropertiesSet() throws ResourceException { + if (this.resourceAdapter == null) { + throw new IllegalArgumentException("'resourceAdapter' or 'resourceAdapterClass' is required"); + } + if (this.bootstrapContext == null) { + this.bootstrapContext = new SimpleBootstrapContext(this.workManager, this.xaTerminator); + } + this.resourceAdapter.start(this.bootstrapContext); + } + + + public Object getObject() { + return this.resourceAdapter; + } + + public Class getObjectType() { + return (this.resourceAdapter != null ? this.resourceAdapter.getClass() : ResourceAdapter.class); + } + + public boolean isSingleton() { + return true; + } + + + /** + * Stops the ResourceAdapter. + * @see javax.resource.spi.ResourceAdapter#stop() + */ + public void destroy() { + this.resourceAdapter.stop(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/support/SimpleBootstrapContext.java b/org.springframework.transaction/src/main/java/org/springframework/jca/support/SimpleBootstrapContext.java new file mode 100644 index 00000000000..82d6a23bb1f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/support/SimpleBootstrapContext.java @@ -0,0 +1,80 @@ +/* + * 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.jca.support; + +import java.util.Timer; + +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.UnavailableException; +import javax.resource.spi.XATerminator; +import javax.resource.spi.work.WorkManager; + +/** + * Simple implementation of the JCA 1.5 {@link javax.resource.spi.BootstrapContext} + * interface, used for bootstrapping a JCA ResourceAdapter in a local environment. + * + *

Delegates to the given WorkManager and XATerminator, if any. Creates simple + * local instances of java.util.Timer. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext) + * @see ResourceAdapterFactoryBean + */ +public class SimpleBootstrapContext implements BootstrapContext { + + private WorkManager workManager; + + private XATerminator xaTerminator; + + + /** + * Create a new SimpleBootstrapContext for the given WorkManager, + * with no XATerminator available. + * @param workManager the JCA WorkManager to use (may be null) + */ + public SimpleBootstrapContext(WorkManager workManager) { + this.workManager = workManager; + } + + /** + * Create a new SimpleBootstrapContext for the given WorkManager and XATerminator. + * @param workManager the JCA WorkManager to use (may be null) + * @param xaTerminator the JCA XATerminator to use (may be null) + */ + public SimpleBootstrapContext(WorkManager workManager, XATerminator xaTerminator) { + this.workManager = workManager; + this.xaTerminator = xaTerminator; + } + + + public WorkManager getWorkManager() { + if (this.workManager == null) { + throw new IllegalStateException("No WorkManager available"); + } + return this.workManager; + } + + public XATerminator getXATerminator() { + return this.xaTerminator; + } + + public Timer createTimer() throws UnavailableException { + return new Timer(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/support/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/support/package.html new file mode 100644 index 00000000000..f73d8bf5f78 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/support/package.html @@ -0,0 +1,8 @@ + + + +Provides generic support classes for JCA usage within Spring, +mainly for local setup of a JCA ResourceAdapter and/or ConnectionFactory. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/DelegatingWork.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/DelegatingWork.java new file mode 100644 index 00000000000..9f3ee639b15 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/DelegatingWork.java @@ -0,0 +1,67 @@ +/* + * 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.jca.work; + +import javax.resource.spi.work.Work; + +import org.springframework.util.Assert; + +/** + * Simple Work adapter that delegates to a given Runnable. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see javax.resource.spi.work.Work + * @see Runnable + */ +public class DelegatingWork implements Work { + + private final Runnable delegate; + + + /** + * Create a new DelegatingWork. + * @param delegate the Runnable implementation to delegate to + */ + public DelegatingWork(Runnable delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + } + + /** + * Return the wrapped Runnable implementation. + */ + public final Runnable getDelegate() { + return this.delegate; + } + + + /** + * Delegates execution to the underlying Runnable. + */ + public void run() { + this.delegate.run(); + } + + /** + * This implementation is empty, since we expect the Runnable + * to terminate based on some specific shutdown signal. + */ + public void release() { + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java new file mode 100644 index 00000000000..223b7637343 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java @@ -0,0 +1,252 @@ +/* + * 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.jca.work; + +import javax.resource.spi.work.ExecutionContext; +import javax.resource.spi.work.Work; +import javax.resource.spi.work.WorkAdapter; +import javax.resource.spi.work.WorkCompletedException; +import javax.resource.spi.work.WorkEvent; +import javax.resource.spi.work.WorkException; +import javax.resource.spi.work.WorkListener; +import javax.resource.spi.work.WorkManager; +import javax.resource.spi.work.WorkRejectedException; + +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.TaskRejectedException; +import org.springframework.core.task.TaskTimeoutException; +import org.springframework.util.Assert; + +/** + * Simple JCA 1.5 {@link javax.resource.spi.work.WorkManager} implementation that + * delegates to a Spring {@link org.springframework.core.task.TaskExecutor}. + * Provides simple task execution including start timeouts, but without support + * for a JCA ExecutionContext (i.e. without support for imported transactions). + * + *

Uses a {@link org.springframework.core.task.SyncTaskExecutor} for {@link #doWork} + * calls and a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} + * for {@link #startWork} and {@link #scheduleWork} calls, by default. + * These default task executors can be overridden through configuration. + * + *

NOTE: This WorkManager does not provide thread pooling by default! + * Specify a {@link org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor} + * (or any other thread-pooling TaskExecutor) as "asyncTaskExecutor" in order to + * achieve actual thread pooling. + * + *

This WorkManager automatically detects a specified + * {@link org.springframework.core.task.AsyncTaskExecutor} implementation + * and uses its extended timeout functionality where appropriate. + * JCA WorkListeners are fully supported in any case. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setSyncTaskExecutor + * @see #setAsyncTaskExecutor + */ +public class SimpleTaskWorkManager implements WorkManager { + + private TaskExecutor syncTaskExecutor = new SyncTaskExecutor(); + + private TaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor(); + + + /** + * Specify the TaskExecutor to use for synchronous work execution + * (i.e. {@link #doWork} calls). + *

Default is a {@link org.springframework.core.task.SyncTaskExecutor}. + */ + public void setSyncTaskExecutor(TaskExecutor syncTaskExecutor) { + this.syncTaskExecutor = syncTaskExecutor; + } + + /** + * Specify the TaskExecutor to use for asynchronous work execution + * (i.e. {@link #startWork} and {@link #scheduleWork} calls). + *

This will typically (but not necessarily) be an + * {@link org.springframework.core.task.AsyncTaskExecutor} implementation. + * Default is a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}. + */ + public void setAsyncTaskExecutor(TaskExecutor asyncTaskExecutor) { + this.asyncTaskExecutor = asyncTaskExecutor; + } + + + public void doWork(Work work) throws WorkException { + doWork(work, WorkManager.INDEFINITE, null, null); + } + + public void doWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.syncTaskExecutor != null, "No 'syncTaskExecutor' set"); + executeWork(this.syncTaskExecutor, work, startTimeout, false, executionContext, workListener); + } + + public long startWork(Work work) throws WorkException { + return startWork(work, WorkManager.INDEFINITE, null, null); + } + + public long startWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.asyncTaskExecutor != null, "No 'asyncTaskExecutor' set"); + return executeWork(this.asyncTaskExecutor, work, startTimeout, true, executionContext, workListener); + } + + public void scheduleWork(Work work) throws WorkException { + scheduleWork(work, WorkManager.INDEFINITE, null, null); + } + + public void scheduleWork(Work work, long startTimeout, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + Assert.state(this.asyncTaskExecutor != null, "No 'asyncTaskExecutor' set"); + executeWork(this.asyncTaskExecutor, work, startTimeout, false, executionContext, workListener); + } + + + /** + * Execute the given Work on the specified TaskExecutor. + * @param taskExecutor the TaskExecutor to use + * @param work the Work to execute + * @param startTimeout the time duration within which the Work is supposed to start + * @param blockUntilStarted whether to block until the Work has started + * @param executionContext the JCA ExecutionContext for the given Work + * @param workListener the WorkListener to clal for the given Work + * @return the time elapsed from Work acceptance until start of execution + * (or -1 if not applicable or not known) + * @throws WorkException if the TaskExecutor did not accept the Work + */ + protected long executeWork(TaskExecutor taskExecutor, Work work, long startTimeout, + boolean blockUntilStarted, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + if (executionContext != null && executionContext.getXid() != null) { + throw new WorkException("SimpleTaskWorkManager does not supported imported XIDs: " + executionContext.getXid()); + } + WorkListener workListenerToUse = workListener; + if (workListenerToUse == null) { + workListenerToUse = new WorkAdapter(); + } + + boolean isAsync = (taskExecutor instanceof AsyncTaskExecutor); + DelegatingWorkAdapter workHandle = new DelegatingWorkAdapter(work, workListenerToUse, !isAsync); + try { + if (isAsync) { + ((AsyncTaskExecutor) taskExecutor).execute(workHandle, startTimeout); + } + else { + taskExecutor.execute(workHandle); + } + } + catch (TaskTimeoutException ex) { + WorkException wex = new WorkRejectedException("TaskExecutor rejected Work because of timeout: " + work, ex); + wex.setErrorCode(WorkException.START_TIMED_OUT); + workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); + throw wex; + } + catch (TaskRejectedException ex) { + WorkException wex = new WorkRejectedException("TaskExecutor rejected Work: " + work, ex); + wex.setErrorCode(WorkException.INTERNAL); + workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); + throw wex; + } + catch (Throwable ex) { + WorkException wex = new WorkException("TaskExecutor failed to execute Work: " + work, ex); + wex.setErrorCode(WorkException.INTERNAL); + throw wex; + } + if (isAsync) { + workListenerToUse.workAccepted(new WorkEvent(this, WorkEvent.WORK_ACCEPTED, work, null)); + } + + if (blockUntilStarted) { + long acceptanceTime = System.currentTimeMillis(); + synchronized (workHandle.monitor) { + try { + while (!workHandle.started) { + workHandle.monitor.wait(); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + return (System.currentTimeMillis() - acceptanceTime); + } + else { + return WorkManager.UNKNOWN; + } + } + + + /** + * Work adapter that supports start timeouts and WorkListener callbacks + * for a given Work that it delegates to. + */ + private static class DelegatingWorkAdapter implements Work { + + private final Work work; + + private final WorkListener workListener; + + private final boolean acceptOnExecution; + + public final Object monitor = new Object(); + + public boolean started = false; + + public DelegatingWorkAdapter(Work work, WorkListener workListener, boolean acceptOnExecution) { + this.work = work; + this.workListener = workListener; + this.acceptOnExecution = acceptOnExecution; + } + + public void run() { + if (this.acceptOnExecution) { + this.workListener.workAccepted(new WorkEvent(this, WorkEvent.WORK_ACCEPTED, work, null)); + } + synchronized (this.monitor) { + this.started = true; + this.monitor.notify(); + } + this.workListener.workStarted(new WorkEvent(this, WorkEvent.WORK_STARTED, this.work, null)); + try { + this.work.run(); + } + catch (RuntimeException ex) { + this.workListener.workCompleted( + new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, new WorkCompletedException(ex))); + throw ex; + } + catch (Error err) { + this.workListener.workCompleted( + new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, new WorkCompletedException(err))); + throw err; + } + this.workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_COMPLETED, this.work, null)); + } + + public void release() { + this.work.release(); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java new file mode 100644 index 00000000000..b2121597f67 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java @@ -0,0 +1,275 @@ +/* + * Copyright 2002-2008 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.jca.work; + +import javax.naming.NamingException; +import javax.resource.spi.BootstrapContext; +import javax.resource.spi.work.ExecutionContext; +import javax.resource.spi.work.Work; +import javax.resource.spi.work.WorkException; +import javax.resource.spi.work.WorkListener; +import javax.resource.spi.work.WorkManager; +import javax.resource.spi.work.WorkRejectedException; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.TaskRejectedException; +import org.springframework.core.task.TaskTimeoutException; +import org.springframework.jca.context.BootstrapContextAware; +import org.springframework.jndi.JndiLocatorSupport; +import org.springframework.scheduling.SchedulingException; +import org.springframework.scheduling.SchedulingTaskExecutor; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.core.task.TaskExecutor} implementation + * that delegates to a JCA 1.5 WorkManager, implementing the + * {@link javax.resource.spi.work.WorkManager} interface. + * + *

This is mainly intended for use within a JCA ResourceAdapter implementation, + * but may also be used in a standalone environment, delegating to a locally + * embedded WorkManager implementation (such as Geronimo's). + * + *

Also implements the JCA 1.5 WorkManager interface itself, delegating all + * calls to the target WorkManager. Hence, a caller can choose whether it wants + * to talk to this executor through the Spring TaskExecutor interface or the + * JCA 1.5 WorkManager interface. + * + *

This adapter is also capable of obtaining a JCA WorkManager from JNDI. + * This is for example appropriate on the Geronimo application server, where + * WorkManager GBeans (e.g. Geronimo's default "DefaultWorkManager" GBean) + * can be linked into the J2EE environment through "gbean-ref" entries + * in the geronimo-web.xml deployment descriptor. + * + *

On JBoss and GlassFish, obtaining the default JCA WorkManager + * requires special lookup steps. See the + * {@link org.springframework.jca.work.jboss.JBossWorkManagerTaskExecutor} + * {@link org.springframework.jca.work.glassfish.GlassFishWorkManagerTaskExecutor} + * classes which are the direct equivalent of this generic JCA adapter class. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #setWorkManager + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ +public class WorkManagerTaskExecutor extends JndiLocatorSupport + implements SchedulingTaskExecutor, AsyncTaskExecutor, WorkManager, BootstrapContextAware, InitializingBean { + + private WorkManager workManager; + + private String workManagerName; + + private boolean blockUntilStarted = false; + + private boolean blockUntilCompleted = false; + + private WorkListener workListener; + + + /** + * Create a new WorkManagerTaskExecutor, expecting bean-style configuration. + * @see #setWorkManager + */ + public WorkManagerTaskExecutor() { + } + + /** + * Create a new WorkManagerTaskExecutor for the given WorkManager. + * @param workManager the JCA WorkManager to delegate to + */ + public WorkManagerTaskExecutor(WorkManager workManager) { + setWorkManager(workManager); + } + + + /** + * Specify the JCA WorkManager instance to delegate to. + */ + public void setWorkManager(WorkManager workManager) { + Assert.notNull(workManager, "WorkManager must not be null"); + this.workManager = workManager; + } + + /** + * Set the JNDI name of the JCA WorkManager. + *

This can either be a fully qualified JNDI name, + * or the JNDI name relative to the current environment + * naming context if "resourceRef" is set to "true". + * @see #setWorkManager + * @see #setResourceRef + */ + public void setWorkManagerName(String workManagerName) { + this.workManagerName = workManagerName; + } + + /** + * Specify the JCA BootstrapContext that contains the + * WorkManager to delegate to. + */ + public void setBootstrapContext(BootstrapContext bootstrapContext) { + Assert.notNull(bootstrapContext, "BootstrapContext must not be null"); + this.workManager = bootstrapContext.getWorkManager(); + } + + /** + * Set whether to let {@link #execute} block until the work + * has been actually started. + *

Uses the JCA startWork operation underneath, + * instead of the default scheduleWork. + * @see javax.resource.spi.work.WorkManager#startWork + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ + public void setBlockUntilStarted(boolean blockUntilStarted) { + this.blockUntilStarted = blockUntilStarted; + } + + /** + * Set whether to let {@link #execute} block until the work + * has been completed. + *

Uses the JCA doWork operation underneath, + * instead of the default scheduleWork. + * @see javax.resource.spi.work.WorkManager#doWork + * @see javax.resource.spi.work.WorkManager#scheduleWork + */ + public void setBlockUntilCompleted(boolean blockUntilCompleted) { + this.blockUntilCompleted = blockUntilCompleted; + } + + /** + * Specify a JCA 1.5 WorkListener to apply, if any. + *

This shared WorkListener instance will be passed on to the + * WorkManager by all {@link #execute} calls on this TaskExecutor. + */ + public void setWorkListener(WorkListener workListener) { + this.workListener = workListener; + } + + public void afterPropertiesSet() throws NamingException { + if (this.workManager == null) { + if (this.workManagerName != null) { + this.workManager = (WorkManager) lookup(this.workManagerName, WorkManager.class); + } + else { + this.workManager = getDefaultWorkManager(); + } + } + } + + /** + * Obtain a default WorkManager to delegate to. + * Called if no explicit WorkManager or WorkManager JNDI name has been specified. + *

The default implementation returns a {@link SimpleTaskWorkManager}. + * Can be overridden in subclasses. + */ + protected WorkManager getDefaultWorkManager() { + return new SimpleTaskWorkManager(); + } + + + //------------------------------------------------------------------------- + // Implementation of the Spring SchedulingTaskExecutor interface + //------------------------------------------------------------------------- + + public void execute(Runnable task) { + execute(task, TIMEOUT_INDEFINITE); + } + + public void execute(Runnable task, long startTimeout) { + Assert.state(this.workManager != null, "No WorkManager specified"); + Work work = new DelegatingWork(task); + try { + if (this.blockUntilCompleted) { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.doWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.doWork(work); + } + } + else if (this.blockUntilStarted) { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.startWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.startWork(work); + } + } + else { + if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { + this.workManager.scheduleWork(work, startTimeout, null, this.workListener); + } + else { + this.workManager.scheduleWork(work); + } + } + } + catch (WorkRejectedException ex) { + if (WorkException.START_TIMED_OUT.equals(ex.getErrorCode())) { + throw new TaskTimeoutException("JCA WorkManager rejected task because of timeout: " + task, ex); + } + else { + throw new TaskRejectedException("JCA WorkManager rejected task: " + task, ex); + } + } + catch (WorkException ex) { + throw new SchedulingException("Could not schedule task on JCA WorkManager", ex); + } + } + + /** + * This task executor prefers short-lived work units. + */ + public boolean prefersShortLivedTasks() { + return true; + } + + + //------------------------------------------------------------------------- + // Implementation of the JCA WorkManager interface + //------------------------------------------------------------------------- + + public void doWork(Work work) throws WorkException { + this.workManager.doWork(work); + } + + public void doWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + this.workManager.doWork(work, delay, executionContext, workListener); + } + + public long startWork(Work work) throws WorkException { + return this.workManager.startWork(work); + } + + public long startWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + return this.workManager.startWork(work, delay, executionContext, workListener); + } + + public void scheduleWork(Work work) throws WorkException { + this.workManager.scheduleWork(work); + } + + public void scheduleWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) + throws WorkException { + + this.workManager.scheduleWork(work, delay, executionContext, workListener); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java new file mode 100644 index 00000000000..735f8cd66d3 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2008 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.jca.work.glassfish; + +import java.lang.reflect.Method; + +import javax.resource.spi.work.WorkManager; + +import org.springframework.jca.work.WorkManagerTaskExecutor; +import org.springframework.util.ReflectionUtils; + +/** + * Spring TaskExecutor adapter for the GlassFish JCA WorkManager. + * Can be defined in web applications to make a TaskExecutor reference + * available, talking to the GlassFish WorkManager (thread pool) underneath. + * + *

This is the GlassFish equivalent of the CommonJ + * {@link org.springframework.scheduling.commonj.WorkManagerTaskExecutor} + * adapter for WebLogic and WebSphere. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see com.sun.enterprise.connectors.work.WorkManagerFactory + */ +public class GlassFishWorkManagerTaskExecutor extends WorkManagerTaskExecutor { + + private static final String WORK_MANAGER_FACTORY_CLASS = "com.sun.enterprise.connectors.work.WorkManagerFactory"; + + private final Method getWorkManagerMethod; + + + public GlassFishWorkManagerTaskExecutor() { + try { + Class wmf = getClass().getClassLoader().loadClass(WORK_MANAGER_FACTORY_CLASS); + this.getWorkManagerMethod = wmf.getMethod("getWorkManager", new Class[] {String.class}); + } + catch (Exception ex) { + throw new IllegalStateException( + "Could not initialize GlassFishWorkManagerTaskExecutor because GlassFish API is not available: " + ex); + } + } + + + /** + * Identify a specific GlassFish thread pool to talk to. + *

The thread pool name matches the resource adapter name + * in default RAR deployment scenarios. + */ + public void setThreadPoolName(String threadPoolName) { + WorkManager wm = (WorkManager) + ReflectionUtils.invokeMethod(this.getWorkManagerMethod, null, new Object[] {threadPoolName}); + if (wm == null) { + throw new IllegalArgumentException("Specified thread pool name '" + threadPoolName + + "' does not correspond to an actual pool definition in GlassFish. Check your configuration!"); + } + setWorkManager(wm); + } + + /** + * Obtains GlassFish's default thread pool. + */ + protected WorkManager getDefaultWorkManager() { + return (WorkManager) ReflectionUtils.invokeMethod(this.getWorkManagerMethod, null, new Object[] {null}); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/package.html new file mode 100644 index 00000000000..71a4a432e5a --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/glassfish/package.html @@ -0,0 +1,8 @@ + + + +Convenience package for obtaining a GlassFish JCA WorkManager for use in +web applications. Provides a Spring TaskExecutor adapter for GlassFish. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java new file mode 100644 index 00000000000..8bd10f1c172 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2008 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.jca.work.jboss; + +import javax.resource.spi.work.WorkManager; + +import org.springframework.jca.work.WorkManagerTaskExecutor; + +/** + * Spring TaskExecutor adapter for the JBoss JCA WorkManager. + * Can be defined in web applications to make a TaskExecutor reference + * available, talking to the JBoss WorkManager (thread pool) underneath. + * + *

This is the JBoss equivalent of the CommonJ + * {@link org.springframework.scheduling.commonj.WorkManagerTaskExecutor} + * adapter for WebLogic and WebSphere. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.jboss.resource.work.JBossWorkManagerMBean + */ +public class JBossWorkManagerTaskExecutor extends WorkManagerTaskExecutor { + + /** + * Obtains the default JBoss JCA WorkManager through a JMX lookup + * for the JBossWorkManagerMBean. + * @see JBossWorkManagerUtils#getWorkManager() + */ + protected WorkManager getDefaultWorkManager() { + return JBossWorkManagerUtils.getWorkManager(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java new file mode 100644 index 00000000000..207e0abd9db --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2008 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.jca.work.jboss; + +import java.lang.reflect.Method; + +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerInvocationHandler; +import javax.management.ObjectName; +import javax.naming.InitialContext; +import javax.resource.spi.work.WorkManager; + +/** + * Utility class for obtaining the JBoss JCA WorkManager, + * typically for use in web applications. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public abstract class JBossWorkManagerUtils { + + private static final String JBOSS_WORK_MANAGER_MBEAN_CLASS_NAME = "org.jboss.resource.work.JBossWorkManagerMBean"; + + private static final String MBEAN_SERVER_CONNECTION_JNDI_NAME = "jmx/invoker/RMIAdaptor"; + + private static final String WORK_MANAGER_OBJECT_NAME = "jboss.jca:service=WorkManager"; + + + /** + * Obtain the default JBoss JCA WorkManager through a JMX lookup + * for the JBossWorkManagerMBean. + * @see org.jboss.resource.work.JBossWorkManagerMBean + */ + public static WorkManager getWorkManager() { + try { + Class mbeanClass = JBossWorkManagerUtils.class.getClassLoader().loadClass(JBOSS_WORK_MANAGER_MBEAN_CLASS_NAME); + InitialContext jndiContext = new InitialContext(); + MBeanServerConnection mconn = (MBeanServerConnection) jndiContext.lookup(MBEAN_SERVER_CONNECTION_JNDI_NAME); + ObjectName objectName = ObjectName.getInstance(WORK_MANAGER_OBJECT_NAME); + Object workManagerMBean = MBeanServerInvocationHandler.newProxyInstance(mconn, objectName, mbeanClass, false); + Method getInstanceMethod = workManagerMBean.getClass().getMethod("getInstance", new Class[0]); + return (WorkManager) getInstanceMethod.invoke(workManagerMBean, new Object[0]); + } + catch (Exception ex) { + throw new IllegalStateException( + "Could not initialize JBossWorkManagerTaskExecutor because JBoss API is not available: " + ex); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/package.html new file mode 100644 index 00000000000..7001a41ca5c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/jboss/package.html @@ -0,0 +1,8 @@ + + + +Convenience package for obtaining a JBoss JCA WorkManager for use in +web applications. Provides a Spring TaskExecutor adapter for JBoss. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/work/package.html b/org.springframework.transaction/src/main/java/org/springframework/jca/work/package.html new file mode 100644 index 00000000000..c7a2f50569c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/work/package.html @@ -0,0 +1,8 @@ + + + +Convenience classes for scheduling based on the JCA 1.5 WorkManager facility, +as supported within JCA 1.5 ResourceAdapters. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/CannotCreateTransactionException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/CannotCreateTransactionException.java new file mode 100644 index 00000000000..b926733c8a0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/CannotCreateTransactionException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when a transaction can't be created using an + * underlying transaction API such as JTA. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +public class CannotCreateTransactionException extends TransactionException { + + /** + * Constructor for CannotCreateTransactionException. + * @param msg the detail message + */ + public CannotCreateTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for CannotCreateTransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public CannotCreateTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/HeuristicCompletionException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/HeuristicCompletionException.java new file mode 100644 index 00000000000..d640d9b4014 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/HeuristicCompletionException.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception that represents a transaction failure caused by a heuristic + * decision on the side of the transaction coordinator. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 17.03.2003 + */ +public class HeuristicCompletionException extends TransactionException { + + /** + * Values for the outcome state of a heuristically completed transaction. + */ + public static final int STATE_UNKNOWN = 0; + public static final int STATE_COMMITTED = 1; + public static final int STATE_ROLLED_BACK = 2; + public static final int STATE_MIXED = 3; + + + public static String getStateString(int state) { + switch (state) { + case STATE_COMMITTED: + return "committed"; + case STATE_ROLLED_BACK: + return "rolled back"; + case STATE_MIXED: + return "mixed"; + default: + return "unknown"; + } + } + + + /** + * The outcome state of the transaction: have some or all resources been committed? + */ + private int outcomeState = STATE_UNKNOWN; + + + /** + * Constructor for HeuristicCompletionException. + * @param outcomeState the outcome state of the transaction + * @param cause the root cause from the transaction API in use + */ + public HeuristicCompletionException(int outcomeState, Throwable cause) { + super("Heuristic completion: outcome state is " + getStateString(outcomeState), cause); + this.outcomeState = outcomeState; + } + + /** + * Return the outcome state of the transaction state, + * as one of the constants in this class. + * @see #STATE_UNKNOWN + * @see #STATE_COMMITTED + * @see #STATE_ROLLED_BACK + * @see #STATE_MIXED + */ + public int getOutcomeState() { + return outcomeState; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/IllegalTransactionStateException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/IllegalTransactionStateException.java new file mode 100644 index 00000000000..087fe98e751 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/IllegalTransactionStateException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when the existence or non-existence of a transaction + * amounts to an illegal state according to the transaction propagation + * behavior that applies. + * + * @author Juergen Hoeller + * @since 21.01.2004 + */ +public class IllegalTransactionStateException extends TransactionUsageException { + + /** + * Constructor for IllegalTransactionStateException. + * @param msg the detail message + */ + public IllegalTransactionStateException(String msg) { + super(msg); + } + + /** + * Constructor for IllegalTransactionStateException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public IllegalTransactionStateException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidIsolationLevelException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidIsolationLevelException.java new file mode 100644 index 00000000000..27eb9ce3c4b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidIsolationLevelException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception that gets thrown when an invalid isolation level is specified, + * i.e. an isolation level that the transaction manager implementation + * doesn't support. + * + * @author Juergen Hoeller + * @since 12.05.2003 + */ +public class InvalidIsolationLevelException extends TransactionUsageException { + + /** + * Constructor for InvalidIsolationLevelException. + * @param msg the detail message + */ + public InvalidIsolationLevelException(String msg) { + super(msg); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidTimeoutException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidTimeoutException.java new file mode 100644 index 00000000000..705990732fc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/InvalidTimeoutException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception that gets thrown when an invalid timeout is specified, + * that is, the specified timeout valid is out of range or the + * transaction manager implementation doesn't support timeouts. + * + * @author Juergen Hoeller + * @since 12.05.2003 + */ +public class InvalidTimeoutException extends TransactionUsageException { + + private int timeout; + + + /** + * Constructor for InvalidTimeoutException. + * @param msg the detail message + * @param timeout the invalid timeout value + */ + public InvalidTimeoutException(String msg, int timeout) { + super(msg); + this.timeout = timeout; + } + + /** + * Return the invalid timeout value. + */ + public int getTimeout() { + return timeout; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/NestedTransactionNotSupportedException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/NestedTransactionNotSupportedException.java new file mode 100644 index 00000000000..ba23bc119f2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/NestedTransactionNotSupportedException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when attempting to work with a nested transaction + * but nested transactions are not supported by the underlying backend. + * + * @author Juergen Hoeller + * @since 1.1 + */ +public class NestedTransactionNotSupportedException extends CannotCreateTransactionException { + + /** + * Constructor for NestedTransactionNotSupportedException. + * @param msg the detail message + */ + public NestedTransactionNotSupportedException(String msg) { + super(msg); + } + + /** + * Constructor for NestedTransactionNotSupportedException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public NestedTransactionNotSupportedException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/NoTransactionException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/NoTransactionException.java new file mode 100644 index 00000000000..193bdda3b5d --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/NoTransactionException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when an operation is attempted that + * relies on an existing transaction (such as setting + * rollback status) and there is no existing transaction. + * This represents an illegal usage of the transaction API. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +public class NoTransactionException extends TransactionUsageException { + + /** + * Constructor for NoTransactionException. + * @param msg the detail message + */ + public NoTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for NoTransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public NoTransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/PlatformTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/PlatformTransactionManager.java new file mode 100644 index 00000000000..068a580bb23 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/PlatformTransactionManager.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2008 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; + +/** + * This is the central interface in Spring's transaction infrastructure. + * Applications can use this directly, but it is not primarily meant as API: + * Typically, applications will work with either TransactionTemplate or + * declarative transaction demarcation through AOP. + * + *

For implementors, it is recommended to derive from the provided + * {@link org.springframework.transaction.support.AbstractPlatformTransactionManager} + * class, which pre-implements the defined propagation behavior and takes care + * of transaction synchronization handling. Subclasses have to implement + * template methods for specific states of the underlying transaction, + * for example: begin, suspend, resume, commit. + * + *

The default implementations of this strategy interface are + * {@link org.springframework.transaction.jta.JtaTransactionManager} and + * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}, + * which can serve as an implementation guide for other transaction strategies. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.05.2003 + * @see org.springframework.transaction.support.TransactionTemplate + * @see org.springframework.transaction.interceptor.TransactionInterceptor + * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean + */ +public interface PlatformTransactionManager { + + /** + * Return a currently active transaction or create a new one, according to + * the specified propagation behavior. + *

Note that parameters like isolation level or timeout will only be applied + * to new transactions, and thus be ignored when participating in active ones. + *

Furthermore, not all transaction definition settings will be supported + * by every transaction manager: A proper transaction manager implementation + * should throw an exception when unsupported settings are encountered. + *

An exception to the above rule is the read-only flag, which should be + * ignored if no explicit read-only mode is supported. Essentially, the + * read-only flag is just a hint for potential optimization. + * @param definition TransactionDefinition instance (can be null for defaults), + * describing propagation behavior, isolation level, timeout etc. + * @return transaction status object representing the new or current transaction + * @throws TransactionException in case of lookup, creation, or system errors + * @throws IllegalTransactionStateException if the given transaction definition + * cannot be executed (for example, if a currently active transaction is in + * conflict with the specified propagation behavior) + * @see TransactionDefinition#getPropagationBehavior + * @see TransactionDefinition#getIsolationLevel + * @see TransactionDefinition#getTimeout + * @see TransactionDefinition#isReadOnly + */ + TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; + + /** + * Commit the given transaction, with regard to its status. If the transaction + * has been marked rollback-only programmatically, perform a rollback. + *

If the transaction wasn't a new one, omit the commit for proper + * participation in the surrounding transaction. If a previous transaction + * has been suspended to be able to create a new one, resume the previous + * transaction after committing the new one. + *

Note that when the commit call completes, no matter if normally or + * throwing an exception, the transaction must be fully completed and + * cleaned up. No rollback call should be expected in such a case. + *

If this method throws an exception other than a TransactionException, + * then some before-commit error caused the commit attempt to fail. For + * example, an O/R Mapping tool might have tried to flush changes to the + * database right before commit, with the resulting DataAccessException + * causing the transaction to fail. The original exception will be + * propagated to the caller of this commit method in such a case. + * @param status object returned by the getTransaction method + * @throws UnexpectedRollbackException in case of an unexpected rollback + * that the transaction coordinator initiated + * @throws HeuristicCompletionException in case of a transaction failure + * caused by a heuristic decision on the side of the transaction coordinator + * @throws TransactionSystemException in case of commit or system errors + * (typically caused by fundamental resource failures) + * @throws IllegalTransactionStateException if the given transaction + * is already completed (that is, committed or rolled back) + * @see TransactionStatus#setRollbackOnly + */ + void commit(TransactionStatus status) throws TransactionException; + + /** + * Perform a rollback of the given transaction. + *

If the transaction wasn't a new one, just set it rollback-only for proper + * participation in the surrounding transaction. If a previous transaction + * has been suspended to be able to create a new one, resume the previous + * transaction after rolling back the new one. + *

Do not call rollback on a transaction if commit threw an exception. + * The transaction will already have been completed and cleaned up when commit + * returns, even in case of a commit exception. Consequently, a rollback call + * after commit failure will lead to an IllegalTransactionStateException. + * @param status object returned by the getTransaction method + * @throws TransactionSystemException in case of rollback or system errors + * (typically caused by fundamental resource failures) + * @throws IllegalTransactionStateException if the given transaction + * is already completed (that is, committed or rolled back) + */ + void rollback(TransactionStatus status) throws TransactionException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/SavepointManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/SavepointManager.java new file mode 100644 index 00000000000..f4f4f379e5c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/SavepointManager.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2005 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; + +/** + * Interface that specifies an API to programmatically manage transaction + * savepoints in a generic fashion. Extended by TransactionStatus to + * expose savepoint management functionality for a specific transaction. + * + *

Note that savepoints can only work within an active transaction. + * Just use this programmatic savepoint handling for advanced needs; + * else, a subtransaction with PROPAGATION_NESTED is preferable. + * + *

This interface is inspired by JDBC 3.0's Savepoint mechanism + * but is independent from any specific persistence technology. + * + * @author Juergen Hoeller + * @since 1.1 + * @see TransactionStatus + * @see TransactionDefinition#PROPAGATION_NESTED + * @see java.sql.Savepoint + */ +public interface SavepointManager { + + /** + * Create a new savepoint. You can roll back to a specific savepoint + * via rollbackToSavepoint, and explicitly release a + * savepoint that you don't need anymore via releaseSavepoint. + *

Note that most transaction managers will automatically release + * savepoints at transaction completion. + * @return a savepoint object, to be passed into rollbackToSavepoint + * or releaseSavepoint + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the savepoint could not be created, + * for example because the transaction is not in an appropriate state + * @see java.sql.Connection#setSavepoint + */ + Object createSavepoint() throws TransactionException; + + /** + * Roll back to the given savepoint. The savepoint will be + * automatically released afterwards. + * @param savepoint the savepoint to roll back to + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the rollback failed + * @see java.sql.Connection#rollback(java.sql.Savepoint) + */ + void rollbackToSavepoint(Object savepoint) throws TransactionException; + + /** + * Explicitly release the given savepoint. + *

Note that most transaction managers will automatically release + * savepoints at transaction completion. + *

Implementations should fail as silently as possible if + * proper resource cleanup will still happen at transaction completion. + * @param savepoint the savepoint to release + * @throws NestedTransactionNotSupportedException if the underlying + * transaction does not support savepoints + * @throws TransactionException if the release failed + * @see java.sql.Connection#releaseSavepoint + */ + void releaseSavepoint(Object savepoint) throws TransactionException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionDefinition.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionDefinition.java new file mode 100644 index 00000000000..e5109d73278 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionDefinition.java @@ -0,0 +1,264 @@ +/* + * Copyright 2002-2006 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; + +import java.sql.Connection; + +/** + * Interface that defines Spring-compliant transaction properties. + * Based on the propagation behavior definitions analogous to EJB CMT attributes. + * + *

Note that isolation level and timeout settings will not get applied unless + * an actual new transaction gets started. As only {@link #PROPAGATION_REQUIRED}, + * {@link #PROPAGATION_REQUIRES_NEW} and {@link #PROPAGATION_NESTED} can cause + * that, it usually doesn't make sense to specify those settings in other cases. + * Furthermore, be aware that not all transaction managers will support those + * advanced features and thus might throw corresponding exceptions when given + * non-default values. + * + *

The {@link #isReadOnly() read-only flag} applies to any transaction context, + * whether backed by an actual resource transaction or operating non-transactionally + * at the resource level. In the latter case, the flag will only apply to managed + * resources within the application, such as a Hibernate Session. + * + * @author Juergen Hoeller + * @since 08.05.2003 + * @see PlatformTransactionManager#getTransaction(TransactionDefinition) + * @see org.springframework.transaction.support.DefaultTransactionDefinition + * @see org.springframework.transaction.interceptor.TransactionAttribute + */ +public interface TransactionDefinition { + + /** + * Support a current transaction; create a new one if none exists. + * Analogous to the EJB transaction attribute of the same name. + *

This is typically the default setting of a transaction definition, + * and typically defines a transaction synchronization scope. + */ + int PROPAGATION_REQUIRED = 0; + + /** + * Support a current transaction; execute non-transactionally if none exists. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: For transaction managers with transaction synchronization, + * PROPAGATION_SUPPORTS is slightly different from no transaction + * at all, as it defines a transaction scope that synchronization might apply to. + * As a consequence, the same resources (a JDBC Connection, a + * Hibernate Session, etc) will be shared for the entire specified + * scope. Note that the exact behavior depends on the actual synchronization + * configuration of the transaction manager! + *

In general, use PROPAGATION_SUPPORTS with care! In particular, do + * not rely on PROPAGATION_REQUIRED or PROPAGATION_REQUIRES_NEW + * within a PROPAGATION_SUPPORTS scope (which may lead to + * synchronization conflicts at runtime). If such nesting is unavoidable, make sure + * to configure your transaction manager appropriately (typically switching to + * "synchronization on actual transaction"). + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + */ + int PROPAGATION_SUPPORTS = 1; + + /** + * Support a current transaction; throw an exception if no current transaction + * exists. Analogous to the EJB transaction attribute of the same name. + *

Note that transaction synchronization within a PROPAGATION_MANDATORY + * scope will always be driven by the surrounding transaction. + */ + int PROPAGATION_MANDATORY = 2; + + /** + * Create a new transaction, suspending the current transaction if one exists. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: Actual transaction suspension will not work out-of-the-box + * on all transaction managers. This in particular applies to + * {@link org.springframework.transaction.jta.JtaTransactionManager}, + * which requires the javax.transaction.TransactionManager + * to be made available it to it (which is server-specific in standard J2EE). + *

A PROPAGATION_REQUIRES_NEW scope always defines its own + * transaction synchronizations. Existing synchronizations will be suspended + * and resumed appropriately. + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_REQUIRES_NEW = 3; + + /** + * Do not support a current transaction; rather always execute non-transactionally. + * Analogous to the EJB transaction attribute of the same name. + *

NOTE: Actual transaction suspension will not work out-of-the-box + * on all transaction managers. This in particular applies to + * {@link org.springframework.transaction.jta.JtaTransactionManager}, + * which requires the javax.transaction.TransactionManager + * to be made available it to it (which is server-specific in standard J2EE). + *

Note that transaction synchronization is not available within a + * PROPAGATION_NOT_SUPPORTED scope. Existing synchronizations + * will be suspended and resumed appropriately. + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_NOT_SUPPORTED = 4; + + /** + * Do not support a current transaction; throw an exception if a current transaction + * exists. Analogous to the EJB transaction attribute of the same name. + *

Note that transaction synchronization is not available within a + * PROPAGATION_NEVER scope. + */ + int PROPAGATION_NEVER = 5; + + /** + * Execute within a nested transaction if a current transaction exists, + * behave like {@link #PROPAGATION_REQUIRED} else. There is no analogous + * feature in EJB. + *

NOTE: Actual creation of a nested transaction will only work on specific + * transaction managers. Out of the box, this only applies to the JDBC + * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * when working on a JDBC 3.0 driver. Some JTA providers might support + * nested transactions as well. + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + */ + int PROPAGATION_NESTED = 6; + + + /** + * Use the default isolation level of the underlying datastore. + * All other levels correspond to the JDBC isolation levels. + * @see java.sql.Connection + */ + int ISOLATION_DEFAULT = -1; + + /** + * Indicates that dirty reads, non-repeatable reads and phantom reads + * can occur. + *

This level allows a row changed by one transaction to be read by + * another transaction before any changes in that row have been committed + * (a "dirty read"). If any of the changes are rolled back, the second + * transaction will have retrieved an invalid row. + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; + + /** + * Indicates that dirty reads are prevented; non-repeatable reads and + * phantom reads can occur. + *

This level only prohibits a transaction from reading a row + * with uncommitted changes in it. + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; + + /** + * Indicates that dirty reads and non-repeatable reads are prevented; + * phantom reads can occur. + *

This level prohibits a transaction from reading a row with + * uncommitted changes in it, and it also prohibits the situation + * where one transaction reads a row, a second transaction alters + * the row, and the first transaction rereads the row, getting + * different values the second time (a "non-repeatable read"). + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; + + /** + * Indicates that dirty reads, non-repeatable reads and phantom reads + * are prevented. + *

This level includes the prohibitions in + * {@link #ISOLATION_REPEATABLE_READ} and further prohibits the + * situation where one transaction reads all rows that satisfy a + * WHERE condition, a second transaction inserts a + * row that satisfies that WHERE condition, and the + * first transaction rereads for the same condition, retrieving + * the additional "phantom" row in the second read. + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; + + + /** + * Use the default timeout of the underlying transaction system, + * or none if timeouts are not supported. + */ + int TIMEOUT_DEFAULT = -1; + + + /** + * Return the propagation behavior. + *

Must return one of the PROPAGATION_XXX constants + * defined on {@link TransactionDefinition this interface}. + * @return the propagation behavior + * @see #PROPAGATION_REQUIRED + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + int getPropagationBehavior(); + + /** + * Return the isolation level. + *

Must return one of the ISOLATION_XXX constants + * defined on {@link TransactionDefinition this interface}. + *

Only makes sense in combination with {@link #PROPAGATION_REQUIRED} + * or {@link #PROPAGATION_REQUIRES_NEW}. + *

Note that a transaction manager that does not support custom + * isolation levels will throw an exception when given any other level + * than {@link #ISOLATION_DEFAULT}. + * @return the isolation level + */ + int getIsolationLevel(); + + /** + * Return the transaction timeout. + *

Must return a number of seconds, or {@link #TIMEOUT_DEFAULT}. + *

Only makes sense in combination with {@link #PROPAGATION_REQUIRED} + * or {@link #PROPAGATION_REQUIRES_NEW}. + *

Note that a transaction manager that does not support timeouts + * will throw an exception when given any other timeout than + * {@link #TIMEOUT_DEFAULT}. + * @return the transaction timeout + */ + int getTimeout(); + + /** + * Return whether to optimize as a read-only transaction. + *

The read-only flag applies to any transaction context, whether + * backed by an actual resource transaction + * ({@link #PROPAGATION_REQUIRED}/{@link #PROPAGATION_REQUIRES_NEW}) or + * operating non-transactionally at the resource level + * ({@link #PROPAGATION_SUPPORTS}). In the latter case, the flag will + * only apply to managed resources within the application, such as a + * Hibernate Session. + *

This just serves as a hint for the actual transaction subsystem; + * it will not necessarily cause failure of write access attempts. + * A transaction manager that cannot interpret the read-only hint will + * not throw an exception when asked for a read-only transaction. + * @return true if the transaction is to be optimized as read-only + * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean) + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() + */ + boolean isReadOnly(); + + /** + * Return the name of this transaction. Can be null. + *

This will be used as the transaction name to be shown in a + * transaction monitor, if applicable (for example, WebLogic's). + *

In case of Spring's declarative transactions, the exposed name + * must (and will) be the + * fully-qualified class name + "." + method name + * (by default). + * @return the name of this transaction + * @see org.springframework.transaction.interceptor.TransactionAspectSupport + * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName() + */ + String getName(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionException.java new file mode 100644 index 00000000000..3040eea3e1b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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; + +import org.springframework.core.NestedRuntimeException; + +/** + * Superclass for all transaction exceptions. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +public abstract class TransactionException extends NestedRuntimeException { + + /** + * Constructor for TransactionException. + * @param msg the detail message + */ + public TransactionException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionStatus.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionStatus.java new file mode 100644 index 00000000000..7687b1cfa93 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionStatus.java @@ -0,0 +1,86 @@ +/* + * 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; + +/** + * Representation of the status of a transaction. + * + *

Transactional code can use this to retrieve status information, + * and to programmatically request a rollback (instead of throwing + * an exception that causes an implicit rollback). + * + *

Derives from the SavepointManager interface to provide access + * to savepoint management facilities. Note that savepoint management + * is only available if supported by the underlying transaction manager. + * + * @author Juergen Hoeller + * @since 27.03.2003 + * @see #setRollbackOnly() + * @see PlatformTransactionManager#getTransaction + * @see org.springframework.transaction.support.TransactionCallback#doInTransaction + * @see org.springframework.transaction.interceptor.TransactionInterceptor#currentTransactionStatus() + */ +public interface TransactionStatus extends SavepointManager { + + /** + * Return whether the present transaction is new (else participating + * in an existing transaction, or potentially not running in an + * actual transaction in the first place). + */ + boolean isNewTransaction(); + + /** + * Return whether this transaction internally carries a savepoint, + * that is, has been created as nested transaction based on a savepoint. + *

This method is mainly here for diagnostic purposes, alongside + * {@link #isNewTransaction()}. For programmatic handling of custom + * savepoints, use SavepointManager's operations. + * @see #isNewTransaction() + * @see #createSavepoint + * @see #rollbackToSavepoint(Object) + * @see #releaseSavepoint(Object) + */ + boolean hasSavepoint(); + + /** + * Set the transaction rollback-only. This instructs the transaction manager + * that the only possible outcome of the transaction may be a rollback, as + * alternative to throwing an exception which would in turn trigger a rollback. + *

This is mainly intended for transactions managed by + * {@link org.springframework.transaction.support.TransactionTemplate} or + * {@link org.springframework.transaction.interceptor.TransactionInterceptor}, + * where the actual commit/rollback decision is made by the container. + * @see org.springframework.transaction.support.TransactionCallback#doInTransaction + * @see org.springframework.transaction.interceptor.TransactionAttribute#rollbackOn + */ + void setRollbackOnly(); + + /** + * Return whether the transaction has been marked as rollback-only + * (either by the application or by the transaction infrastructure). + */ + boolean isRollbackOnly(); + + /** + * Return whether this transaction is completed, that is, + * whether it has already been committed or rolled back. + * @see PlatformTransactionManager#commit + * @see PlatformTransactionManager#rollback + */ + boolean isCompleted(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSuspensionNotSupportedException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSuspensionNotSupportedException.java new file mode 100644 index 00000000000..297b695ce93 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSuspensionNotSupportedException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when attempting to suspend an existing transaction + * but transaction suspension is not supported by the underlying backend. + * + * @author Juergen Hoeller + * @since 1.1 + */ +public class TransactionSuspensionNotSupportedException extends CannotCreateTransactionException { + + /** + * Constructor for TransactionSuspensionNotSupportedException. + * @param msg the detail message + */ + public TransactionSuspensionNotSupportedException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionSuspensionNotSupportedException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionSuspensionNotSupportedException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSystemException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSystemException.java new file mode 100644 index 00000000000..bd3f2fa2d82 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionSystemException.java @@ -0,0 +1,88 @@ +/* + * 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; + +import org.springframework.util.Assert; + +/** + * Exception thrown when a general transaction system error is encountered, + * like on commit or rollback. + * + * @author Juergen Hoeller + * @since 24.03.2003 + */ +public class TransactionSystemException extends TransactionException { + + private Throwable applicationException; + + + /** + * Constructor for TransactionSystemException. + * @param msg the detail message + */ + public TransactionSystemException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionSystemException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionSystemException(String msg, Throwable cause) { + super(msg, cause); + } + + + /** + * Set an application exception that was thrown before this transaction exception, + * preserving the original exception despite the overriding TransactionSystemException. + * @param ex the application exception + * @throws IllegalStateException if this TransactionSystemException already holds an + * application exception + */ + public void initApplicationException(Throwable ex) { + Assert.notNull(ex, "Application exception must not be null"); + if (this.applicationException != null) { + throw new IllegalStateException("Already holding an application exception: " + this.applicationException); + } + this.applicationException = ex; + } + + /** + * Return the application exception that was thrown before this transaction exception, + * if any. + * @return the application exception, or null if none set + */ + public final Throwable getApplicationException() { + return this.applicationException; + } + + /** + * Return the exception that was the first to be thrown within the failed transaction: + * i.e. the application exception, if any, or the TransactionSystemException's own cause. + * @return the original exception, or null if there was none + */ + public Throwable getOriginalException() { + return (this.applicationException != null ? this.applicationException : getCause()); + } + + public boolean contains(Class exType) { + return super.contains(exType) || (exType != null && exType.isInstance(this.applicationException)); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionTimedOutException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionTimedOutException.java new file mode 100644 index 00000000000..2228af4bff0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionTimedOutException.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception to be thrown when a transaction has timed out. + * + *

Thrown by Spring's local transaction strategies if the deadline + * for a transaction has been reached when an operation is attempted, + * according to the timeout specified for the given transaction. + * + *

Beyond such checks before each transactional operation, Spring's + * local transaction strategies will also pass appropriate timeout values + * to resource operations (for example to JDBC Statements, letting the JDBC + * driver respect the timeout). Such operations will usually throw native + * resource exceptions (for example, JDBC SQLExceptions) if their operation + * timeout has been exceeded, to be converted to Spring's DataAccessException + * in the respective DAO (which might use Spring's JdbcTemplate, for example). + * + *

In a JTA environment, it is up to the JTA transaction coordinator + * to apply transaction timeouts. Usually, the corresponding JTA-aware + * connection pool will perform timeout checks and throw corresponding + * native resource exceptions (for example, JDBC SQLExceptions). + * + * @author Juergen Hoeller + * @since 1.1.5 + * @see org.springframework.transaction.support.ResourceHolderSupport#getTimeToLiveInMillis + * @see java.sql.Statement#setQueryTimeout + * @see java.sql.SQLException + */ +public class TransactionTimedOutException extends TransactionException { + + /** + * Constructor for TransactionTimedOutException. + * @param msg the detail message + */ + public TransactionTimedOutException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionTimedOutException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionTimedOutException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionUsageException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionUsageException.java new file mode 100644 index 00000000000..28c4f859ba5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/TransactionUsageException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Superclass for exceptions caused by inappropriate usage of + * a Spring transaction API. + * + * @author Rod Johnson + * @since 22.03.2003 + */ +public class TransactionUsageException extends TransactionException { + + /** + * Constructor for TransactionUsageException. + * @param msg the detail message + */ + public TransactionUsageException(String msg) { + super(msg); + } + + /** + * Constructor for TransactionUsageException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public TransactionUsageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/UnexpectedRollbackException.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/UnexpectedRollbackException.java new file mode 100644 index 00000000000..6201cb11abc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/UnexpectedRollbackException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Thrown when an attempt to commit a transaction resulted + * in an unexpected rollback. + * + * @author Rod Johnson + * @since 17.03.2003 + */ +public class UnexpectedRollbackException extends TransactionException { + + /** + * Constructor for UnexpectedRollbackException. + * @param msg the detail message + */ + public UnexpectedRollbackException(String msg) { + super(msg); + } + + /** + * Constructor for UnexpectedRollbackException. + * @param msg the detail message + * @param cause the root cause from the transaction API in use + */ + public UnexpectedRollbackException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java new file mode 100644 index 00000000000..d7a3d206024 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java @@ -0,0 +1,160 @@ +/* + * 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.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Implementation of the + * {@link org.springframework.transaction.interceptor.TransactionAttributeSource} + * interface for 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. + * + *

This is a direct alternative to + * {@link org.springframework.transaction.interceptor.AttributesTransactionAttributeSource}, + * which is able to read in source-level attributes via Commons Attributes. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see Transactional + * @see TransactionAnnotationParser + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + * @see org.springframework.transaction.interceptor.TransactionInterceptor#setTransactionAttributeSource + * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean#setTransactionAttributeSource + */ +public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource + implements Serializable { + + private static final boolean ejb3Present = ClassUtils.isPresent( + "javax.ejb.TransactionAttribute", AnnotationTransactionAttributeSource.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. + */ + public AnnotationTransactionAttributeSource() { + this(true); + } + + /** + * Create a custom AnnotationTransactionAttributeSource, supporting + * public methods that carry the 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 AspectJ class weaving) + */ + public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) { + this.publicMethodsOnly = publicMethodsOnly; + this.annotationParsers = new LinkedHashSet(2); + this.annotationParsers.add(new SpringTransactionAnnotationParser()); + if (ejb3Present) { + this.annotationParsers.add(new Ejb3TransactionAnnotationParser()); + } + } + + /** + * Create a custom AnnotationTransactionAttributeSource. + * @param annotationParser the TransactionAnnotationParser to use + */ + public AnnotationTransactionAttributeSource(TransactionAnnotationParser annotationParser) { + this.publicMethodsOnly = true; + Assert.notNull(annotationParser, "TransactionAnnotationParser must not be null"); + this.annotationParsers = Collections.singleton(annotationParser); + } + + /** + * Create a custom AnnotationTransactionAttributeSource. + * @param annotationParsers the TransactionAnnotationParsers to use + */ + public AnnotationTransactionAttributeSource(Set annotationParsers) { + this.publicMethodsOnly = true; + Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified"); + this.annotationParsers = annotationParsers; + } + + + protected TransactionAttribute findTransactionAttribute(Method method) { + return determineTransactionAttribute(method); + } + + protected TransactionAttribute findTransactionAttribute(Class clazz) { + return determineTransactionAttribute(clazz); + } + + /** + * 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. + *

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 + */ + protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) { + for (TransactionAnnotationParser annotationParser : this.annotationParsers) { + TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae); + if (attr != null) { + return attr; + } + } + return null; + } + + /** + * By default, only public methods can be made transactional. + */ + protected boolean allowPublicMethodsOnly() { + return this.publicMethodsOnly; + } + + + public boolean equals(Object other) { + return (this == other || other instanceof AnnotationTransactionAttributeSource); + } + + public int hashCode() { + return AnnotationTransactionAttributeSource.class.hashCode(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java new file mode 100644 index 00000000000..74c25c38d5f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; + +import javax.ejb.ApplicationException; +import javax.ejb.TransactionAttributeType; + +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * Strategy implementation for parsing EJB3's {@link javax.ejb.TransactionAttribute} + * annotation. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class Ejb3TransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + + public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { + javax.ejb.TransactionAttribute ann = ae.getAnnotation(javax.ejb.TransactionAttribute.class); + if (ann != null) { + return parseTransactionAnnotation(ann); + } + else { + return null; + } + } + + public TransactionAttribute parseTransactionAnnotation(javax.ejb.TransactionAttribute ann) { + return new Ejb3TransactionAttribute(ann.value()); + } + + + /** + * EJB3-specific TransactionAttribute, implementing EJB3's rollback rules + * which are based on annotated exceptions. + */ + private static class Ejb3TransactionAttribute extends DefaultTransactionDefinition + implements TransactionAttribute { + + public Ejb3TransactionAttribute(TransactionAttributeType type) { + setPropagationBehaviorName(PREFIX_PROPAGATION + type.name()); + } + + public boolean rollbackOn(Throwable ex) { + ApplicationException ann = ex.getClass().getAnnotation(ApplicationException.class); + return (ann != null ? ann.rollback() : (ex instanceof RuntimeException || ex instanceof Error)); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Isolation.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Isolation.java new file mode 100644 index 00000000000..b6f3ca416d7 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Isolation.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2005 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.annotation; + +import org.springframework.transaction.TransactionDefinition; + +/** + * Enumeration that represents transaction isolation levels + * for use with the JDK 1.5+ transaction annotation, corresponding + * to the TransactionDefinition interface. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.transaction.annotation.Transactional + * @see org.springframework.transaction.TransactionDefinition + */ +public enum Isolation { + + /** + * Use the default isolation level of the underlying datastore. + * All other levels correspond to the JDBC isolation levels. + * @see java.sql.Connection + */ + DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), + + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom reads + * can occur. This level allows a row changed by one transaction to be read by + * another transaction before any changes in that row have been committed + * (a "dirty read"). If any of the changes are rolled back, the second + * transaction will have retrieved an invalid row. + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), + + /** + * A constant indicating that dirty reads are prevented; non-repeatable reads + * and phantom reads can occur. This level only prohibits a transaction + * from reading a row with uncommitted changes in it. + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), + + /** + * A constant indicating that dirty reads and non-repeatable reads are + * prevented; phantom reads can occur. This level prohibits a transaction + * from reading a row with uncommitted changes in it, and it also prohibits + * the situation where one transaction reads a row, a second transaction + * alters the row, and the first transaction rereads the row, getting + * different values the second time (a "non-repeatable read"). + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), + + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom + * reads are prevented. This level includes the prohibitions in + * ISOLATION_REPEATABLE_READ and further prohibits the situation + * where one transaction reads all rows that satisfy a WHERE + * condition, a second transaction inserts a row that satisfies that + * WHERE condition, and the first transaction rereads for the + * same condition, retrieving the additional "phantom" row in the second read. + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); + + + private final int value; + + + Isolation(int value) { this.value = value; } + + public int value() { return value; } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Propagation.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Propagation.java new file mode 100644 index 00000000000..aa904379c72 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Propagation.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2005 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.annotation; + +import org.springframework.transaction.TransactionDefinition; + +/** + * Enumeration that represents transaction propagation behaviors + * for use with the JDK 1.5+ transaction annotation, corresponding + * to the TransactionDefinition interface. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.transaction.annotation.Transactional + * @see org.springframework.transaction.TransactionDefinition + */ +public enum Propagation { + + /** + * Support a current transaction, create a new one if none exists. + * Analogous to EJB transaction attribute of the same name. + *

This is the default setting of a transaction annotation. + */ + REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), + + /** + * Support a current transaction, execute non-transactionally if none exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: For transaction managers with transaction synchronization, + * PROPAGATION_SUPPORTS is slightly different from no transaction at all, + * as it defines a transaction scopp that synchronization will apply for. + * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) + * will be shared for the entire specified scope. Note that this depends on + * the actual synchronization configuration of the transaction manager. + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + */ + SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), + + /** + * Support a current transaction, throw an exception if none exists. + * Analogous to EJB transaction attribute of the same name. + */ + MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), + + /** + * Create a new transaction, suspend the current transaction if one exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: Actual transaction suspension will not work on out-of-the-box + * on all transaction managers. This in particular applies to JtaTransactionManager, + * which requires the javax.transaction.TransactionManager to be + * made available it to it (which is server-specific in standard J2EE). + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), + + /** + * Execute non-transactionally, suspend the current transaction if one exists. + * Analogous to EJB transaction attribute of the same name. + *

Note: Actual transaction suspension will not work on out-of-the-box + * on all transaction managers. This in particular applies to JtaTransactionManager, + * which requires the javax.transaction.TransactionManager to be + * made available it to it (which is server-specific in standard J2EE). + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), + + /** + * Execute non-transactionally, throw an exception if a transaction exists. + * Analogous to EJB transaction attribute of the same name. + */ + NEVER(TransactionDefinition.PROPAGATION_NEVER), + + /** + * Execute within a nested transaction if a current transaction exists, + * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. + *

Note: Actual creation of a nested transaction will only work on specific + * transaction managers. Out of the box, this only applies to the JDBC + * DataSourceTransactionManager when working on a JDBC 3.0 driver. + * Some JTA providers might support nested transactions as well. + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + */ + NESTED(TransactionDefinition.PROPAGATION_NESTED); + + + private final int value; + + + Propagation(int value) { this.value = value; } + + public int value() { return value; } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java new file mode 100644 index 00000000000..cc98efa11ec --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; + +import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; +import org.springframework.transaction.interceptor.RollbackRuleAttribute; +import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy implementation for parsing Spring's {@link Transactional} annotation. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + + public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { + Transactional ann = ae.getAnnotation(Transactional.class); + if (ann != null) { + return parseTransactionAnnotation(ann); + } + else { + return null; + } + } + + public TransactionAttribute parseTransactionAnnotation(Transactional ann) { + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + rbta.setPropagationBehavior(ann.propagation().value()); + rbta.setIsolationLevel(ann.isolation().value()); + rbta.setTimeout(ann.timeout()); + rbta.setReadOnly(ann.readOnly()); + ArrayList rollBackRules = new ArrayList(); + Class[] rbf = ann.rollbackFor(); + for (int i = 0; i < rbf.length; ++i) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbf[i]); + rollBackRules.add(rule); + } + String[] rbfc = ann.rollbackForClassName(); + for (int i = 0; i < rbfc.length; ++i) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbfc[i]); + rollBackRules.add(rule); + } + Class[] nrbf = ann.noRollbackFor(); + for (int i = 0; i < nrbf.length; ++i) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbf[i]); + rollBackRules.add(rule); + } + String[] nrbfc = ann.noRollbackForClassName(); + for (int i = 0; i < nrbfc.length; ++i) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbfc[i]); + rollBackRules.add(rule); + } + rbta.getRollbackRules().addAll(rollBackRules); + return rbta; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java new file mode 100644 index 00000000000..c1517c41394 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java @@ -0,0 +1,50 @@ +/* + * 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.annotation; + +import java.lang.reflect.AnnotatedElement; + +import org.springframework.transaction.interceptor.TransactionAttribute; + +/** + * Strategy interface for parsing known transaction annotation types. + * {@link AnnotationTransactionAttributeSource} delegates to such + * parsers for supporting specific annotation types such as Spring's own + * {@link Transactional} or EJB3's {@link javax.ejb.TransactionAttribute}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotationTransactionAttributeSource + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + */ +public interface TransactionAnnotationParser { + + /** + * Parse the transaction attribute for the given method or class, + * based on a known annotation type. + *

This essentially parses a known transaction annotation into Spring's + * metadata attribute class. Returns null if the method/class + * is not transactional. + * @param ae the annotated method or class + * @return TransactionAttribute the configured transaction attribute, + * or null if none was found + * @see AnnotationTransactionAttributeSource#determineTransactionAttribute + */ + TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java new file mode 100644 index 00000000000..54585fd53dc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2006 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.transaction.TransactionDefinition; + +/** + * Describes transaction attributes on a method or class. + * + *

This annotation type is generally directly comparable to Spring's + * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute} + * class, and in fact {@link AnnotationTransactionAttributeSource} will directly + * convert the data to the latter class, so that Spring's transaction support code + * does not have to know about annotations. If no rules are relevant to the exception, + * it will be treated like + * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute} + * (rolling back on runtime exceptions). + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute + * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Transactional { + + /** + * The transaction propagation type. + *

Defaults to {@link Propagation#REQUIRED}. + */ + Propagation propagation() default Propagation.REQUIRED; + + /** + * The transaction isolation level. + *

Defaults to {@link Isolation#DEFAULT}. + */ + Isolation isolation() default Isolation.DEFAULT; + + /** + * The timeout for this transaction. + *

Defaults to the default timeout of the underlying transaction system. + */ + int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; + + /** + * true if the transaction is read-only. + *

Defaults to false. + */ + boolean readOnly() default false; + + /** + * Defines zero (0) or more exception {@link Class classes}, which must be a + * subclass of {@link Throwable}, indicating which exception types must cause + * a transaction rollback. + *

This is the preferred way to construct a rollback rule, matching the + * exception class and subclasses. + *

Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)} + */ + Class[] rollbackFor() default {}; + + /** + * Defines zero (0) or more exception names (for exceptions which must be a + * subclass of {@link Throwable}), indicating which exception types must cause + * a transaction rollback. + *

This can be a substring, with no wildcard support at present. + * A value of "ServletException" would match + * {@link javax.servlet.ServletException} and subclasses, for example. + *

NB: Consider carefully how specific the pattern is, and whether + * to include package information (which isn't mandatory). For example, + * "Exception" will match nearly anything, and will probably hide other rules. + * "java.lang.Exception" would be correct if "Exception" was meant to define + * a rule for all checked exceptions. With more unusual {@link Exception} + * names such as "BaseBusinessException" there is no need to use a FQN. + *

Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)} + */ + String[] rollbackForClassName() default {}; + + /** + * Defines zero (0) or more exception {@link Class Classes}, which must be a + * subclass of {@link Throwable}, indicating which exception types must not + * cause a transaction rollback. + *

This is the preferred way to construct a rollback rule, matching the + * exception class and subclasses. + *

Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)} + */ + Class[] noRollbackFor() default {}; + + /** + * Defines zero (0) or more exception names (for exceptions which must be a + * subclass of {@link Throwable}) indicating which exception types must not + * cause a transaction rollback. + *

See the description of {@link #rollbackForClassName()} for more info on how + * the specified names are treated. + *

Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)} + */ + String[] noRollbackForClassName() default {}; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/package.html new file mode 100644 index 00000000000..b43afac0bf0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/package.html @@ -0,0 +1,9 @@ + + + +JDK 1.5+ annotation for transaction demarcation. +Hooked into Spring's transaction interception infrastructure +via a special TransactionAttributeSource implementation. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java new file mode 100644 index 00000000000..b38aebff753 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2008 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.config; + +import org.w3c.dom.Element; + +import org.springframework.aop.config.AopNamespaceUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} + * implementation that allows users to easily configure all the infrastructure + * beans required to enable annotation-driven transaction demarcation. + * + *

By default, all proxies are created as JDK proxies. This may cause some + * problems if you are injecting objects as concrete classes rather than + * interfaces. To overcome this restriction you can set the + * 'proxy-target-class' attribute to 'true', which + * will result in class-based proxies being created. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + */ +class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { + + public static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; + + /** + * The bean name of the internally managed transaction advisor (mode="proxy"). + */ + public static final String TRANSACTION_ADVISOR_BEAN_NAME = + "org.springframework.transaction.config.internalTransactionAdvisor"; + + /** + * The bean name of the internally managed transaction aspect (mode="aspectj"). + */ + public static final String TRANSACTION_ASPECT_BEAN_NAME = + "org.springframework.transaction.config.internalTransactionAspect"; + + private static final String TRANSACTION_ASPECT_CLASS_NAME = + "org.springframework.transaction.aspectj.AnnotationTransactionAspect"; + + + /** + * Parses the '<tx:annotation-driven/>' tag. Will + * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator} + * with the container as necessary. + */ + public BeanDefinition parse(Element element, ParserContext parserContext) { + String mode = element.getAttribute("mode"); + if ("aspectj".equals(mode)) { + // mode="aspectj" + registerTransactionAspect(element, parserContext); + } + else { + // mode="proxy" + AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext); + } + return null; + } + + private void registerTransactionAspect(Element element, ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(TRANSACTION_ASPECT_BEAN_NAME)) { + RootBeanDefinition def = new RootBeanDefinition(); + def.setBeanClassName(TRANSACTION_ASPECT_CLASS_NAME); + def.setFactoryMethodName("aspectOf"); + registerTransactionManager(element, def); + parserContext.registerBeanComponent(new BeanComponentDefinition(def, TRANSACTION_ASPECT_BEAN_NAME)); + } + } + + private static void registerTransactionManager(Element element, BeanDefinition def) { + String transactionManagerName = (element.hasAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) ? + element.getAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME); + def.getPropertyValues().addPropertyValue( + TxNamespaceUtils.TRANSACTION_MANAGER_PROPERTY, new RuntimeBeanReference(transactionManagerName)); + } + + + /** + * Inner class to just introduce an AOP framework dependency when actually in proxy mode. + */ + private static class AopAutoProxyConfigurer { + + public static void configureAutoProxyCreator(Element element, ParserContext parserContext) { + AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); + + if (!parserContext.getRegistry().containsBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME)) { + Object eleSource = parserContext.extractSource(element); + + // Create the TransactionAttributeSource definition. + Class sourceClass = TxNamespaceUtils.getAnnotationTransactionAttributeSourceClass(); + RootBeanDefinition sourceDef = new RootBeanDefinition(sourceClass); + sourceDef.setSource(eleSource); + sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); + + // Create the TransactionInterceptor definition. + RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); + interceptorDef.setSource(eleSource); + interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registerTransactionManager(element, interceptorDef); + interceptorDef.getPropertyValues().addPropertyValue(TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, + new RuntimeBeanReference(sourceName)); + String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); + + // Create the TransactionAttributeSourceAdvisor definition. + RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); + advisorDef.setSource(eleSource); + advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + advisorDef.getPropertyValues().addPropertyValue(TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, + new RuntimeBeanReference(sourceName)); + advisorDef.getPropertyValues().addPropertyValue("adviceBeanName", interceptorName); + if (element.hasAttribute("order")) { + advisorDef.getPropertyValues().addPropertyValue("order", element.getAttribute("order")); + } + parserContext.getRegistry().registerBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME, advisorDef); + + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); + compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, TRANSACTION_ADVISOR_BEAN_NAME)); + parserContext.registerComponent(compositeDef); + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java new file mode 100644 index 00000000000..689d1e454b4 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2008 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.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.ClassUtils; + +/** + * Parser for the <tx:jta-transaction-manager/> element, + * autodetecting BEA WebLogic, IBM WebSphere and Oracle OC4J. + * + * @author Juergen Hoeller + * @author Christian Dupuis + * @since 2.5 + */ +public class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + private static final String WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME = + "org.springframework.transaction.jta.WebLogicJtaTransactionManager"; + + private static final String WEBSPHERE_TRANSACTION_MANAGER_CLASS_NAME = + "org.springframework.transaction.jta.WebSphereUowTransactionManager"; + + private static final String OC4J_TRANSACTION_MANAGER_CLASS_NAME = + "org.springframework.transaction.jta.OC4JJtaTransactionManager"; + + private static final String JTA_TRANSACTION_MANAGER_CLASS_NAME = + "org.springframework.transaction.jta.JtaTransactionManager"; + + public static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = + AnnotationDrivenBeanDefinitionParser.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME; + + + private static final boolean weblogicPresent = ClassUtils.isPresent( + "weblogic.transaction.UserTransaction", JtaTransactionManagerBeanDefinitionParser.class.getClassLoader()); + + private static final boolean webspherePresent = ClassUtils.isPresent( + "com.ibm.wsspi.uow.UOWManager", JtaTransactionManagerBeanDefinitionParser.class.getClassLoader()); + + private static final boolean oc4jPresent = ClassUtils.isPresent( + "oracle.j2ee.transaction.OC4JTransactionManager", JtaTransactionManagerBeanDefinitionParser.class.getClassLoader()); + + + protected String getBeanClassName(Element element) { + if (weblogicPresent) { + return WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME; + } + else if (webspherePresent) { + return WEBSPHERE_TRANSACTION_MANAGER_CLASS_NAME; + } + else if (oc4jPresent) { + return OC4J_TRANSACTION_MANAGER_CLASS_NAME; + } + else { + return JTA_TRANSACTION_MANAGER_CLASS_NAME; + } + } + + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { + return DEFAULT_TRANSACTION_MANAGER_BEAN_NAME; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java new file mode 100644 index 00000000000..644558a1b8e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java @@ -0,0 +1,163 @@ +/* + * 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.config; + +import java.util.LinkedList; +import java.util.List; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; +import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; +import org.springframework.transaction.interceptor.RollbackRuleAttribute; +import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} + * for the <tx:advice> tag. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 2.0 + */ +class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + private static final String ATTRIBUTES = "attributes"; + + private static final String TIMEOUT = "timeout"; + + private static final String READ_ONLY = "read-only"; + + private static final String NAME_MAP = "nameMap"; + + private static final String PROPAGATION = "propagation"; + + private static final String ISOLATION = "isolation"; + + private static final String ROLLBACK_FOR = "rollback-for"; + + private static final String NO_ROLLBACK_FOR = "no-rollback-for"; + + + protected Class getBeanClass(Element element) { + return TransactionInterceptor.class; + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + // Set the transaction manager property. + String transactionManagerName = (element.hasAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) ? + element.getAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) : "transactionManager"); + builder.addPropertyReference(TxNamespaceUtils.TRANSACTION_MANAGER_PROPERTY, transactionManagerName); + + List txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES); + if (txAttributes.size() > 1) { + parserContext.getReaderContext().error( + "Element is allowed at most once inside element ", element); + } + else if (txAttributes.size() == 1) { + // Using attributes source. + Element attributeSourceElement = (Element) txAttributes.get(0); + RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext); + builder.addPropertyValue(TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, attributeSourceDefinition); + } + else { + // Assume annotations source. + Class sourceClass = TxNamespaceUtils.getAnnotationTransactionAttributeSourceClass(); + builder.addPropertyValue(TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, new RootBeanDefinition(sourceClass)); + } + } + + private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) { + List methods = DomUtils.getChildElementsByTagName(attrEle, "method"); + ManagedMap transactionAttributeMap = new ManagedMap(methods.size()); + transactionAttributeMap.setSource(parserContext.extractSource(attrEle)); + + for (int i = 0; i < methods.size(); i++) { + Element methodEle = (Element) methods.get(i); + + String name = methodEle.getAttribute("name"); + TypedStringValue nameHolder = new TypedStringValue(name); + nameHolder.setSource(parserContext.extractSource(methodEle)); + + RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute(); + String propagation = methodEle.getAttribute(PROPAGATION); + String isolation = methodEle.getAttribute(ISOLATION); + String timeout = methodEle.getAttribute(TIMEOUT); + String readOnly = methodEle.getAttribute(READ_ONLY); + if (StringUtils.hasText(propagation)) { + attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation); + } + if (StringUtils.hasText(isolation)) { + attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation); + } + if (StringUtils.hasText(timeout)) { + try { + attribute.setTimeout(Integer.parseInt(timeout)); + } + catch (NumberFormatException ex) { + parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle); + } + } + if (StringUtils.hasText(readOnly)) { + attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY)).booleanValue()); + } + + List rollbackRules = new LinkedList(); + if (methodEle.hasAttribute(ROLLBACK_FOR)) { + String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR); + addRollbackRuleAttributesTo(rollbackRules,rollbackForValue); + } + if (methodEle.hasAttribute(NO_ROLLBACK_FOR)) { + String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR); + addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue); + } + attribute.setRollbackRules(rollbackRules); + + transactionAttributeMap.put(nameHolder, attribute); + } + + RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class); + attributeSourceDefinition.setSource(parserContext.extractSource(attrEle)); + attributeSourceDefinition.getPropertyValues().addPropertyValue(NAME_MAP, transactionAttributeMap); + return attributeSourceDefinition; + } + + private void addRollbackRuleAttributesTo(List rollbackRules, String rollbackForValue) { + String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(rollbackForValue); + for (int i = 0; i < exceptionTypeNames.length; i++) { + rollbackRules.add(new RollbackRuleAttribute(StringUtils.trimWhitespace(exceptionTypeNames[i]))); + } + } + + private void addNoRollbackRuleAttributesTo(List rollbackRules, String noRollbackForValue) { + String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(noRollbackForValue); + for (int i = 0; i < exceptionTypeNames.length; i++) { + rollbackRules.add(new NoRollbackRuleAttribute(StringUtils.trimWhitespace(exceptionTypeNames[i]))); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java new file mode 100644 index 00000000000..3124362a382 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java @@ -0,0 +1,46 @@ +/* + * 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.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * NamespaceHandler allowing for the configuration of + * declarative transaction management using either XML or using annotations. + * + *

This namespace handler is the central piece of functionality in the + * Spring 2 transaction management facilities and offers two appraoched + * to declaratively manage transactions. + * + *

One approach uses transaction semantics defined in XML using the + * <tx:advice> elements, the other uses annotations + * in combination with the <tx:annotation-driven> element. + * Both approached are detailed to great extent in the Spring reference manual. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class TxNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); + registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); + registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceUtils.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceUtils.java new file mode 100644 index 00000000000..591346412eb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceUtils.java @@ -0,0 +1,56 @@ +/* + * 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.config; + +import org.springframework.core.Conventions; +import org.springframework.core.JdkVersion; +import org.springframework.util.ClassUtils; + +/** + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +class TxNamespaceUtils { + + public static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager"; + + public static final String TRANSACTION_MANAGER_PROPERTY = + Conventions.attributeNameToPropertyName(TRANSACTION_MANAGER_ATTRIBUTE); + + public static final String TRANSACTION_ATTRIBUTE_SOURCE = "transactionAttributeSource"; + + private static final String ANNOTATION_TRANSACTION_ATTRIBUTE_SOURCE_CLASS_NAME = + "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"; + + + public static Class getAnnotationTransactionAttributeSourceClass() { + if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) { + throw new IllegalStateException( + "AnnotationTransactionAttributeSource is only available on Java 1.5 and higher"); + } + try { + return ClassUtils.forName( + ANNOTATION_TRANSACTION_ATTRIBUTE_SOURCE_CLASS_NAME, TxNamespaceUtils.class.getClassLoader()); + } + catch (Throwable ex) { + throw new IllegalStateException("Unable to load Java 1.5 dependent class [" + + ANNOTATION_TRANSACTION_ATTRIBUTE_SOURCE_CLASS_NAME + "]", ex); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/package.html new file mode 100644 index 00000000000..00469d38c4f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/package.html @@ -0,0 +1,8 @@ + + + +Support package for declarative transaction configuration, +with XML schema being the primary configuration format. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.0.xsd b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.0.xsd new file mode 100644 index 00000000000..817d0b610e2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.0.xsd @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.5.xsd b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.5.xsd new file mode 100644 index 00000000000..aa19c459d62 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/spring-tx-2.5.xsd @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java new file mode 100644 index 00000000000..91f27d675f4 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java @@ -0,0 +1,230 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.CollectionFactory; +import org.springframework.core.JdkVersion; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Abstract implementation of {@link TransactionAttributeSource} that caches + * attributes for methods and implements a fallback policy: 1. specific target + * method; 2. target class; 3. declaring method; 4. declaring class/interface. + * + *

Defaults to using the target class's transaction attribute if none is + * associated with the target method. Any transaction attribute associated with + * the target method completely overrides a class transaction attribute. + * If none found on the target class, the interface that the invoked method + * has been called through (in case of a JDK proxy) will be checked. + * + *

This implementation caches attributes by method after they are first used. + * If it is ever desirable to allow dynamic changing of transaction attributes + * (which is very unlikely), caching could be made configurable. Caching is + * desirable because of the cost of evaluating rollback rules. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource { + + /** + * Canonical value held in cache to indicate no transaction attribute was + * found for this method, and we don't need to look again. + */ + private final static Object NULL_TRANSACTION_ATTRIBUTE = new Object(); + + + /** + * Logger available to subclasses. + *

As this base class is not marked Serializable, the logger will be recreated + * after serialization - provided that the concrete subclass is Serializable. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * Cache of TransactionAttributes, keyed by DefaultCacheKey (Method + target Class). + *

As this base class is not marked Serializable, the cache will be recreated + * after serialization - provided that the concrete subclass is Serializable. + */ + final Map attributeCache = CollectionFactory.createConcurrentMapIfPossible(16); + + + /** + * Determine the transaction attribute for this method invocation. + *

Defaults to the class's transaction attribute if no method attribute is found. + * @param method the method for the current invocation (never null) + * @param targetClass the target class for this invocation (may be null) + * @return TransactionAttribute for this method, or null if the method + * is not transactional + */ + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + // First, see if we have a cached value. + Object cacheKey = getCacheKey(method, targetClass); + Object cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // Value will either be canonical value indicating there is no transaction attribute, + // or an actual transaction attribute. + if (cached == NULL_TRANSACTION_ATTRIBUTE) { + return null; + } + else { + return (TransactionAttribute) cached; + } + } + else { + // We need to work it out. + TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass); + // Put it in the cache. + if (txAtt == null) { + this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + method.getName() + "] with attribute [" + txAtt + "]"); + } + this.attributeCache.put(cacheKey, txAtt); + } + return txAtt; + } + } + + /** + * Determine a cache key for the given method and target class. + *

Must not produce same key for overloaded methods. + * Must produce same key for different instances of the same method. + * @param method the method (never null) + * @param targetClass the target class (may be null) + * @return the cache key (never null) + */ + protected Object getCacheKey(Method method, Class targetClass) { + return new DefaultCacheKey(method, targetClass); + } + + /** + * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result. + * {@link #getTransactionAttribute} is effectively a caching decorator for this method. + * @see #getTransactionAttribute + */ + private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) { + // Don't allow no-public methods as required. + if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { + return null; + } + + // The method may be on an interface, but we need attributes from the target class. + // If the target class is null, the method will be unchanged. + Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); + // If we are dealing with method with generic parameters, find the original method. + if (JdkVersion.isAtLeastJava15()) { + specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); + } + + // First try is the method in the target class. + TransactionAttribute txAtt = findTransactionAttribute(specificMethod); + if (txAtt != null) { + return txAtt; + } + + // Second try is the transaction attribute on the target class. + txAtt = findTransactionAttribute(specificMethod.getDeclaringClass()); + if (txAtt != null) { + return txAtt; + } + + if (specificMethod != method) { + // Fallback is to look at the original method. + txAtt = findTransactionAttribute(method); + if (txAtt != null) { + return txAtt; + } + // Last fallback is the class of the original method. + return findTransactionAttribute(method.getDeclaringClass()); + } + return null; + } + + + /** + * Subclasses need to implement this to return the transaction attribute + * for the given method, if any. + * @param method the method to retrieve the attribute for + * @return all transaction attribute associated with this method + * (or null if none) + */ + protected abstract TransactionAttribute findTransactionAttribute(Method method); + + /** + * Subclasses need to implement this to return the transaction attribute + * for the given class, if any. + * @param clazz the class to retrieve the attribute for + * @return all transaction attribute associated with this class + * (or null if none) + */ + protected abstract TransactionAttribute findTransactionAttribute(Class clazz); + + + /** + * Should only public methods be allowed to have transactional semantics? + *

The default implementation returns false. + */ + protected boolean allowPublicMethodsOnly() { + return false; + } + + + /** + * Default cache key for the TransactionAttribute cache. + */ + private static class DefaultCacheKey { + + private final Method method; + + private final Class targetClass; + + public DefaultCacheKey(Method method, Class targetClass) { + this.method = method; + this.targetClass = targetClass; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DefaultCacheKey)) { + return false; + } + DefaultCacheKey otherKey = (DefaultCacheKey) other; + return (this.method.equals(otherKey.method) && + ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); + } + + public int hashCode() { + return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AttributesTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AttributesTransactionAttributeSource.java new file mode 100644 index 00000000000..8865a163ff9 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/AttributesTransactionAttributeSource.java @@ -0,0 +1,151 @@ +/* + * 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.interceptor; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.metadata.Attributes; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Implementation of the {@link TransactionAttributeSource} interface that reads + * metadata via Spring's {@link org.springframework.metadata.Attributes} abstraction. + * + *

Typically used for reading in source-level attributes via Commons Attributes. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.metadata.Attributes + * @see org.springframework.metadata.commons.CommonsAttributes + */ +public class AttributesTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource + implements InitializingBean { + + /** + * Underlying Attributes implementation that we're using. + */ + private Attributes attributes; + + + /** + * Create a new AttributesTransactionAttributeSource. + * @see #setAttributes + */ + public AttributesTransactionAttributeSource() { + } + + /** + * Create a new AttributesTransactionAttributeSource. + * @param attributes the Attributes implementation to use + * @see org.springframework.metadata.commons.CommonsAttributes + */ + public AttributesTransactionAttributeSource(Attributes attributes) { + Assert.notNull(attributes, "Attributes must not be null"); + this.attributes = attributes; + } + + /** + * Set the Attributes implementation to use. + * @see org.springframework.metadata.commons.CommonsAttributes + */ + public void setAttributes(Attributes attributes) { + this.attributes = attributes; + } + + public void afterPropertiesSet() { + Assert.notNull(this.attributes, "Property 'attributes' is required"); + } + + + protected TransactionAttribute findTransactionAttribute(Method method) { + Assert.notNull(this.attributes, "Property 'attributes' is required"); + return findTransactionAttribute(this.attributes.getAttributes(method)); + } + + protected TransactionAttribute findTransactionAttribute(Class clazz) { + Assert.notNull(this.attributes, "Property 'attributes' is required"); + return findTransactionAttribute(this.attributes.getAttributes(clazz)); + } + + /** + * Return the transaction attribute, given this set of attributes + * attached to a method or class. + *

Protected rather than private as subclasses may want to customize + * how this is done: for example, returning a TransactionAttribute + * affected by the values of other attributes. + *

This implementation takes into account RollbackRuleAttributes, + * if the TransactionAttribute is a RuleBasedTransactionAttribute. + * @param atts attributes attached to a method or class (may be null) + * @return TransactionAttribute the corresponding transaction attribute, + * or null if none was found + */ + protected TransactionAttribute findTransactionAttribute(Collection atts) { + if (atts == null) { + return null; + } + + TransactionAttribute txAttribute = null; + + // Check whether there is a transaction attribute. + for (Iterator it = atts.iterator(); it.hasNext() && txAttribute == null; ) { + Object att = it.next(); + if (att instanceof TransactionAttribute) { + txAttribute = (TransactionAttribute) att; + } + } + + // Check if we have a RuleBasedTransactionAttribute. + if (txAttribute instanceof RuleBasedTransactionAttribute) { + RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) txAttribute; + // We really want value: bit of a hack. + List rollbackRules = new LinkedList(); + for (Iterator it = atts.iterator(); it.hasNext(); ) { + Object att = it.next(); + if (att instanceof RollbackRuleAttribute) { + rollbackRules.add(att); + } + } + // Repeatedly setting this isn't elegant, but it works. + rbta.setRollbackRules(rollbackRules); + } + + return txAttribute; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AttributesTransactionAttributeSource)) { + return false; + } + AttributesTransactionAttributeSource otherTas = (AttributesTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.attributes, otherTas.attributes); + } + + public int hashCode() { + return AttributesTransactionAttributeSource.class.hashCode(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java new file mode 100644 index 00000000000..bd6ad3130f8 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/BeanFactoryTransactionAttributeSourceAdvisor.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; + +/** + * Advisor driven by a {@link TransactionAttributeSource}, used to include + * a transaction advice bean for methods that are transactional. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see #setAdviceBeanName + * @see TransactionInterceptor + * @see TransactionAttributeSourceAdvisor + */ +public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { + + private TransactionAttributeSource transactionAttributeSource; + + private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { + protected TransactionAttributeSource getTransactionAttributeSource() { + return transactionAttributeSource; + } + }; + + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. This should usually be identical to the source reference + * set on the transaction interceptor itself. + * @see TransactionInterceptor#setTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionAttributeSource = transactionAttributeSource; + } + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + */ + public void setClassFilter(ClassFilter classFilter) { + this.pointcut.setClassFilter(classFilter); + } + + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java new file mode 100644 index 00000000000..4266e8c3dac --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.util.Assert; + +/** + * Composite {@link TransactionAttributeSource} implementation that iterates + * over a given array of {@link TransactionAttributeSource} instances. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class CompositeTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + private final TransactionAttributeSource[] transactionAttributeSources; + + + /** + * Create a new CompositeTransactionAttributeSource for the given sources. + * @param transactionAttributeSources the TransactionAttributeSource instances to combine + */ + public CompositeTransactionAttributeSource(TransactionAttributeSource[] transactionAttributeSources) { + Assert.notNull(transactionAttributeSources, "TransactionAttributeSource array must not be null"); + this.transactionAttributeSources = transactionAttributeSources; + } + + /** + * Return the TransactionAttributeSource instances that this + * CompositeTransactionAttributeSource combines. + */ + public final TransactionAttributeSource[] getTransactionAttributeSources() { + return this.transactionAttributeSources; + } + + + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + for (int i = 0; i < this.transactionAttributeSources.length; i++) { + TransactionAttributeSource tas = this.transactionAttributeSources[i]; + TransactionAttribute ta = tas.getTransactionAttribute(method, targetClass); + if (ta != null) { + return ta; + } + } + return null; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java new file mode 100644 index 00000000000..6af4bd74a7b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * Transaction attribute that takes the EJB approach to rolling + * back on runtime, but not checked, exceptions. + * + * @author Rod Johnson + * @since 16.03.2003 + */ +public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute { + + /** + * Create a new DefaultTransactionAttribute, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionAttribute() { + super(); + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionAttribute(TransactionAttribute other) { + super(other); + } + + /** + * Create a new DefaultTransactionAttribute with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public DefaultTransactionAttribute(int propagationBehavior) { + super(propagationBehavior); + } + + + /** + * Default behavior is as with EJB: rollback on unchecked exception. + * Additionally attempt to rollback on Error. + * Consistent with TransactionTemplate's behavior. + */ + public boolean rollbackOn(Throwable ex) { + return (ex instanceof RuntimeException || ex instanceof Error); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java new file mode 100644 index 00000000000..167b0e0a4b7 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import java.io.Serializable; + +import org.springframework.util.Assert; + +/** + * {@link TransactionAttribute} implementation that delegates all calls to a given target + * {@link TransactionAttribute} instance. Abstract because it is meant to be subclassed, + * with subclasses overriding specific methods that are not supposed to simply delegate + * to the target instance. + * + * @author Juergen Hoeller + * @since 1.2 + */ +public abstract class DelegatingTransactionAttribute implements TransactionAttribute, Serializable { + + private final TransactionAttribute targetAttribute; + + + /** + * Create a DelegatingTransactionAttribute for the given target attribute. + * @param targetAttribute the target TransactionAttribute to delegate to + */ + public DelegatingTransactionAttribute(TransactionAttribute targetAttribute) { + Assert.notNull(targetAttribute, "Target attribute must not be null"); + this.targetAttribute = targetAttribute; + } + + + public int getPropagationBehavior() { + return this.targetAttribute.getPropagationBehavior(); + } + + public int getIsolationLevel() { + return this.targetAttribute.getIsolationLevel(); + } + + public int getTimeout() { + return this.targetAttribute.getTimeout(); + } + + public boolean isReadOnly() { + return this.targetAttribute.isReadOnly(); + } + + public String getName() { + return this.targetAttribute.getName(); + } + + public boolean rollbackOn(Throwable ex) { + return this.targetAttribute.rollbackOn(ex); + } + + + public boolean equals(Object obj) { + return this.targetAttribute.equals(obj); + } + + public int hashCode() { + return this.targetAttribute.hashCode(); + } + + public String toString() { + return this.targetAttribute.toString(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java new file mode 100644 index 00000000000..77488c13a7a --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.util.ObjectUtils; + +/** + * Very simple implementation of TransactionAttributeSource which will always return + * the same TransactionAttribute for all methods fed to it. The TransactionAttribute + * may be specified, but will otherwise default to PROPAGATION_REQUIRED. This may be + * used in the cases where you want to use the same transaction attribute with all + * methods being handled by a transaction interceptor. + * + * @author Colin Sampaleanu + * @since 15.10.2003 + * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean + * @see org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator + */ +public class MatchAlwaysTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + + + /** + * Allows a transaction attribute to be specified, using the String form, for + * example, "PROPAGATION_REQUIRED". + * @param transactionAttribute The String form of the transactionAttribute to use. + * @see org.springframework.transaction.interceptor.TransactionAttributeEditor + */ + public void setTransactionAttribute(TransactionAttribute transactionAttribute) { + this.transactionAttribute = transactionAttribute; + } + + + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + return this.transactionAttribute; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MatchAlwaysTransactionAttributeSource)) { + return false; + } + MatchAlwaysTransactionAttributeSource otherTas = (MatchAlwaysTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.transactionAttribute, otherTas.transactionAttribute); + } + + public int hashCode() { + return MatchAlwaysTransactionAttributeSource.class.hashCode(); + } + + public String toString() { + return getClass().getName() + ": " + this.transactionAttribute; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java new file mode 100644 index 00000000000..a3eaec359b1 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java @@ -0,0 +1,269 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; + +/** + * Simple {@link TransactionAttributeSource} implementation that + * allows attributes to be stored per method in a {@link Map}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 24.04.2003 + * @see #isMatch + * @see NameMatchTransactionAttributeSource + */ +public class MethodMapTransactionAttributeSource + implements TransactionAttributeSource, BeanClassLoaderAware, InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Map from method name to attribute value */ + private Map methodMap; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private boolean eagerlyInitialized = false; + + private boolean initialized = false; + + /** Map from Method to TransactionAttribute */ + private final Map transactionAttributeMap = new HashMap(); + + /** Map from Method to name pattern used for registration */ + private final Map methodNameMap = new HashMap(); + + + /** + * Set a name/attribute map, consisting of "FQCN.method" method names + * (e.g. "com.mycompany.mycode.MyClass.myMethod") and + * {@link TransactionAttribute} instances (or Strings to be converted + * to TransactionAttribute instances). + *

Intended for configuration via setter injection, typically within + * a Spring bean factory. Relies on {@link #afterPropertiesSet()} + * being called afterwards. + * @param methodMap said {@link Map} from method name to attribute value + * @see TransactionAttribute + * @see TransactionAttributeEditor + */ + public void setMethodMap(Map methodMap) { + this.methodMap = methodMap; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + /** + * Eagerly initializes the specified + * {@link #setMethodMap(java.util.Map) "methodMap"}, if any. + * @see #initMethodMap(java.util.Map) + */ + public void afterPropertiesSet() { + initMethodMap(this.methodMap); + this.eagerlyInitialized = true; + this.initialized = true; + } + + /** + * Initialize the specified {@link #setMethodMap(java.util.Map) "methodMap"}, if any. + * @param methodMap Map from method names to TransactionAttribute instances + * (or Strings to be converted to TransactionAttribute instances) + * @see #setMethodMap + */ + protected void initMethodMap(Map methodMap) { + if (methodMap != null) { + Iterator it = methodMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + if (!(key instanceof String)) { + throw new IllegalArgumentException( + "Invalid method map key [" + key + "]: only Strings allowed"); + } + Object value = entry.getValue(); + // Check whether we need to convert from String to TransactionAttribute. + TransactionAttribute attr = null; + if (value instanceof TransactionAttribute) { + attr = (TransactionAttribute) value; + } + else if (value instanceof String) { + TransactionAttributeEditor editor = new TransactionAttributeEditor(); + editor.setAsText((String) value); + attr = (TransactionAttribute) editor.getValue(); + } + else { + throw new IllegalArgumentException("Value [" + value + "] is neither of type [" + + TransactionAttribute.class.getName() + "] nor a String"); + } + addTransactionalMethod((String) key, attr); + } + } + } + + + /** + * Add an attribute for a transactional method. + *

Method names can end or start with "*" for matching multiple methods. + * @param name class and method name, separated by a dot + * @param attr attribute associated with the method + * @throws IllegalArgumentException in case of an invalid name + */ + public void addTransactionalMethod(String name, TransactionAttribute attr) { + Assert.notNull(name, "Name must not be null"); + int lastDotIndex = name.lastIndexOf("."); + if (lastDotIndex == -1) { + throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName"); + } + String className = name.substring(0, lastDotIndex); + String methodName = name.substring(lastDotIndex + 1); + Class clazz = ClassUtils.resolveClassName(className, this.beanClassLoader); + addTransactionalMethod(clazz, methodName, attr); + } + + /** + * Add an attribute for a transactional method. + * Method names can end or start with "*" for matching multiple methods. + * @param clazz target interface or class + * @param mappedName mapped method name + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(Class clazz, String mappedName, TransactionAttribute attr) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(mappedName, "Mapped name must not be null"); + String name = clazz.getName() + '.' + mappedName; + + // TODO address method overloading? At present this will + // simply match all methods that have the given name. + // Consider EJB syntax (int, String) etc.? + Method[] methods = clazz.getDeclaredMethods(); + List matchingMethods = new ArrayList(); + for (int i = 0; i < methods.length; i++) { + if (isMatch(methods[i].getName(), mappedName)) { + matchingMethods.add(methods[i]); + } + } + if (matchingMethods.isEmpty()) { + throw new IllegalArgumentException( + "Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]"); + } + + // register all matching methods + for (Iterator it = matchingMethods.iterator(); it.hasNext();) { + Method method = (Method) it.next(); + String regMethodName = (String) this.methodNameMap.get(method); + if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) { + // No already registered method name, or more specific + // method name specification now -> (re-)register method. + if (logger.isDebugEnabled() && regMethodName != null) { + logger.debug("Replacing attribute for transactional method [" + method + "]: current name '" + + name + "' is more specific than '" + regMethodName + "'"); + } + this.methodNameMap.put(method, name); + addTransactionalMethod(method, attr); + } + else { + if (logger.isDebugEnabled() && regMethodName != null) { + logger.debug("Keeping attribute for transactional method [" + method + "]: current name '" + + name + "' is not more specific than '" + regMethodName + "'"); + } + } + } + } + + /** + * Add an attribute for a transactional method. + * @param method the method + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(Method method, TransactionAttribute attr) { + Assert.notNull(method, "Method must not be null"); + Assert.notNull(attr, "TransactionAttribute must not be null"); + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + method + "] with attribute [" + attr + "]"); + } + this.transactionAttributeMap.put(method, attr); + } + + /** + * Return if the given method name matches the mapped name. + *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" + * matches, as well as direct equality. + * @param methodName the method name of the class + * @param mappedName the name in the descriptor + * @return if the names match + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + if (this.eagerlyInitialized) { + return (TransactionAttribute) this.transactionAttributeMap.get(method); + } + else { + synchronized (this.transactionAttributeMap) { + if (!this.initialized) { + initMethodMap(this.methodMap); + this.initialized = true; + } + return (TransactionAttribute) this.transactionAttributeMap.get(method); + } + } + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MethodMapTransactionAttributeSource)) { + return false; + } + MethodMapTransactionAttributeSource otherTas = (MethodMapTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.methodMap, otherTas.methodMap); + } + + public int hashCode() { + return MethodMapTransactionAttributeSource.class.hashCode(); + } + + public String toString() { + return getClass().getName() + ": " + this.methodMap; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java new file mode 100644 index 00000000000..388c51d06c2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; + +/** + * Simple {@link TransactionAttributeSource} implementation that + * allows attributes to be matched by registered name. + * + * @author Juergen Hoeller + * @since 21.08.2003 + * @see #isMatch + * @see MethodMapTransactionAttributeSource + */ +public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + /** + * Logger available to subclasses. + *

Static for optimal serialization. + */ + protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); + + /** Keys are method names; values are TransactionAttributes */ + private Map nameMap = new HashMap(); + + + /** + * Set a name/attribute map, consisting of method names + * (e.g. "myMethod") and TransactionAttribute instances + * (or Strings to be converted to TransactionAttribute instances). + * @see TransactionAttribute + * @see TransactionAttributeEditor + */ + public void setNameMap(Map nameMap) { + Iterator it = nameMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + String name = (String) entry.getKey(); + + // Check whether we need to convert from String to TransactionAttribute. + TransactionAttribute attr = null; + if (entry.getValue() instanceof TransactionAttribute) { + attr = (TransactionAttribute) entry.getValue(); + } + else { + TransactionAttributeEditor editor = new TransactionAttributeEditor(); + editor.setAsText(entry.getValue().toString()); + attr = (TransactionAttribute) editor.getValue(); + } + + addTransactionalMethod(name, attr); + } + } + + /** + * Parses the given properties into a name/attribute map. + * Expects method names as keys and String attributes definitions as values, + * parsable into TransactionAttribute instances via TransactionAttributeEditor. + * @see #setNameMap + * @see TransactionAttributeEditor + */ + public void setProperties(Properties transactionAttributes) { + TransactionAttributeEditor tae = new TransactionAttributeEditor(); + for (Iterator it = transactionAttributes.keySet().iterator(); it.hasNext(); ) { + String methodName = (String) it.next(); + String value = transactionAttributes.getProperty(methodName); + tae.setAsText(value); + TransactionAttribute attr = (TransactionAttribute) tae.getValue(); + addTransactionalMethod(methodName, attr); + } + } + + /** + * Add an attribute for a transactional method. + *

Method names can be exact matches, or of the pattern "xxx*", + * "*xxx" or "*xxx*" for matching multiple methods. + * @param methodName the name of the method + * @param attr attribute associated with the method + */ + public void addTransactionalMethod(String methodName, TransactionAttribute attr) { + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); + } + this.nameMap.put(methodName, attr); + } + + + public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) { + // look for direct name match + String methodName = method.getName(); + TransactionAttribute attr = (TransactionAttribute) this.nameMap.get(methodName); + + if (attr == null) { + // Look for most specific name match. + String bestNameMatch = null; + for (Iterator it = this.nameMap.keySet().iterator(); it.hasNext();) { + String mappedName = (String) it.next(); + if (isMatch(methodName, mappedName) && + (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { + attr = (TransactionAttribute) this.nameMap.get(mappedName); + bestNameMatch = mappedName; + } + } + } + + return attr; + } + + /** + * Return if the given method name matches the mapped name. + *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, + * as well as direct equality. Can be overridden in subclasses. + * @param methodName the method name of the class + * @param mappedName the name in the descriptor + * @return if the names match + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof NameMatchTransactionAttributeSource)) { + return false; + } + NameMatchTransactionAttributeSource otherTas = (NameMatchTransactionAttributeSource) other; + return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap); + } + + public int hashCode() { + return NameMatchTransactionAttributeSource.class.hashCode(); + } + + public String toString() { + return getClass().getName() + ": " + this.nameMap; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java new file mode 100644 index 00000000000..4621b3b3369 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java @@ -0,0 +1,52 @@ +/* + * 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.interceptor; + +/** + * Tag subclass of {@link RollbackRuleAttribute} that has the opposite behavior + * to the RollbackRuleAttribute superclass. + * + * @author Rod Johnson + * @since 09.04.2003 + */ +public class NoRollbackRuleAttribute extends RollbackRuleAttribute { + + /** + * Create a new instance of the NoRollbackRuleAttribute class + * for the supplied {@link Throwable} class. + * @param clazz the Throwable class + * @see RollbackRuleAttribute#RollbackRuleAttribute(Class) + */ + public NoRollbackRuleAttribute(Class clazz) { + super(clazz); + } + + /** + * Create a new instance of the NoRollbackRuleAttribute class + * for the supplied exceptionName. + * @param exceptionName the exception name pattern + * @see RollbackRuleAttribute#RollbackRuleAttribute(String) + */ + public NoRollbackRuleAttribute(String exceptionName) { + super(exceptionName); + } + + public String toString() { + return "No" + super.toString(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java new file mode 100644 index 00000000000..ce6a7ba467b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java @@ -0,0 +1,144 @@ +/* + * 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.interceptor; + +import org.springframework.util.Assert; + +import java.io.Serializable; + +/** + * Rule determining whether or not a given exception (and any subclasses) + * should cause a rollback. + * + *

Multiple such rules can be applied to determine whether a transaction + * should commit or rollback after an exception has been thrown. + * + * @author Rod Johnson + * @since 09.04.2003 + * @see NoRollbackRuleAttribute + */ +public class RollbackRuleAttribute implements Serializable{ + + /** + * The {@link RollbackRuleAttribute rollback rule} for + * {@link RuntimeException RuntimeExceptions}. + */ + public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS = + new RollbackRuleAttribute(RuntimeException.class); + + + /** + * Could hold exception, resolving class name but would always require FQN. + * This way does multiple string comparisons, but how often do we decide + * whether to roll back a transaction following an exception? + */ + private final String exceptionName; + + + /** + * Create a new instance of the RollbackRuleAttribute class. + *

This is the preferred way to construct a rollback rule that matches + * the supplied {@link Exception} class (and subclasses). + * @param clazz throwable class; must be {@link Throwable} or a subclass + * of Throwable + * @throws IllegalArgumentException if the supplied clazz is + * not a Throwable type or is null + */ + public RollbackRuleAttribute(Class clazz) { + Assert.notNull(clazz, "'clazz' cannot be null."); + if (!Throwable.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException( + "Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable"); + } + this.exceptionName = clazz.getName(); + } + + /** + * Create a new instance of the RollbackRuleAttribute class + * for the given exceptionName. + *

This can be a substring, with no wildcard support at present. A value + * of "ServletException" would match + * javax.servlet.ServletException and subclasses, for example. + *

NB: Consider carefully how specific the pattern is, and + * whether to include package information (which is not mandatory). For + * example, "Exception" will match nearly anything, and will probably hide + * other rules. "java.lang.Exception" would be correct if "Exception" was + * meant to define a rule for all checked exceptions. With more unusual + * exception names such as "BaseBusinessException" there's no need to use a + * fully package-qualified name. + * @param exceptionName the exception name pattern; can also be a fully + * package-qualified class name + * @throws IllegalArgumentException if the supplied + * exceptionName is null or empty + */ + public RollbackRuleAttribute(String exceptionName) { + Assert.hasText(exceptionName, "'exceptionName' cannot be null or empty."); + this.exceptionName = exceptionName; + } + + + /** + * Return the pattern for the exception name. + */ + public String getExceptionName() { + return exceptionName; + } + + /** + * Return the depth of the superclass matching. + *

0 means ex matches exactly. Returns + * -1 if there is no match. Otherwise, returns depth with the + * lowest depth winning. + */ + public int getDepth(Throwable ex) { + return getDepth(ex.getClass(), 0); + } + + + private int getDepth(Class exceptionClass, int depth) { + if (exceptionClass.getName().indexOf(this.exceptionName) != -1) { + // Found it! + return depth; + } + // If we've gone as far as we can go and haven't found it... + if (exceptionClass.equals(Throwable.class)) { + return -1; + } + return getDepth(exceptionClass.getSuperclass(), depth + 1); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof RollbackRuleAttribute)) { + return false; + } + RollbackRuleAttribute rhs = (RollbackRuleAttribute) other; + return this.exceptionName.equals(rhs.exceptionName); + } + + public int hashCode() { + return this.exceptionName.hashCode(); + } + + public String toString() { + return "RollbackRuleAttribute with pattern [" + this.exceptionName + "]"; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java new file mode 100644 index 00000000000..eaaf9807af2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java @@ -0,0 +1,173 @@ +/* + * 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.interceptor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * TransactionAttribute implementation that works out whether a given exception + * should cause transaction rollback by applying a number of rollback rules, + * both positive and negative. If no rules are relevant to the exception, it + * behaves like DefaultTransactionAttribute (rolling back on runtime exceptions). + * + *

TransactionAttributeEditor creates objects of this class. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 09.04.2003 + * @see TransactionAttributeEditor + */ +public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable { + + /** Prefix for rollback-on-exception rules in description strings */ + public static final String PREFIX_ROLLBACK_RULE = "-"; + + /** Prefix for commit-on-exception rules in description strings */ + public static final String PREFIX_COMMIT_RULE = "+"; + + + /** Static for optimal serializability */ + private static final Log logger = LogFactory.getLog(RuleBasedTransactionAttribute.class); + + private List rollbackRules; + + + /** + * Create a new RuleBasedTransactionAttribute, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + * @see #setRollbackRules + */ + public RuleBasedTransactionAttribute() { + super(); + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + * @see #setRollbackRules + */ + public RuleBasedTransactionAttribute(RuleBasedTransactionAttribute other) { + super(other); + this.rollbackRules = new ArrayList(other.rollbackRules); + } + + /** + * Create a new DefaultTransactionAttribute with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @param rollbackRules the list of RollbackRuleAttributes to apply + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public RuleBasedTransactionAttribute(int propagationBehavior, List rollbackRules) { + super(propagationBehavior); + this.rollbackRules = rollbackRules; + } + + + /** + * Set the list of RollbackRuleAttribute objects + * (and/or NoRollbackRuleAttribute objects) to apply. + * @see RollbackRuleAttribute + * @see NoRollbackRuleAttribute + */ + public void setRollbackRules(List rollbackRules) { + this.rollbackRules = rollbackRules; + } + + /** + * Return the list of RollbackRuleAttribute objects + * (never null). + */ + public List getRollbackRules() { + if (this.rollbackRules == null) { + this.rollbackRules = new LinkedList(); + } + return this.rollbackRules; + } + + + /** + * Winning rule is the shallowest rule (that is, the closest in the + * inheritance hierarchy to the exception). If no rule applies (-1), + * return false. + * @see TransactionAttribute#rollbackOn(java.lang.Throwable) + */ + public boolean rollbackOn(Throwable ex) { + if (logger.isTraceEnabled()) { + logger.trace("Applying rules to determine whether transaction should rollback on " + ex); + } + + RollbackRuleAttribute winner = null; + int deepest = Integer.MAX_VALUE; + + if (this.rollbackRules != null) { + for (Iterator it = this.rollbackRules.iterator(); it.hasNext();) { + RollbackRuleAttribute rule = (RollbackRuleAttribute) it.next(); + int depth = rule.getDepth(ex); + if (depth >= 0 && depth < deepest) { + deepest = depth; + winner = rule; + } + } + } + + if (logger.isTraceEnabled()) { + logger.trace("Winning rollback rule is: " + winner); + } + + // User superclass behavior (rollback on unchecked) if no rule matches. + if (winner == null) { + logger.trace("No relevant rollback rule found: applying default rules"); + return super.rollbackOn(ex); + } + + return !(winner instanceof NoRollbackRuleAttribute); + } + + + public String toString() { + StringBuffer result = getDefinitionDescription(); + if (this.rollbackRules != null) { + for (Iterator it = this.rollbackRules.iterator(); it.hasNext();) { + RollbackRuleAttribute rule = (RollbackRuleAttribute) it.next(); + String sign = (rule instanceof NoRollbackRuleAttribute ? PREFIX_COMMIT_RULE : PREFIX_ROLLBACK_RULE); + result.append(',').append(sign).append(rule.getExceptionName()); + } + } + return result.toString(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java new file mode 100644 index 00000000000..882bd0ce653 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -0,0 +1,455 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import java.lang.reflect.Method; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.NamedThreadLocal; +import org.springframework.transaction.NoTransactionException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.util.ClassUtils; + +/** + * Base class for transactional aspects, such as the AOP Alliance + * {@link TransactionInterceptor} or an AspectJ aspect. + * + *

This enables the underlying Spring transaction infrastructure to be used + * easily to implement an aspect for any aspect system. + * + *

Subclasses are responsible for calling methods in this class in the + * correct order. + * + *

If no transaction name has been specified in the + * TransactionAttribute, the exposed name will be the + * fully-qualified class name + "." + method name + * (by default). + * + *

Uses the Strategy design pattern. A + * PlatformTransactionManager implementation will perform the + * actual transaction management, and a TransactionAttributeSource + * is used for determining transaction definitions. + * + *

A transaction aspect is serializable if it's + * PlatformTransactionManager and + * TransactionAttributeSource are serializable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + * @see #setTransactionManager + * @see #setTransactionAttributes + * @see #setTransactionAttributeSource + */ +public abstract class TransactionAspectSupport implements InitializingBean { + + // NOTE: This class must not implement Serializable because it serves as base + // class for AspectJ aspects (which are not allowed to implement Serializable)! + + /** + * Holder to support the currentTransactionStatus() method, + * and to support communication between different cooperating advices + * (e.g. before and after advice) if the aspect involves more than a + * single method (as will be the case for around advice). + */ + private static final ThreadLocal transactionInfoHolder = + new NamedThreadLocal("Current aspect-driven transaction"); + + + /** + * Subclasses can use this to return the current TransactionInfo. + * Only subclasses that cannot handle all operations in one method, + * such as an AspectJ aspect involving distinct before and after advice, + * need to use this mechanism to get at the current TransactionInfo. + * An around advice such as an AOP Alliance MethodInterceptor can hold a + * reference to the TransactionInfo throughout the aspect method. + *

A TransactionInfo will be returned even if no transaction was created. + * The TransactionInfo.hasTransaction() method can be used to query this. + *

To find out about specific transaction characteristics, consider using + * TransactionSynchronizationManager's isSynchronizationActive() + * and/or isActualTransactionActive() methods. + * @return TransactionInfo bound to this thread, or null if none + * @see TransactionInfo#hasTransaction() + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive() + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + protected static TransactionInfo currentTransactionInfo() throws NoTransactionException { + return (TransactionInfo) transactionInfoHolder.get(); + } + /** + * Return the transaction status of the current method invocation. + * Mainly intended for code that wants to set the current transaction + * rollback-only but not throw an application exception. + * @throws NoTransactionException if the transaction info cannot be found, + * because the method was invoked outside an AOP invocation context + */ + public static TransactionStatus currentTransactionStatus() throws NoTransactionException { + TransactionInfo info = currentTransactionInfo(); + if (info == null) { + throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope"); + } + return currentTransactionInfo().transactionStatus; + } + + + protected final Log logger = LogFactory.getLog(getClass()); + + /** Delegate used to create, commit and rollback transactions */ + private PlatformTransactionManager transactionManager; + + /** Helper used to find transaction attributes */ + private TransactionAttributeSource transactionAttributeSource; + + + /** + * Set the transaction manager. This will perform actual + * transaction management: This class is just a way of invoking it. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Return the transaction manager. + */ + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + /** + * Set properties with method names as keys and transaction attribute + * descriptors (parsed via TransactionAttributeEditor) as values: + * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly". + *

Note: Method names are always applied to the target class, + * no matter if defined in an interface or the class itself. + *

Internally, a NameMatchTransactionAttributeSource will be + * created from the given properties. + * @see #setTransactionAttributeSource + * @see TransactionAttributeEditor + * @see NameMatchTransactionAttributeSource + */ + public void setTransactionAttributes(Properties transactionAttributes) { + NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource(); + tas.setProperties(transactionAttributes); + this.transactionAttributeSource = tas; + } + + /** + * Set multiple transaction attribute sources which are used to find transaction + * attributes. Will build a CompositeTransactionAttributeSource for the given sources. + * @see CompositeTransactionAttributeSource + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see AttributesTransactionAttributeSource + * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) { + this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources); + } + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. If specifying a String property value, a PropertyEditor + * will create a MethodMapTransactionAttributeSource from the value. + * @see TransactionAttributeSourceEditor + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see AttributesTransactionAttributeSource + * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionAttributeSource = transactionAttributeSource; + } + + /** + * Return the transaction attribute source. + */ + public TransactionAttributeSource getTransactionAttributeSource() { + return this.transactionAttributeSource; + } + + + /** + * Check that required properties were set. + */ + public void afterPropertiesSet() { + if (getTransactionManager() == null) { + throw new IllegalArgumentException("Property 'transactionManager' is required"); + } + if (getTransactionAttributeSource() == null) { + throw new IllegalArgumentException( + "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " + + "If there are no transactional methods, then don't use a transaction aspect."); + } + } + + + /** + * Create a transaction if necessary, based on the given method and class. + *

Performs a default TransactionAttribute lookup for the given method. + * @param method method about to execute + * @param targetClass class the method is on + * @return a TransactionInfo object, whether or not a transaction was created. + * The hasTransaction() method on TransactionInfo can be used to tell if there + * was a transaction created. + * @see #getTransactionAttributeSource() + */ + protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) { + // If the transaction attribute is null, the method is non-transactional. + TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); + return createTransactionIfNecessary(txAttr, methodIdentification(method)); + } + + /** + * Convenience method to return a String representation of this Method + * for use in logging. Can be overridden in subclasses to provide a + * different identifier for the given method. + * @param method the method we're interested in + * @return log message identifying this method + * @see org.springframework.util.ClassUtils#getQualifiedMethodName + */ + protected String methodIdentification(Method method) { + return ClassUtils.getQualifiedMethodName(method); + } + + /** + * Create a transaction if necessary based on the given TransactionAttribute. + *

Allows callers to perform custom TransactionAttribute lookups through + * the TransactionAttributeSource. + * @param txAttr the TransactionAttribute (may be null) + * @param joinpointIdentification the fully qualified method name + * (used for monitoring and logging purposes) + * @return a TransactionInfo object, whether or not a transaction was created. + * The hasTransaction() method on TransactionInfo can be used to + * tell if there was a transaction created. + * @see #getTransactionAttributeSource() + */ + protected TransactionInfo createTransactionIfNecessary( + TransactionAttribute txAttr, final String joinpointIdentification) { + + // If no name specified, apply method identification as transaction name. + if (txAttr != null && txAttr.getName() == null) { + txAttr = new DelegatingTransactionAttribute(txAttr) { + public String getName() { + return joinpointIdentification; + } + }; + } + + TransactionStatus status = null; + if (txAttr != null) { + PlatformTransactionManager tm = getTransactionManager(); + if (tm != null) { + status = tm.getTransaction(txAttr); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + + "] because no transaction manager has been configured"); + } + } + } + return prepareTransactionInfo(txAttr, joinpointIdentification, status); + } + + /** + * Prepare a TransactionInfo for the given attribute and status object. + * @param txAttr the TransactionAttribute (may be null) + * @param joinpointIdentification the fully qualified method name + * (used for monitoring and logging purposes) + * @param status the TransactionStatus for the current transaction + * @return the prepared TransactionInfo object + */ + protected TransactionInfo prepareTransactionInfo( + TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) { + + TransactionInfo txInfo = new TransactionInfo(txAttr, joinpointIdentification); + if (txAttr != null) { + // We need a transaction for this method + if (logger.isTraceEnabled()) { + logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // The transaction manager will flag an error if an incompatible tx already exists + txInfo.newTransactionStatus(status); + } + else { + // The TransactionInfo.hasTransaction() method will return + // false. We created it only to preserve the integrity of + // the ThreadLocal stack maintained in this class. + if (logger.isTraceEnabled()) + logger.trace("Don't need to create transaction for [" + joinpointIdentification + + "]: This method isn't transactional."); + } + + // We always bind the TransactionInfo to the thread, even if we didn't create + // a new transaction here. This guarantees that the TransactionInfo stack + // will be managed correctly even if no transaction was created by this aspect. + txInfo.bindToThread(); + return txInfo; + } + + /** + * Execute after successful completion of call, but not after an exception was handled. + * Do nothing if we didn't create a transaction. + * @param txInfo information about the current transaction + */ + protected void commitTransactionAfterReturning(TransactionInfo txInfo) { + if (txInfo != null && txInfo.hasTransaction()) { + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + getTransactionManager().commit(txInfo.getTransactionStatus()); + } + } + + /** + * Handle a throwable, completing the transaction. + * We may commit or roll back, depending on the configuration. + * @param txInfo information about the current transaction + * @param ex throwable encountered + */ + protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { + if (txInfo != null && txInfo.hasTransaction()) { + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + + "] after exception: " + ex); + } + if (txInfo.transactionAttribute.rollbackOn(ex)) { + try { + getTransactionManager().rollback(txInfo.getTransactionStatus()); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by rollback error", ex); + throw err; + } + } + else { + // We don't roll back on this exception. + // Will still roll back if TransactionStatus.isRollbackOnly() is true. + try { + getTransactionManager().commit(txInfo.getTransactionStatus()); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by commit exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by commit exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by commit error", ex); + throw err; + } + } + } + } + + /** + * Reset the TransactionInfo ThreadLocal. + *

Call this in all cases: exception or normal return! + * @param txInfo information about the current transaction (may be null) + */ + protected void cleanupTransactionInfo(TransactionInfo txInfo) { + if (txInfo != null) { + txInfo.restoreThreadLocalStatus(); + } + } + + + /** + * Opaque object used to hold Transaction information. Subclasses + * must pass it back to methods on this class, but not see its internals. + */ + protected class TransactionInfo { + + private final TransactionAttribute transactionAttribute; + + private final String joinpointIdentification; + + private TransactionStatus transactionStatus; + + private TransactionInfo oldTransactionInfo; + + public TransactionInfo(TransactionAttribute transactionAttribute, String joinpointIdentification) { + this.transactionAttribute = transactionAttribute; + this.joinpointIdentification = joinpointIdentification; + } + + public TransactionAttribute getTransactionAttribute() { + return this.transactionAttribute; + } + + /** + * Return a String representation of this joinpoint (usually a Method call) + * for use in logging. + */ + public String getJoinpointIdentification() { + return this.joinpointIdentification; + } + + public void newTransactionStatus(TransactionStatus status) { + this.transactionStatus = status; + } + + public TransactionStatus getTransactionStatus() { + return this.transactionStatus; + } + + /** + * Return whether a transaction was created by this aspect, + * or whether we just have a placeholder to keep ThreadLocal stack integrity. + */ + public boolean hasTransaction() { + return (this.transactionStatus != null); + } + + private void bindToThread() { + // Expose current TransactionStatus, preserving any existing TransactionStatus + // for restoration after this transaction is complete. + this.oldTransactionInfo = (TransactionInfo) transactionInfoHolder.get(); + transactionInfoHolder.set(this); + } + + private void restoreThreadLocalStatus() { + // Use stack to restore old transaction TransactionInfo. + // Will be null if none was set. + transactionInfoHolder.set(this.oldTransactionInfo); + } + + public String toString() { + return this.transactionAttribute.toString(); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java new file mode 100644 index 00000000000..6be460614a5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2005 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.interceptor; + +import org.springframework.transaction.TransactionDefinition; + +/** + * This interface adds a rollbackOn specification to TransactionDefinition. + * As custom rollbackOn is only possible with AOP, this class resides + * in the AOP transaction package. + * + * @author Rod Johnson + * @since 16.03.2003 + * @see DefaultTransactionAttribute + * @see RuleBasedTransactionAttribute + */ +public interface TransactionAttribute extends TransactionDefinition { + + /** + * Should we roll back on the given exception? + * @param ex the exception to evaluate + * @return whether to perform a rollback or not + */ + boolean rollbackOn(Throwable ex); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java new file mode 100644 index 00000000000..fa30d91ad50 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.StringUtils; + +/** + * PropertyEditor for {@link TransactionAttribute} objects. Accepts a String of form + *

PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2 + *

where only propagation code is required. For example: + *

PROPAGATION_MANDATORY,ISOLATION_DEFAULT + * + *

The tokens can be in any order. Propagation and isolation codes + * must use the names of the constants in the TransactionDefinition class. Timeout values + * are in seconds. If no timeout is specified, the transaction manager will apply a default + * timeout specific to the particular transaction manager. + * + *

A "+" before an exception name substring indicates that transactions should commit + * even if this exception is thrown; a "-" that they should roll back. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 24.04.2003 + * @see org.springframework.transaction.TransactionDefinition + * @see org.springframework.core.Constants + */ +public class TransactionAttributeEditor extends PropertyEditorSupport { + + /** + * Format is PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2. + * Null or the empty string means that the method is non transactional. + * @see java.beans.PropertyEditor#setAsText(java.lang.String) + */ + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasLength(text)) { + // tokenize it with "," + String[] tokens = StringUtils.commaDelimitedListToStringArray(text); + RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute(); + for (int i = 0; i < tokens.length; i++) { + // Trim leading and trailing whitespace. + String token = StringUtils.trimWhitespace(tokens[i].trim()); + // Check whether token contains illegal whitespace within text. + if (StringUtils.containsWhitespace(token)) { + throw new IllegalArgumentException( + "Transaction attribute token contains illegal whitespace: [" + token + "]"); + } + // Check token type. + if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_PROPAGATION)) { + attr.setPropagationBehaviorName(token); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_ISOLATION)) { + attr.setIsolationLevelName(token); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_TIMEOUT)) { + String value = token.substring(DefaultTransactionAttribute.PREFIX_TIMEOUT.length()); + attr.setTimeout(Integer.parseInt(value)); + } + else if (token.equals(RuleBasedTransactionAttribute.READ_ONLY_MARKER)) { + attr.setReadOnly(true); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_COMMIT_RULE)) { + attr.getRollbackRules().add(new NoRollbackRuleAttribute(token.substring(1))); + } + else if (token.startsWith(RuleBasedTransactionAttribute.PREFIX_ROLLBACK_RULE)) { + attr.getRollbackRules().add(new RollbackRuleAttribute(token.substring(1))); + } + else { + throw new IllegalArgumentException("Invalid transaction attribute token: [" + token + "]"); + } + } + setValue(attr); + } + else { + setValue(null); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java new file mode 100644 index 00000000000..3dcbf6a6fcb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.lang.reflect.Method; + +/** + * Interface used by TransactionInterceptor. Implementations know + * how to source transaction attributes, whether from configuration, + * metadata attributes at source level, or anywhere else. + * + * @author Rod Johnson + * @since 15.04.2003 + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionProxyFactoryBean#setTransactionAttributeSource + */ +public interface TransactionAttributeSource { + + /** + * Return the transaction attribute for this method. + * Return null if the method is non-transactional. + * @param method method + * @param targetClass target class. May be null, in which + * case the declaring class of the method must be used. + * @return TransactionAttribute the matching transaction attribute, + * or null if none found + */ + TransactionAttribute getTransactionAttribute(Method method, Class targetClass); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java new file mode 100644 index 00000000000..55a41642833 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceAdvisor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; + +/** + * Advisor driven by a {@link TransactionAttributeSource}, used to include + * a {@link TransactionInterceptor} only for methods that are transactional. + * + *

Because the AOP framework caches advice calculations, this is normally + * faster than just letting the TransactionInterceptor run and find out + * itself that it has no work to do. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setTransactionInterceptor + * @see TransactionProxyFactoryBean + */ +public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { + + private TransactionInterceptor transactionInterceptor; + + private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { + protected TransactionAttributeSource getTransactionAttributeSource() { + return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null); + } + }; + + + /** + * Create a new TransactionAttributeSourceAdvisor. + */ + public TransactionAttributeSourceAdvisor() { + } + + /** + * Create a new TransactionAttributeSourceAdvisor. + * @param interceptor the transaction interceptor to use for this advisor + */ + public TransactionAttributeSourceAdvisor(TransactionInterceptor interceptor) { + setTransactionInterceptor(interceptor); + } + + + /** + * Set the transaction interceptor to use for this advisor. + */ + public void setTransactionInterceptor(TransactionInterceptor interceptor) { + this.transactionInterceptor = interceptor; + } + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + */ + public void setClassFilter(ClassFilter classFilter) { + this.pointcut.setClassFilter(classFilter); + } + + + public Advice getAdvice() { + return this.transactionInterceptor; + } + + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java new file mode 100644 index 00000000000..b63a0ce4d5e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.beans.PropertyEditorSupport; +import java.util.Iterator; +import java.util.Properties; + +import org.springframework.beans.propertyeditors.PropertiesEditor; +import org.springframework.util.StringUtils; + +/** + * Property editor that converts a String into a {@link TransactionAttributeSource}. + * The transaction attribute string must be parseable by the + * {@link TransactionAttributeEditor} in this package. + * + *

Strings are in property syntax, with the form:
+ * FQCN.methodName=<transaction attribute string> + * + *

For example:
+ * com.mycompany.mycode.MyClass.myMethod=PROPAGATION_MANDATORY,ISOLATION_DEFAULT + * + *

NOTE: The specified class must be the one where the methods are + * defined; in case of implementing an interface, the interface class name. + * + *

Note: Will register all overloaded methods for a given name. + * Does not support explicit registration of certain overloaded methods. + * Supports "xxx*" mappings, e.g. "notify*" for "notify" and "notifyAll". + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 26.04.2003 + * @see org.springframework.transaction.interceptor.TransactionAttributeEditor + */ +public class TransactionAttributeSourceEditor extends PropertyEditorSupport { + + public void setAsText(String text) throws IllegalArgumentException { + MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource(); + if (StringUtils.hasLength(text)) { + // Use properties editor to tokenize the hold string. + PropertiesEditor propertiesEditor = new PropertiesEditor(); + propertiesEditor.setAsText(text); + Properties props = (Properties) propertiesEditor.getValue(); + + // Now we have properties, process each one individually. + TransactionAttributeEditor tae = new TransactionAttributeEditor(); + for (Iterator iter = props.keySet().iterator(); iter.hasNext();) { + String name = (String) iter.next(); + String value = props.getProperty(name); + + // Convert value to a transaction attribute. + tae.setAsText(value); + TransactionAttribute attr = (TransactionAttribute) tae.getValue(); + + // Register name and attribute. + source.addTransactionalMethod(name, attr); + } + } + setValue(source); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java new file mode 100644 index 00000000000..dbe89553ba0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2008 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.interceptor; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.aop.support.StaticMethodMatcherPointcut; +import org.springframework.util.ObjectUtils; + +/** + * Inner class that implements a Pointcut that matches if the underlying + * {@link TransactionAttributeSource} has an attribute for a given method. + * + * @author Juergen Hoeller + * @since 2.5.5 + */ +abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { + + public boolean matches(Method method, Class targetClass) { + TransactionAttributeSource tas = getTransactionAttributeSource(); + return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TransactionAttributeSourcePointcut)) { + return false; + } + TransactionAttributeSourcePointcut otherPc = (TransactionAttributeSourcePointcut) other; + return ObjectUtils.nullSafeEquals(getTransactionAttributeSource(), otherPc.getTransactionAttributeSource()); + } + + public int hashCode() { + return TransactionAttributeSourcePointcut.class.hashCode(); + } + + public String toString() { + return getClass().getName() + ": " + getTransactionAttributeSource(); + } + + + /** + * Obtain the underlying TransactionAttributeSource (may be null). + * To be implemented by subclasses. + */ + protected abstract TransactionAttributeSource getTransactionAttributeSource(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java new file mode 100644 index 00000000000..d5ce87cc906 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java @@ -0,0 +1,224 @@ +/* + * 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.interceptor; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Properties; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +import org.springframework.transaction.support.TransactionCallback; + +/** + * AOP Alliance MethodInterceptor for declarative transaction + * management using the common Spring transaction infrastructure + * ({@link org.springframework.transaction.PlatformTransactionManager}). + * + *

Derives from the {@link TransactionAspectSupport} class which + * contains the integration with Spring's underlying transaction API. + * TransactionInterceptor simply calls the relevant superclass methods + * such as {@link #createTransactionIfNecessary} in the correct order. + * + *

TransactionInterceptors are thread-safe. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see TransactionProxyFactoryBean + * @see org.springframework.aop.framework.ProxyFactoryBean + * @see org.springframework.aop.framework.ProxyFactory + */ +public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { + + /** + * Create a new TransactionInterceptor. + *

Transaction manager and transaction attributes still need to be set. + * @see #setTransactionManager + * @see #setTransactionAttributes(java.util.Properties) + * @see #setTransactionAttributeSource(TransactionAttributeSource) + */ + public TransactionInterceptor() { + } + + /** + * Create a new TransactionInterceptor. + * @param ptm the transaction manager to perform the actual transaction management + * @param attributes the transaction attributes in properties format + * @see #setTransactionManager + * @see #setTransactionAttributes(java.util.Properties) + */ + public TransactionInterceptor(PlatformTransactionManager ptm, Properties attributes) { + setTransactionManager(ptm); + setTransactionAttributes(attributes); + } + + /** + * Create a new TransactionInterceptor. + * @param ptm the transaction manager to perform the actual transaction management + * @param tas the attribute source to be used to find transaction attributes + * @see #setTransactionManager + * @see #setTransactionAttributeSource(TransactionAttributeSource) + */ + public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) { + setTransactionManager(ptm); + setTransactionAttributeSource(tas); + } + + + public Object invoke(final MethodInvocation invocation) throws Throwable { + // Work out the target class: may be null. + // The TransactionAttributeSource should be passed the target class + // as well as the method, which may be from an interface. + Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null); + + // If the transaction attribute is null, the method is non-transactional. + final TransactionAttribute txAttr = + getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass); + final String joinpointIdentification = methodIdentification(invocation.getMethod()); + + if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) { + // Standard transaction demarcation with getTransaction and commit/rollback calls. + TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification); + Object retVal = null; + try { + // This is an around advice: Invoke the next interceptor in the chain. + // This will normally result in a target object being invoked. + retVal = invocation.proceed(); + } + catch (Throwable ex) { + // target invocation exception + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } + finally { + cleanupTransactionInfo(txInfo); + } + commitTransactionAfterReturning(txInfo); + return retVal; + } + + else { + // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. + try { + Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr, + new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status); + try { + return invocation.proceed(); + } + catch (Throwable ex) { + if (txAttr.rollbackOn(ex)) { + // A RuntimeException: will lead to a rollback. + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + else { + throw new ThrowableHolderException(ex); + } + } + else { + // A normal return value: will lead to a commit. + return new ThrowableHolder(ex); + } + } + finally { + cleanupTransactionInfo(txInfo); + } + } + }); + + // Check result: It might indicate a Throwable to rethrow. + if (result instanceof ThrowableHolder) { + throw ((ThrowableHolder) result).getThrowable(); + } + else { + return result; + } + } + catch (ThrowableHolderException ex) { + throw ex.getCause(); + } + } + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization, although this class itself doesn't carry state anyway... + ois.defaultReadObject(); + + // Serialize all relevant superclass fields. + // Superclass can't implement Serializable because it also serves as base class + // for AspectJ aspects (which are not allowed to implement Serializable)! + setTransactionManager((PlatformTransactionManager) ois.readObject()); + setTransactionAttributeSource((TransactionAttributeSource) ois.readObject()); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + // Rely on default serialization, although this class itself doesn't carry state anyway... + oos.defaultWriteObject(); + + // Deserialize superclass fields. + oos.writeObject(getTransactionManager()); + oos.writeObject(getTransactionAttributeSource()); + } + + + /** + * Internal holder class for a Throwable, used as a return value + * from a TransactionCallback (to be subsequently unwrapped again). + */ + private static class ThrowableHolder { + + private final Throwable throwable; + + public ThrowableHolder(Throwable throwable) { + this.throwable = throwable; + } + + public final Throwable getThrowable() { + return this.throwable; + } + } + + + /** + * Internal holder class for a Throwable, used as a RuntimeException to be + * thrown from a TransactionCallback (and subsequently unwrapped again). + */ + private static class ThrowableHolderException extends RuntimeException { + + public ThrowableHolderException(Throwable throwable) { + super(throwable); + } + + public String toString() { + return getCause().toString(); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java new file mode 100644 index 00000000000..bfdd9c1c3a2 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java @@ -0,0 +1,198 @@ +/* + * Copyright 2002-2006 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.interceptor; + +import java.util.Properties; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Proxy factory bean for simplified declarative transaction handling. + * This is a convenient alternative to a standard AOP + * {@link org.springframework.aop.framework.ProxyFactoryBean} + * with a separate {@link TransactionInterceptor} definition. + * + *

This class is intended to cover the typical case of declarative + * transaction demarcation: namely, wrapping a singleton target object with a + * transactional proxy, proxying all the interfaces that the target implements. + * + *

There are three main properties that need to be specified: + *

+ * + *

If the "transactionManager" property is not set explicitly and this {@link FactoryBean} + * is running in a {@link ListableBeanFactory}, a single matching bean of type + * {@link PlatformTransactionManager} will be fetched from the {@link BeanFactory}. + * + *

In contrast to {@link TransactionInterceptor}, the transaction attributes are + * specified as properties, with method names as keys and transaction attribute + * descriptors as values. Method names are always applied to the target class. + * + *

Internally, a {@link TransactionInterceptor} instance is used, but the user of this + * class does not have to care. Optionally, a method pointcut can be specified + * to cause conditional invocation of the underlying {@link TransactionInterceptor}. + * + *

The "preInterceptors" and "postInterceptors" properties can be set to add + * additional interceptors to the mix, like + * {@link org.springframework.aop.interceptor.PerformanceMonitorInterceptor} or + * {@link org.springframework.orm.hibernate3.HibernateInterceptor} / + * {@link org.springframework.orm.jdo.JdoInterceptor}. + * + *

HINT: This class is often used with parent / child bean definitions. + * Typically, you will define the transaction manager and default transaction + * attributes (for method name patterns) in an abstract parent bean definition, + * deriving concrete child bean definitions for specific target objects. + * This reduces the per-bean definition effort to a minimum. + * + *

+ * <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
+ *     abstract="true">
+ *   <property name="transactionManager" ref="transactionManager"/>
+ *   <property name="transactionAttributes">
+ *     <props>
+ *       <prop key="insert*">PROPAGATION_REQUIRED</prop>
+ *       <prop key="update*">PROPAGATION_REQUIRED</prop>
+ *       <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
+ *     </props>
+ *   </property>
+ * </bean>
+ *
+ * <bean id="myProxy" parent="baseTransactionProxy">
+ *   <property name="target" ref="myTarget"/>
+ * </bean>
+ *
+ * <bean id="yourProxy" parent="baseTransactionProxy">
+ *   <property name="target" ref="yourTarget"/>
+ * </bean>
+ * + * @author Juergen Hoeller + * @author Dmitriy Kopylenko + * @author Rod Johnson + * @since 21.08.2003 + * @see #setTransactionManager + * @see #setTarget + * @see #setTransactionAttributes + * @see TransactionInterceptor + * @see org.springframework.aop.framework.ProxyFactoryBean + */ +public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBean + implements FactoryBean, BeanFactoryAware { + + private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + + private Pointcut pointcut; + + + /** + * Set the transaction manager. This will perform actual + * transaction management: This class is just a way of invoking it. + * @see TransactionInterceptor#setTransactionManager + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionInterceptor.setTransactionManager(transactionManager); + } + + /** + * Set properties with method names as keys and transaction attribute + * descriptors (parsed via TransactionAttributeEditor) as values: + * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly". + *

Note: Method names are always applied to the target class, + * no matter if defined in an interface or the class itself. + *

Internally, a NameMatchTransactionAttributeSource will be + * created from the given properties. + * @see #setTransactionAttributeSource + * @see TransactionInterceptor#setTransactionAttributes + * @see TransactionAttributeEditor + * @see NameMatchTransactionAttributeSource + */ + public void setTransactionAttributes(Properties transactionAttributes) { + this.transactionInterceptor.setTransactionAttributes(transactionAttributes); + } + + /** + * Set the transaction attribute source which is used to find transaction + * attributes. If specifying a String property value, a PropertyEditor + * will create a MethodMapTransactionAttributeSource from the value. + * @see #setTransactionAttributes + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionAttributeSourceEditor + * @see MethodMapTransactionAttributeSource + * @see NameMatchTransactionAttributeSource + * @see AttributesTransactionAttributeSource + * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { + this.transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + } + + /** + * Set a pointcut, i.e a bean that can cause conditional invocation + * of the TransactionInterceptor depending on method and attributes passed. + * Note: Additional interceptors are always invoked. + * @see #setPreInterceptors + * @see #setPostInterceptors + */ + public void setPointcut(Pointcut pointcut) { + this.pointcut = pointcut; + } + + /** + * This callback is optional: If running in a BeanFactory and no transaction + * manager has been set explicitly, a single matching bean of type + * PlatformTransactionManager will be fetched from the BeanFactory. + * @see org.springframework.beans.factory.BeanFactoryUtils#beanOfTypeIncludingAncestors + * @see org.springframework.transaction.PlatformTransactionManager + */ + public void setBeanFactory(BeanFactory beanFactory) { + if (this.transactionInterceptor.getTransactionManager() == null && + beanFactory instanceof ListableBeanFactory) { + ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; + PlatformTransactionManager ptm = (PlatformTransactionManager) + BeanFactoryUtils.beanOfTypeIncludingAncestors(lbf, PlatformTransactionManager.class); + this.transactionInterceptor.setTransactionManager(ptm); + } + } + + + /** + * Creates an advisor for this FactoryBean's TransactionInterceptor. + */ + protected Object createMainInterceptor() { + this.transactionInterceptor.afterPropertiesSet(); + if (this.pointcut != null) { + return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor); + } + else { + // Rely on default pointcut. + return new TransactionAttributeSourceAdvisor(this.transactionInterceptor); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/package.html new file mode 100644 index 00000000000..5afabeedf18 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/package.html @@ -0,0 +1,17 @@ + + + +AOP-based solution for declarative transaction demarcation. +Builds on the AOP infrastructure in org.springframework.aop.framework. +Any POJO can be transactionally advised with Spring. + +

The TransactionFactoryProxyBean can be used to create transactional +AOP proxies transparently to code that uses them. + +

The TransactionInterceptor is the AOP Alliance MethodInterceptor that +delivers transactional advice, based on the Spring transaction abstraction. +This allows declarative transaction management in any environment, +even without JTA if an application uses only a single database. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JotmFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JotmFactoryBean.java new file mode 100644 index 00000000000..1d681605617 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JotmFactoryBean.java @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2008 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.jta; + +import javax.naming.NamingException; +import javax.transaction.SystemException; + +import org.objectweb.jotm.Current; +import org.objectweb.jotm.Jotm; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; + +/** + * {@link FactoryBean} that retrieves the JTA UserTransaction/TransactionManager + * for ObjectWeb's JOTM. Will retrieve + * an already active JOTM instance if found (e.g. if running in JOnAS), + * else create a new local JOTM instance. + * + *

With JOTM, the same object implements both the + * {@link javax.transaction.UserTransaction} and the + * {@link javax.transaction.TransactionManager} interface, + * as returned by this FactoryBean. + * + *

A local JOTM instance is well-suited for working in conjunction with + * ObjectWeb's XAPool, e.g. with bean + * definitions like the following: + * + *

+ * <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/>
+ *
+ * <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
+ *   <property name="userTransaction" ref="jotm"/>
+ * </bean>
+ *
+ * <bean id="innerDataSource" class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
+ *   <property name="transactionManager" ref="jotm"/>
+ *   <property name="driverName" value="..."/>
+ *   <property name="url" value="..."/>
+ *   <property name="user" value="..."/>
+ *   <property name="password" value="..."/>
+ * </bean>
+ *
+ * <bean id="dataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
+ *   <property name="dataSource" ref="innerDataSource"/>
+ *   <property name="user" value="..."/>
+ *   <property name="password" value="..."/>
+ *   <property name="maxSize" value="..."/>
+ * </bean>
+ * + * Note that Spring's {@link JtaTransactionManager} will automatically detect + * that the passed-in UserTransaction reference also implements the + * TransactionManager interface. Hence, it is not necessary to specify a + * separate reference for JtaTransactionManager's "transactionManager" property. + * + *

Implementation note: This FactoryBean uses JOTM's static access method + * to obtain the JOTM {@link org.objectweb.jotm.Current} object, which + * implements both the UserTransaction and the TransactionManager interface, + * as mentioned above. + * + * @author Juergen Hoeller + * @since 21.01.2004 + * @see JtaTransactionManager#setUserTransaction + * @see JtaTransactionManager#setTransactionManager + * @see org.objectweb.jotm.Current + */ +public class JotmFactoryBean implements FactoryBean, DisposableBean { + + private Current jotmCurrent; + + private Jotm jotm; + + + public JotmFactoryBean() throws NamingException { + // Check for already active JOTM instance. + this.jotmCurrent = Current.getCurrent(); + + // If none found, create new local JOTM instance. + if (this.jotmCurrent == null) { + // Only for use within the current Spring context: + // local, not bound to registry. + this.jotm = new Jotm(true, false); + this.jotmCurrent = Current.getCurrent(); + } + } + + /** + * Set the default transaction timeout for the JOTM instance. + *

Should only be called for a local JOTM instance, + * not when accessing an existing (shared) JOTM instance. + */ + public void setDefaultTimeout(int defaultTimeout) { + this.jotmCurrent.setDefaultTimeout(defaultTimeout); + // The following is a JOTM oddity: should be used for demarcation transaction only, + // but is required here in order to actually get rid of JOTM's default (60 seconds). + try { + this.jotmCurrent.setTransactionTimeout(defaultTimeout); + } + catch (SystemException ex) { + // should never happen + } + } + + + /** + * Return the JOTM instance created by this factory bean, if any. + * Will be null if an already active JOTM instance is used. + *

Application code should never need to access this. + */ + public Jotm getJotm() { + return this.jotm; + } + + public Object getObject() { + return this.jotmCurrent; + } + + public Class getObjectType() { + return this.jotmCurrent.getClass(); + } + + public boolean isSingleton() { + return true; + } + + + /** + * Stop the local JOTM instance, if created by this FactoryBean. + */ + public void destroy() { + if (this.jotm != null) { + this.jotm.stop(); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaAfterCompletionSynchronization.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaAfterCompletionSynchronization.java new file mode 100644 index 00000000000..ecbb2a100dc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaAfterCompletionSynchronization.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2006 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.jta; + +import java.util.List; + +import javax.transaction.Status; +import javax.transaction.Synchronization; + +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationUtils; + +/** + * Adapter for a JTA Synchronization, invoking the afterCompletion of + * Spring TransactionSynchronizations after the outer JTA transaction has completed. + * Applied when participating in an existing (non-Spring) JTA transaction. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class JtaAfterCompletionSynchronization implements Synchronization { + + private final List synchronizations; + + + /** + * Create a new JtaAfterCompletionSynchronization for the given synchronization objects. + * @param synchronizations the List of TransactionSynchronization objects + * @see org.springframework.transaction.support.TransactionSynchronization + */ + public JtaAfterCompletionSynchronization(List synchronizations) { + this.synchronizations = synchronizations; + } + + + public void beforeCompletion() { + } + + public void afterCompletion(int status) { + switch (status) { + case Status.STATUS_COMMITTED: + try { + TransactionSynchronizationUtils.invokeAfterCommit(this.synchronizations); + } + finally { + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_COMMITTED); + } + break; + case Status.STATUS_ROLLEDBACK: + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); + break; + default: + TransactionSynchronizationUtils.invokeAfterCompletion( + this.synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + } +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java new file mode 100644 index 00000000000..f0689a8b8cb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java @@ -0,0 +1,1214 @@ +/* + * Copyright 2002-2008 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.jta; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.List; +import java.util.Properties; + +import javax.naming.NamingException; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.InvalidTransactionException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jndi.JndiTemplate; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.HeuristicCompletionException; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.InvalidIsolationLevelException; +import org.springframework.transaction.NestedTransactionNotSupportedException; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionSuspensionNotSupportedException; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.UnexpectedRollbackException; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * {@link org.springframework.transaction.PlatformTransactionManager} implementation + * for JTA, delegating to a backend JTA provider. This is typically used to delegate + * to a J2EE server's transaction coordinator, but may also be configured with a + * local JTA provider which is embedded within the application. + * + *

This transaction manager is appropriate for handling distributed transactions, + * i.e. transactions that span multiple resources, and for controlling transactions on + * application server resources (e.g. JDBC DataSources available in JNDI) in general. + * For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient, + * and for accessing a single resource with Hibernate (including transactional cache), + * HibernateTransactionManager is appropriate, for example. + * + *

Transaction synchronization is active by default, to allow data access support + * classes to register resources that are opened within the transaction for closing at + * transaction completion time. Spring's support classes for JDBC, Hibernate, JDO etc + * all perform such registration, allowing for reuse of the same Hibernate Session etc + * within the transaction. Standard JTA does not even guarantee that for Connections + * from a transactional JDBC DataSource: Spring's synchronization solves those issues. + * + *

For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER), a plain + * JtaTransactionManager definition is all you need, completely portable across all + * J2EE servers. This corresponds to the functionality of the JTA UserTransaction, + * for which J2EE specifies a standard JNDI name ("java:comp/UserTransaction"). + * There is no need to configure a server-specific TransactionManager lookup for this + * kind of JTA usage. + * + *

Note: Advanced JTA usage below. Dealing with these mechanisms is not + * necessary for typical usage scenarios. + * + *

Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with + * a JTA TransactionManager being registered, via the "transactionManagerName" or + * "transactionManager" property. The location of this well-defined JTA object is + * not specified by J2EE; it is specific to each J2EE server, often kept + * in JNDI like the JTA UserTransaction. Some well-known JNDI locations are: + *

+ * + *

All of these cases are autodetected by JtaTransactionManager, provided that the + * "autodetectTransactionManager" flag is set to "true" (which it is by default). + * + *

Note: Support for the JTA TransactionManager interface is not required by J2EE. + * Almost all J2EE servers expose it, but do so as extension to J2EE. There might be some + * issues with compatibility, despite the TransactionManager interface being part of JTA. + * As a consequence, Spring provides various vendor-specific PlatformTransactionManagers, + * which are recommended to be used if appropriate: {@link WebLogicJtaTransactionManager}, + * {@link WebSphereUowTransactionManager} and {@link OC4JJtaTransactionManager}. + * For all other J2EE servers, the standard JtaTransactionManager is sufficient. + * + *

Consider using Spring 2.5's tx:jta-transaction-manager configuration + * element for automatically picking the appropriate JTA platform transaction manager + * (automatically detecting WebLogic, WebSphere and OC4J). + * + *

This pure JtaTransactionManager supports timeouts but not per-transaction + * isolation levels. Custom subclasses may override {@link #doJtaBegin} for + * specific JTA extensions in order to provide this functionality; Spring includes + * corresponding {@link WebLogicJtaTransactionManager} and {@link OC4JJtaTransactionManager} + * classes, for BEA's WebLogic Server and Oracle's OC4J, respectively. Such adapters + * for specific J2EE transaction coordinators may also expose transaction names for + * monitoring; with standard JTA, transaction names will simply be ignored. + * + *

JTA 1.1 adds the TransactionSynchronizationRegistry facility, as public Java EE 5 + * API in addition to the standard JTA UserTransaction handle. As of Spring 2.5, this + * JtaTransactionManager autodetects the TransactionSynchronizationRegistry and uses + * it for registering Spring-managed synchronizations when participating in an existing + * JTA transaction (e.g. controlled by EJB CMT). If no TransactionSynchronizationRegistry + * is available (or the JTA 1.1 API isn't available), then such synchronizations + * will be registered via the (non-J2EE) JTA TransactionManager handle. + * + *

This class is serializable. However, active synchronizations do not survive + * serialization. + * + * @author Juergen Hoeller + * @since 24.03.2003 + * @see javax.transaction.UserTransaction + * @see javax.transaction.TransactionManager + * @see javax.transaction.TransactionSynchronizationRegistry + * @see #setUserTransactionName + * @see #setUserTransaction + * @see #setTransactionManagerName + * @see #setTransactionManager + * @see JotmFactoryBean + * @see WebSphereTransactionManagerFactoryBean + * @see WebLogicJtaTransactionManager + */ +public class JtaTransactionManager extends AbstractPlatformTransactionManager + implements TransactionFactory, InitializingBean, Serializable { + + /** + * Default JNDI location for the JTA UserTransaction. Many J2EE servers + * also provide support for the JTA TransactionManager interface there. + * @see #setUserTransactionName + * @see #setAutodetectTransactionManager + */ + public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction"; + + /** + * Fallback JNDI locations for the JTA TransactionManager. Applied if + * the JTA UserTransaction does not implement the JTA TransactionManager + * interface, provided that the "autodetectTransactionManager" flag is "true". + * @see #setTransactionManagerName + * @see #setAutodetectTransactionManager + */ + public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES = + new String[] {"java:comp/TransactionManager", "java:appserver/TransactionManager", + "java:pm/TransactionManager", "java:/TransactionManager"}; + + /** + * Standard Java EE 5 JNDI location for the JTA TransactionSynchronizationRegistry. + * Autodetected when available. + */ + public static final String DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME = + "java:comp/TransactionSynchronizationRegistry"; + + + private static final String TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME = + "javax.transaction.TransactionSynchronizationRegistry"; + + + private transient JndiTemplate jndiTemplate = new JndiTemplate(); + + private transient UserTransaction userTransaction; + + private String userTransactionName; + + private boolean autodetectUserTransaction = true; + + private boolean cacheUserTransaction = true; + + private boolean userTransactionObtainedFromJndi = false; + + private transient TransactionManager transactionManager; + + private String transactionManagerName; + + private boolean autodetectTransactionManager = true; + + private String transactionSynchronizationRegistryName; + + private transient Object transactionSynchronizationRegistry; + + private boolean allowCustomIsolationLevels = false; + + + /** + * Create a new JtaTransactionManager instance, to be configured as bean. + * Invoke afterPropertiesSet to activate the configuration. + * @see #setUserTransactionName + * @see #setUserTransaction + * @see #setTransactionManagerName + * @see #setTransactionManager + * @see #afterPropertiesSet() + */ + public JtaTransactionManager() { + setNestedTransactionAllowed(true); + } + + /** + * Create a new JtaTransactionManager instance. + * @param userTransaction the JTA UserTransaction to use as direct reference + */ + public JtaTransactionManager(UserTransaction userTransaction) { + this(); + Assert.notNull(userTransaction, "UserTransaction must not be null"); + this.userTransaction = userTransaction; + } + + /** + * Create a new JtaTransactionManager instance. + * @param userTransaction the JTA UserTransaction to use as direct reference + * @param transactionManager the JTA TransactionManager to use as direct reference + */ + public JtaTransactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { + this(); + Assert.notNull(userTransaction, "UserTransaction must not be null"); + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.userTransaction = userTransaction; + this.transactionManager = transactionManager; + } + + /** + * Create a new JtaTransactionManager instance. + * @param transactionManager the JTA TransactionManager to use as direct reference + */ + public JtaTransactionManager(TransactionManager transactionManager) { + this(); + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + this.userTransaction = buildUserTransaction(transactionManager); + } + + + /** + * Set the JndiTemplate to use for JNDI lookups. + * A default one is used if not set. + */ + public void setJndiTemplate(JndiTemplate jndiTemplate) { + if (jndiTemplate == null) { + throw new IllegalArgumentException("jndiTemplate must not be null"); + } + this.jndiTemplate = jndiTemplate; + } + + /** + * Return the JndiTemplate used for JNDI lookups. + */ + public JndiTemplate getJndiTemplate() { + return this.jndiTemplate; + } + + /** + * Set the JNDI environment to use for JNDI lookups. + * Creates a JndiTemplate with the given environment settings. + * @see #setJndiTemplate + */ + public void setJndiEnvironment(Properties jndiEnvironment) { + this.jndiTemplate = new JndiTemplate(jndiEnvironment); + } + + /** + * Return the JNDI environment to use for JNDI lookups. + */ + public Properties getJndiEnvironment() { + return this.jndiTemplate.getEnvironment(); + } + + + /** + * Set the JTA UserTransaction to use as direct reference. + *

Typically just used for local JTA setups; in a J2EE environment, + * the UserTransaction will always be fetched from JNDI. + * @see #setUserTransactionName + * @see #setAutodetectUserTransaction + */ + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + /** + * Return the JTA UserTransaction that this transaction manager uses. + */ + public UserTransaction getUserTransaction() { + return this.userTransaction; + } + + /** + * Set the JNDI name of the JTA UserTransaction. + *

Note that the UserTransaction will be autodetected at the J2EE default + * location "java:comp/UserTransaction" if not specified explicitly. + * @see #DEFAULT_USER_TRANSACTION_NAME + * @see #setUserTransaction + * @see #setAutodetectUserTransaction + */ + public void setUserTransactionName(String userTransactionName) { + this.userTransactionName = userTransactionName; + } + + /** + * Set whether to autodetect the JTA UserTransaction at its default + * JNDI location "java:comp/UserTransaction", as specified by J2EE. + * Will proceed without UserTransaction if none found. + *

Default is "true", autodetecting the UserTransaction unless + * it has been specified explicitly. Turn this flag off to allow for + * JtaTransactionManager operating against the TransactionManager only, + * despite a default UserTransaction being available. + * @see #DEFAULT_USER_TRANSACTION_NAME + */ + public void setAutodetectUserTransaction(boolean autodetectUserTransaction) { + this.autodetectUserTransaction = autodetectUserTransaction; + } + + /** + * Set whether to cache the JTA UserTransaction object fetched from JNDI. + *

Default is "true": UserTransaction lookup will only happen at startup, + * reusing the same UserTransaction handle for all transactions of all threads. + * This is the most efficient choice for all application servers that provide + * a shared UserTransaction object (the typical case). + *

Turn this flag off to enforce a fresh lookup of the UserTransaction + * for every transaction. This is only necessary for application servers + * that return a new UserTransaction for every transaction, keeping state + * tied to the UserTransaction object itself rather than the current thread. + * @see #setUserTransactionName + */ + public void setCacheUserTransaction(boolean cacheUserTransaction) { + this.cacheUserTransaction = cacheUserTransaction; + } + + /** + * Set the JTA TransactionManager to use as direct reference. + *

A TransactionManager is necessary for suspending and resuming transactions, + * as this not supported by the UserTransaction interface. + *

Note that the TransactionManager will be autodetected if the JTA + * UserTransaction object implements the JTA TransactionManager interface too, + * as well as autodetected at various well-known fallback JNDI locations. + * @see #setTransactionManagerName + * @see #setAutodetectTransactionManager + */ + public void setTransactionManager(TransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Return the JTA TransactionManager that this transaction manager uses. + */ + public TransactionManager getTransactionManager() { + return this.transactionManager; + } + + /** + * Set the JNDI name of the JTA TransactionManager. + *

A TransactionManager is necessary for suspending and resuming transactions, + * as this not supported by the UserTransaction interface. + *

Note that the TransactionManager will be autodetected if the JTA + * UserTransaction object implements the JTA TransactionManager interface too, + * as well as autodetected at various well-known fallback JNDI locations. + * @see #setTransactionManager + * @see #setAutodetectTransactionManager + */ + public void setTransactionManagerName(String transactionManagerName) { + this.transactionManagerName = transactionManagerName; + } + + /** + * Set whether to autodetect a JTA UserTransaction object that implements + * the JTA TransactionManager interface too (i.e. the JNDI location for the + * TransactionManager is "java:comp/UserTransaction", same as for the UserTransaction). + * Also checks the fallback JNDI locations "java:comp/TransactionManager" and + * "java:/TransactionManager". Will proceed without TransactionManager if none found. + *

Default is "true", autodetecting the TransactionManager unless it has been + * specified explicitly. Can be turned off to deliberately ignore an available + * TransactionManager, for example when there are known issues with suspend/resume + * and any attempt to use REQUIRES_NEW or NOT_SUPPORTED should fail fast. + * @see #FALLBACK_TRANSACTION_MANAGER_NAMES + */ + public void setAutodetectTransactionManager(boolean autodetectTransactionManager) { + this.autodetectTransactionManager = autodetectTransactionManager; + } + + /** + * Set the JNDI name of the JTA 1.1 TransactionSynchronizationRegistry. + *

Note that the TransactionSynchronizationRegistry will be autodetected + * at the Java EE 5 default location "java:comp/TransactionSynchronizationRegistry" + * if not specified explicitly. + * @see #DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME + */ + public void setTransactionSynchronizationRegistryName(String transactionSynchronizationRegistryName) { + this.transactionSynchronizationRegistryName = transactionSynchronizationRegistryName; + } + + /** + * Set whether to allow custom isolation levels to be specified. + *

Default is "false", throwing an exception if a non-default isolation level + * is specified for a transaction. Turn this flag on if affected resource adapters + * check the thread-bound transaction context and apply the specified isolation + * levels individually (e.g. through a IsolationLevelDataSourceRouter). + * @see org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter + */ + public void setAllowCustomIsolationLevels(boolean allowCustomIsolationLevels) { + this.allowCustomIsolationLevels = allowCustomIsolationLevels; + } + + + /** + * Initialize the UserTransaction as well as the TransactionManager handle. + * @see #initUserTransactionAndTransactionManager() + */ + public void afterPropertiesSet() throws TransactionSystemException { + initUserTransactionAndTransactionManager(); + checkUserTransactionAndTransactionManager(); + initTransactionSynchronizationRegistry(); + } + + /** + * Initialize the UserTransaction as well as the TransactionManager handle. + * @throws TransactionSystemException if initialization failed + */ + protected void initUserTransactionAndTransactionManager() throws TransactionSystemException { + // Fetch JTA UserTransaction from JNDI, if necessary. + if (this.userTransaction == null) { + if (StringUtils.hasLength(this.userTransactionName)) { + this.userTransaction = lookupUserTransaction(this.userTransactionName); + this.userTransactionObtainedFromJndi = true; + } + else { + this.userTransaction = retrieveUserTransaction(); + } + } + + // Fetch JTA TransactionManager from JNDI, if necessary. + if (this.transactionManager == null) { + if (StringUtils.hasLength(this.transactionManagerName)) { + this.transactionManager = lookupTransactionManager(this.transactionManagerName); + } + else { + this.transactionManager = retrieveTransactionManager(); + } + } + + // Autodetect UserTransaction at its default JNDI location. + if (this.userTransaction == null && this.autodetectUserTransaction) { + this.userTransaction = findUserTransaction(); + } + + // Autodetect UserTransaction object that implements TransactionManager, + // and check fallback JNDI locations else. + if (this.transactionManager == null && this.autodetectTransactionManager) { + this.transactionManager = findTransactionManager(this.userTransaction); + } + + // If only JTA TransactionManager specified, create UserTransaction handle for it. + if (this.userTransaction == null && this.transactionManager != null) { + this.userTransaction = buildUserTransaction(this.transactionManager); + } + } + + /** + * Check the UserTransaction as well as the TransactionManager handle, + * assuming standard JTA requirements. + * @throws IllegalStateException if no sufficient handles are available + */ + protected void checkUserTransactionAndTransactionManager() throws IllegalStateException { + // We at least need the JTA UserTransaction. + if (this.userTransaction != null) { + if (logger.isInfoEnabled()) { + logger.info("Using JTA UserTransaction: " + this.userTransaction); + } + } + else { + throw new IllegalStateException("No JTA UserTransaction available - specify either " + + "'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'"); + } + + // For transaction suspension, the JTA TransactionManager is necessary too. + if (this.transactionManager != null) { + if (logger.isInfoEnabled()) { + logger.info("Using JTA TransactionManager: " + this.transactionManager); + } + } + else { + logger.warn("No JTA TransactionManager found: " + + "transaction suspension and synchronization with existing JTA transactions not available"); + } + } + + /** + * Initialize the JTA 1.1 TransactionSynchronizationRegistry, if available. + *

To be called after {@link #initUserTransactionAndTransactionManager()}, + * since it may check the UserTransaction and TransactionManager handles. + * @throws TransactionSystemException if initialization failed + */ + protected void initTransactionSynchronizationRegistry() { + if (StringUtils.hasLength(this.transactionSynchronizationRegistryName)) { + this.transactionSynchronizationRegistry = + lookupTransactionSynchronizationRegistry(this.transactionSynchronizationRegistryName); + } + else { + this.transactionSynchronizationRegistry = retrieveTransactionSynchronizationRegistry(); + if (this.transactionSynchronizationRegistry == null) { + this.transactionSynchronizationRegistry = + findTransactionSynchronizationRegistry(this.userTransaction, this.transactionManager); + } + } + + if (this.transactionSynchronizationRegistry != null) { + if (logger.isInfoEnabled()) { + logger.info("Using JTA TransactionSynchronizationRegistry: " + this.transactionSynchronizationRegistry); + } + } + } + + + /** + * Build a UserTransaction handle based on the given TransactionManager. + * @param transactionManager the TransactionManager + * @return a corresponding UserTransaction handle + */ + protected UserTransaction buildUserTransaction(TransactionManager transactionManager) { + if (transactionManager instanceof UserTransaction) { + return (UserTransaction) transactionManager; + } + else { + return new UserTransactionAdapter(transactionManager); + } + } + + /** + * Look up the JTA UserTransaction in JNDI via the configured name. + *

Called by afterPropertiesSet if no direct UserTransaction reference was set. + * Can be overridden in subclasses to provide a different UserTransaction object. + * @param userTransactionName the JNDI name of the UserTransaction + * @return the UserTransaction object + * @throws TransactionSystemException if the JNDI lookup failed + * @see #setJndiTemplate + * @see #setUserTransactionName + */ + protected UserTransaction lookupUserTransaction(String userTransactionName) + throws TransactionSystemException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]"); + } + return (UserTransaction) getJndiTemplate().lookup(userTransactionName, UserTransaction.class); + } + catch (NamingException ex) { + throw new TransactionSystemException( + "JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex); + } + } + + /** + * Look up the JTA TransactionManager in JNDI via the configured name. + *

Called by afterPropertiesSet if no direct TransactionManager reference was set. + * Can be overridden in subclasses to provide a different TransactionManager object. + * @param transactionManagerName the JNDI name of the TransactionManager + * @return the UserTransaction object + * @throws TransactionSystemException if the JNDI lookup failed + * @see #setJndiTemplate + * @see #setTransactionManagerName + */ + protected TransactionManager lookupTransactionManager(String transactionManagerName) + throws TransactionSystemException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Retrieving JTA TransactionManager from JNDI location [" + transactionManagerName + "]"); + } + return (TransactionManager) getJndiTemplate().lookup(transactionManagerName, TransactionManager.class); + } + catch (NamingException ex) { + throw new TransactionSystemException( + "JTA TransactionManager is not available at JNDI location [" + transactionManagerName + "]", ex); + } + } + + /** + * Look up the JTA 1.1 TransactionSynchronizationRegistry in JNDI via the configured name. + *

Can be overridden in subclasses to provide a different TransactionManager object. + * @param registryName the JNDI name of the + * TransactionSynchronizationRegistry + * @return the TransactionSynchronizationRegistry object + * @throws TransactionSystemException if the JNDI lookup failed + * @see #setJndiTemplate + * @see #setTransactionSynchronizationRegistryName + */ + protected Object lookupTransactionSynchronizationRegistry(String registryName) + throws TransactionSystemException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Retrieving JTA TransactionSynchronizationRegistry from JNDI location [" + registryName + "]"); + } + Class registryClass = ClassUtils.forName(TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME, + JtaTransactionManager.class.getClassLoader()); + return getJndiTemplate().lookup(registryName, registryClass); + } + catch (ClassNotFoundException ex) { + throw new TransactionSystemException( + "JTA 1.1 [" + TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME + "] not available"); + } + catch (NamingException ex) { + throw new TransactionSystemException( + "JTA TransactionSynchronizationRegistry is not available at JNDI location [" + registryName + "]", ex); + } + } + + /** + * Allows subclasses to retrieve the JTA UserTransaction in a vendor-specific manner. + * Only called if no "userTransaction" or "userTransactionName" specified. + *

The default implementation simply returns null. + * @return the JTA UserTransaction handle to use, or null if none found + * @throws TransactionSystemException in case of errors + * @see #setUserTransaction + * @see #setUserTransactionName + */ + protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { + return null; + } + + /** + * Allows subclasses to retrieve the JTA TransactionManager in a vendor-specific manner. + * Only called if no "transactionManager" or "transactionManagerName" specified. + *

The default implementation simply returns null. + * @return the JTA TransactionManager handle to use, or null if none found + * @throws TransactionSystemException in case of errors + * @see #setTransactionManager + * @see #setTransactionManagerName + */ + protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { + return null; + } + + /** + * Allows subclasses to retrieve the JTA 1.1 TransactionSynchronizationRegistry + * in a vendor-specific manner. + *

The default implementation simply returns null. + * @return the JTA TransactionSynchronizationRegistry handle to use, + * or null if none found + * @throws TransactionSystemException in case of errors + */ + protected Object retrieveTransactionSynchronizationRegistry() throws TransactionSystemException { + return null; + } + + /** + * Find the JTA UserTransaction through a default JNDI lookup: + * "java:comp/UserTransaction". + * @return the JTA UserTransaction reference, or null if not found + * @see #DEFAULT_USER_TRANSACTION_NAME + */ + protected UserTransaction findUserTransaction() { + String jndiName = DEFAULT_USER_TRANSACTION_NAME; + try { + UserTransaction ut = (UserTransaction) getJndiTemplate().lookup(jndiName, UserTransaction.class); + if (logger.isDebugEnabled()) { + logger.debug("JTA UserTransaction found at default JNDI location [" + jndiName + "]"); + } + this.userTransactionObtainedFromJndi = true; + return ut; + } + catch (NamingException ex) { + if (logger.isDebugEnabled()) { + logger.debug("No JTA UserTransaction found at default JNDI location [" + jndiName + "]", ex); + } + return null; + } + } + + /** + * Find the JTA TransactionManager through autodetection: checking whether the + * UserTransaction object implements the TransactionManager, and checking the + * fallback JNDI locations. + * @param ut the JTA UserTransaction object + * @return the JTA TransactionManager reference, or null if not found + * @see #FALLBACK_TRANSACTION_MANAGER_NAMES + */ + protected TransactionManager findTransactionManager(UserTransaction ut) { + if (ut instanceof TransactionManager) { + if (logger.isDebugEnabled()) { + logger.debug("JTA UserTransaction object [" + ut + "] implements TransactionManager"); + } + return (TransactionManager) ut; + } + + // Check fallback JNDI locations. + for (int i = 0; i < FALLBACK_TRANSACTION_MANAGER_NAMES.length; i++) { + String jndiName = FALLBACK_TRANSACTION_MANAGER_NAMES[i]; + try { + TransactionManager tm = (TransactionManager) getJndiTemplate().lookup(jndiName, TransactionManager.class); + if (logger.isDebugEnabled()) { + logger.debug("JTA TransactionManager found at fallback JNDI location [" + jndiName + "]"); + } + return tm; + } + catch (NamingException ex) { + if (logger.isDebugEnabled()) { + logger.debug("No JTA TransactionManager found at fallback JNDI location [" + jndiName + "]", ex); + } + } + } + + // OK, so no JTA TransactionManager is available... + return null; + } + + /** + * Find the JTA 1.1 TransactionSynchronizationRegistry through autodetection: + * checking whether the UserTransaction object or TransactionManager object + * implements it, and checking Java EE 5's standard JNDI location. + *

The default implementation simply returns null. + * @param ut the JTA UserTransaction object + * @param tm the JTA TransactionManager object + * @return the JTA TransactionSynchronizationRegistry handle to use, + * or null if none found + * @throws TransactionSystemException in case of errors + */ + protected Object findTransactionSynchronizationRegistry(UserTransaction ut, TransactionManager tm) + throws TransactionSystemException { + + try { + Class registryClass = ClassUtils.forName(TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME, + JtaTransactionManager.class.getClassLoader()); + + // If we came here, we might be on Java EE 5, since the JTA 1.1 API is present. + if (this.userTransactionObtainedFromJndi) { + // UserTransaction has already been obtained from JNDI, so the + // TransactionSynchronizationRegistry probably sits there as well. + String jndiName = DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME; + try { + Object tsr = getJndiTemplate().lookup(jndiName, registryClass); + if (logger.isDebugEnabled()) { + logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]"); + } + return tsr; + } + catch (NamingException ex) { + if (logger.isDebugEnabled()) { + logger.debug( + "No JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]", ex); + } + } + } + + // Check whether the UserTransaction or TransactionManager implements it... + if (registryClass.isInstance(ut)) { + return ut; + } + if (registryClass.isInstance(tm)) { + return tm; + } + + // OK, so no JTA 1.1 TransactionSynchronizationRegistry is available, + // despite the API being present... + return null; + } + catch (ClassNotFoundException ex) { + logger.debug("JTA 1.1 [" + TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME + "] not available"); + return null; + } + } + + + /** + * This implementation returns a JtaTransactionObject instance for the + * JTA UserTransaction. + *

The UserTransaction object will either be looked up freshly for the + * current transaction, or the cached one looked up at startup will be used. + * The latter is the default: Most application servers use a shared singleton + * UserTransaction that can be cached. Turn off the "cacheUserTransaction" + * flag to enforce a fresh lookup for every transaction. + * @see #setCacheUserTransaction + */ + protected Object doGetTransaction() { + UserTransaction ut = getUserTransaction(); + if (ut == null) { + throw new CannotCreateTransactionException("No JTA UserTransaction available - " + + "programmatic PlatformTransactionManager.getTransaction usage not supported"); + } + if (!this.cacheUserTransaction) { + ut = lookupUserTransaction( + this.userTransactionName != null ? this.userTransactionName : DEFAULT_USER_TRANSACTION_NAME); + } + return doGetJtaTransaction(ut); + } + + /** + * Get a JTA transaction object for the given current UserTransaction. + *

Subclasses can override this to provide a JtaTransactionObject + * subclass, for example holding some additional JTA handle needed. + * @param ut the UserTransaction handle to use for the current transaction + * @return the JtaTransactionObject holding the UserTransaction + */ + protected JtaTransactionObject doGetJtaTransaction(UserTransaction ut) { + return new JtaTransactionObject(ut); + } + + protected boolean isExistingTransaction(Object transaction) { + JtaTransactionObject txObject = (JtaTransactionObject) transaction; + try { + return (txObject.getUserTransaction().getStatus() != Status.STATUS_NO_TRANSACTION); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on getStatus", ex); + } + } + + /** + * This implementation returns false to cause a further invocation + * of doBegin despite an already existing transaction. + *

JTA implementations might support nested transactions via further + * UserTransaction.begin() invocations, but never support savepoints. + * @see #doBegin + * @see javax.transaction.UserTransaction#begin() + */ + protected boolean useSavepointForNestedTransaction() { + return false; + } + + + protected void doBegin(Object transaction, TransactionDefinition definition) { + JtaTransactionObject txObject = (JtaTransactionObject) transaction; + try { + doJtaBegin(txObject, definition); + } + catch (NotSupportedException ex) { + // assume nested transaction not supported + throw new NestedTransactionNotSupportedException( + "JTA implementation does not support nested transactions", ex); + } + catch (UnsupportedOperationException ex) { + // assume nested transaction not supported + throw new NestedTransactionNotSupportedException( + "JTA implementation does not support nested transactions", ex); + } + catch (SystemException ex) { + throw new CannotCreateTransactionException("JTA failure on begin", ex); + } + } + + /** + * Perform a JTA begin on the JTA UserTransaction or TransactionManager. + *

This implementation only supports standard JTA functionality: + * that is, no per-transaction isolation levels and no transaction names. + * Can be overridden in subclasses, for specific JTA implementations. + *

Calls applyIsolationLevel and applyTimeout + * before invoking the UserTransaction's begin method. + * @param txObject the JtaTransactionObject containing the UserTransaction + * @param definition TransactionDefinition instance, describing propagation + * behavior, isolation level, read-only flag, timeout, and transaction name + * @throws NotSupportedException if thrown by JTA methods + * @throws SystemException if thrown by JTA methods + * @see #getUserTransaction + * @see #getTransactionManager + * @see #applyIsolationLevel + * @see #applyTimeout + * @see JtaTransactionObject#getUserTransaction() + * @see javax.transaction.UserTransaction#setTransactionTimeout + * @see javax.transaction.UserTransaction#begin + */ + protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) + throws NotSupportedException, SystemException { + + applyIsolationLevel(txObject, definition.getIsolationLevel()); + int timeout = determineTimeout(definition); + applyTimeout(txObject, timeout); + txObject.getUserTransaction().begin(); + } + + /** + * Apply the given transaction isolation level. The default implementation + * will throw an exception for any level other than ISOLATION_DEFAULT. + *

To be overridden in subclasses for specific JTA implementations, + * as alternative to overriding the full {@link #doJtaBegin} method. + * @param txObject the JtaTransactionObject containing the UserTransaction + * @param isolationLevel isolation level taken from transaction definition + * @throws InvalidIsolationLevelException if the given isolation level + * cannot be applied + * @throws SystemException if thrown by the JTA implementation + * @see #doJtaBegin + * @see JtaTransactionObject#getUserTransaction() + * @see #getTransactionManager() + */ + protected void applyIsolationLevel(JtaTransactionObject txObject, int isolationLevel) + throws InvalidIsolationLevelException, SystemException { + + if (!this.allowCustomIsolationLevels && isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { + throw new InvalidIsolationLevelException( + "JtaTransactionManager does not support custom isolation levels by default - " + + "switch 'allowCustomIsolationLevels' to 'true'"); + } + } + + /** + * Apply the given transaction timeout. The default implementation will call + * UserTransaction.setTransactionTimeout for a non-default timeout value. + * @param txObject the JtaTransactionObject containing the UserTransaction + * @param timeout timeout value taken from transaction definition + * @throws SystemException if thrown by the JTA implementation + * @see #doJtaBegin + * @see JtaTransactionObject#getUserTransaction() + * @see javax.transaction.UserTransaction#setTransactionTimeout(int) + */ + protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException { + if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getUserTransaction().setTransactionTimeout(timeout); + } + } + + + protected Object doSuspend(Object transaction) { + JtaTransactionObject txObject = (JtaTransactionObject) transaction; + try { + return doJtaSuspend(txObject); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on suspend", ex); + } + } + + /** + * Perform a JTA suspend on the JTA TransactionManager. + *

Can be overridden in subclasses, for specific JTA implementations. + * @param txObject the JtaTransactionObject containing the UserTransaction + * @return the suspended JTA Transaction object + * @throws SystemException if thrown by JTA methods + * @see #getTransactionManager() + * @see javax.transaction.TransactionManager#suspend() + */ + protected Object doJtaSuspend(JtaTransactionObject txObject) throws SystemException { + if (getTransactionManager() == null) { + throw new TransactionSuspensionNotSupportedException( + "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + + "specify the 'transactionManager' or 'transactionManagerName' property"); + } + return getTransactionManager().suspend(); + } + + protected void doResume(Object transaction, Object suspendedResources) { + JtaTransactionObject txObject = (JtaTransactionObject) transaction; + try { + doJtaResume(txObject, suspendedResources); + } + catch (InvalidTransactionException ex) { + throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex); + } + catch (IllegalStateException ex) { + throw new TransactionSystemException("Unexpected internal transaction state", ex); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on resume", ex); + } + } + + /** + * Perform a JTA resume on the JTA TransactionManager. + *

Can be overridden in subclasses, for specific JTA implementations. + * @param txObject the JtaTransactionObject containing the UserTransaction + * @param suspendedTransaction the suspended JTA Transaction object + * @throws InvalidTransactionException if thrown by JTA methods + * @throws SystemException if thrown by JTA methods + * @see #getTransactionManager() + * @see javax.transaction.TransactionManager#resume(javax.transaction.Transaction) + */ + protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction) + throws InvalidTransactionException, SystemException { + + if (getTransactionManager() == null) { + throw new TransactionSuspensionNotSupportedException( + "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + + "specify the 'transactionManager' or 'transactionManagerName' property"); + } + getTransactionManager().resume((Transaction) suspendedTransaction); + } + + + /** + * This implementation returns "true": a JTA commit will properly handle + * transactions that have been marked rollback-only at a global level. + */ + protected boolean shouldCommitOnGlobalRollbackOnly() { + return true; + } + + protected void doCommit(DefaultTransactionStatus status) { + JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); + try { + int jtaStatus = txObject.getUserTransaction().getStatus(); + if (jtaStatus == Status.STATUS_NO_TRANSACTION) { + // Should never happen... would have thrown an exception before + // and as a consequence led to a rollback, not to a commit call. + // In any case, the transaction is already fully cleaned up. + throw new UnexpectedRollbackException("JTA transaction already completed - probably rolled back"); + } + if (jtaStatus == Status.STATUS_ROLLEDBACK) { + // Only really happens on JBoss 4.2 in case of an early timeout... + // Explicit rollback call necessary to clean up the transaction. + // IllegalStateException expected on JBoss; call still necessary. + try { + txObject.getUserTransaction().rollback(); + } + catch (IllegalStateException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); + } + } + throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)"); + } + txObject.getUserTransaction().commit(); + } + catch (RollbackException ex) { + throw new UnexpectedRollbackException( + "JTA transaction unexpectedly rolled back (maybe due to a timeout)", ex); + } + catch (HeuristicMixedException ex) { + throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex); + } + catch (HeuristicRollbackException ex) { + throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex); + } + catch (IllegalStateException ex) { + throw new TransactionSystemException("Unexpected internal transaction state", ex); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on commit", ex); + } + } + + protected void doRollback(DefaultTransactionStatus status) { + JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); + try { + int jtaStatus = txObject.getUserTransaction().getStatus(); + if (jtaStatus != Status.STATUS_NO_TRANSACTION) { + try { + txObject.getUserTransaction().rollback(); + } + catch (IllegalStateException ex) { + if (jtaStatus == Status.STATUS_ROLLEDBACK) { + // Only really happens on JBoss 4.2 in case of an early timeout... + if (logger.isDebugEnabled()) { + logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); + } + } + else { + throw new TransactionSystemException("Unexpected internal transaction state", ex); + } + } + } + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on rollback", ex); + } + } + + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting JTA transaction rollback-only"); + } + try { + int jtaStatus = txObject.getUserTransaction().getStatus(); + if (jtaStatus != Status.STATUS_NO_TRANSACTION && jtaStatus != Status.STATUS_ROLLEDBACK) { + txObject.getUserTransaction().setRollbackOnly(); + } + } + catch (IllegalStateException ex) { + throw new TransactionSystemException("Unexpected internal transaction state", ex); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on setRollbackOnly", ex); + } + } + + + protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations) { + JtaTransactionObject txObject = (JtaTransactionObject) transaction; + logger.debug("Registering after-completion synchronization with existing JTA transaction"); + try { + doRegisterAfterCompletionWithJtaTransaction(txObject, synchronizations); + } + catch (RollbackException ex) { + logger.debug("Participating in existing JTA transaction that has been marked for rollback: " + + "cannot register Spring after-completion callbacks with outer JTA transaction - " + + "immediately performing Spring after-completion callbacks with outcome status 'rollback'. " + + "Original exception: " + ex); + invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); + } + catch (IllegalStateException ex) { + logger.debug("Participating in existing JTA transaction, but unexpected internal transaction " + + "state encountered: cannot register Spring after-completion callbacks with outer JTA " + + "transaction - processing Spring after-completion callbacks with outcome status 'unknown'" + + "Original exception: " + ex); + invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on registerSynchronization", ex); + } + } + + /** + * Register a JTA synchronization on the JTA TransactionManager, for calling + * afterCompletion on the given Spring TransactionSynchronizations. + *

The default implementation registers the synchronizations on the + * JTA 1.1 TransactionSynchronizationRegistry, if available, or on the + * JTA TransactionManager's current Transaction - again, if available. + * If none of the two is available, a warning will be logged. + *

Can be overridden in subclasses, for specific JTA implementations. + * @param txObject the current transaction object + * @param synchronizations List of TransactionSynchronization objects + * @throws RollbackException if thrown by JTA methods + * @throws SystemException if thrown by JTA methods + * @see #getTransactionManager() + * @see javax.transaction.Transaction#registerSynchronization + * @see javax.transaction.TransactionSynchronizationRegistry#registerInterposedSynchronization + */ + protected void doRegisterAfterCompletionWithJtaTransaction(JtaTransactionObject txObject, List synchronizations) + throws RollbackException, SystemException { + + int jtaStatus = txObject.getUserTransaction().getStatus(); + if (jtaStatus == Status.STATUS_NO_TRANSACTION) { + throw new RollbackException("JTA transaction already completed - probably rolled back"); + } + if (jtaStatus == Status.STATUS_ROLLEDBACK) { + throw new RollbackException("JTA transaction already rolled back (probably due to a timeout)"); + } + + if (this.transactionSynchronizationRegistry != null) { + // JTA 1.1 TransactionSynchronizationRegistry available - use it. + new InterposedSynchronizationDelegate().registerInterposedSynchronization( + new JtaAfterCompletionSynchronization(synchronizations)); + } + + else if (getTransactionManager() != null) { + // At least the JTA TransactionManager available - use that one. + Transaction transaction = getTransactionManager().getTransaction(); + if (transaction == null) { + throw new IllegalStateException("No JTA Transaction available"); + } + transaction.registerSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); + } + + else { + // No JTA TransactionManager available - log a warning. + logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager available: " + + "cannot register Spring after-completion callbacks with outer JTA transaction - " + + "processing Spring after-completion callbacks with outcome status 'unknown'"); + invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + } + + + //--------------------------------------------------------------------- + // Implementation of TransactionFactory interface + //--------------------------------------------------------------------- + + public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { + TransactionManager tm = getTransactionManager(); + Assert.state(tm != null, "No JTA TransactionManager available"); + if (timeout >= 0) { + tm.setTransactionTimeout(timeout); + } + tm.begin(); + return tm.getTransaction(); + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Create template for client-side JNDI lookup. + this.jndiTemplate = new JndiTemplate(); + + // Perform a fresh lookup for JTA handles. + initUserTransactionAndTransactionManager(); + initTransactionSynchronizationRegistry(); + } + + + /** + * Inner class to avoid a direct dependency on the JTA 1.1 API + * (javax.transaction.TransactionSynchronizationRegistry interface). + */ + private class InterposedSynchronizationDelegate { + + public void registerInterposedSynchronization(Synchronization synch) { + ((TransactionSynchronizationRegistry) transactionSynchronizationRegistry).registerInterposedSynchronization(synch); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java new file mode 100644 index 00000000000..ea11e006edc --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java @@ -0,0 +1,75 @@ +/* + * 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.jta; + +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; + +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.SmartTransactionObject; + +/** + * JTA transaction object, representing a {@link javax.transaction.UserTransaction}. + * Used as transaction object by Spring's {@link JtaTransactionManager}. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Juergen Hoeller + * @since 1.1 + * @see JtaTransactionManager + * @see javax.transaction.UserTransaction + */ +public class JtaTransactionObject implements SmartTransactionObject { + + private final UserTransaction userTransaction; + + + /** + * Create a new JtaTransactionObject for the given JTA UserTransaction. + * @param userTransaction the JTA UserTransaction for the current transaction + * (either a shared object or retrieved through a fresh per-transaction lookuip) + */ + public JtaTransactionObject(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + /** + * Return the JTA UserTransaction object for the current transaction. + */ + public final UserTransaction getUserTransaction() { + return this.userTransaction; + } + + + /** + * This implementation checks the UserTransaction's rollback-only flag. + */ + public boolean isRollbackOnly() { + if (this.userTransaction == null) { + return false; + } + try { + int jtaStatus = this.userTransaction.getStatus(); + return (jtaStatus == Status.STATUS_MARKED_ROLLBACK || jtaStatus == Status.STATUS_ROLLEDBACK); + } + catch (SystemException ex) { + throw new TransactionSystemException("JTA failure on getStatus", ex); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/OC4JJtaTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/OC4JJtaTransactionManager.java new file mode 100644 index 00000000000..34d5f35b54d --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/OC4JJtaTransactionManager.java @@ -0,0 +1,250 @@ +/* + * 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.jta; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.UserTransaction; + +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.util.ClassUtils; + +/** + * Special {@link JtaTransactionManager} variant for Oracle OC4J (10.1.3 and higher). + * Supports the full power of Spring's transaction definitions on OC4J's + * transaction coordinator, beyond standard JTA: transaction names + * and per-transaction isolation levels. + * + *

Uses OC4J's special begin(name) method to start a JTA transaction, + * in orderto make Spring-driven transactions visible in OC4J's transaction + * monitor. In case of Spring's declarative transactions, the exposed name will + * (by default) be the fully-qualified class name + "." + method name. + * + *

Supports a per-transaction isolation level through OC4J's corresponding + * OC4JTransaction.setTransactionIsolation(int) method. This will + * apply the specified isolation level (e.g. ISOLATION_SERIALIZABLE) to all + * JDBC Connections that participate in the given transaction. + * + *

Automatically detects the available OC4J server version and adapts accordingly. + * Supports the "com.evermind.server" package in OC4J 10.1.3.2 as well as the + * "oracle.j2ee.transaction" package in later OC4J versions. + * + *

By default, the JTA UserTransaction and TransactionManager handles are + * fetched directly from OC4J's TransactionUtility in 10.1.3.2+. + * This can be overridden by specifying "userTransaction"/"userTransactionName" + * and "transactionManager"/"transactionManagerName", passing in existing handles + * or specifying corresponding JNDI locations to look up. + * + *

Thanks to Oracle for donating the original version of this extended OC4J + * integration code to the Spring project! + * + * @author Paul Parkinson + * @author Juergen Hoeller + * @since 2.0.3 + * @see org.springframework.transaction.TransactionDefinition#getName + * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel + * @see oracle.j2ee.transaction.OC4JTransactionManager#begin(String) + * @see oracle.j2ee.transaction.OC4JTransaction#setTransactionIsolation + * @see oracle.j2ee.transaction.TransactionUtility + */ +public class OC4JJtaTransactionManager extends JtaTransactionManager { + + private static final String TRANSACTION_UTILITY_CLASS_NAME = + "oracle.j2ee.transaction.TransactionUtility"; + + private static final String TRANSACTION_MANAGER_CLASS_NAME = + "oracle.j2ee.transaction.OC4JTransactionManager"; + + private static final String TRANSACTION_CLASS_NAME = + "oracle.j2ee.transaction.OC4JTransaction"; + + private static final String FALLBACK_TRANSACTION_MANAGER_CLASS_NAME = + "com.evermind.server.ApplicationServerTransactionManager"; + + private static final String FALLBACK_TRANSACTION_CLASS_NAME = + "com.evermind.server.ApplicationServerTransaction"; + + + private Method beginWithNameMethod; + + private Method setTransactionIsolationMethod; + + + public void afterPropertiesSet() throws TransactionSystemException { + super.afterPropertiesSet(); + loadOC4JTransactionClasses(); + } + + protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { + try { + Class transactionUtilityClass = getClass().getClassLoader().loadClass(TRANSACTION_UTILITY_CLASS_NAME); + Method getInstanceMethod = transactionUtilityClass.getMethod("getInstance", new Class[0]); + Object transactionUtility = getInstanceMethod.invoke(null, new Object[0]); + logger.debug("Retrieving JTA UserTransaction from OC4J TransactionUtility"); + Method getUserTransactionMethod = + transactionUtility.getClass().getMethod("getOC4JUserTransaction", new Class[0]); + return (UserTransaction) getUserTransactionMethod.invoke(transactionUtility, new Object[0]); + } + catch (ClassNotFoundException ex) { + logger.debug("Could not find OC4J 10.1.3.2 TransactionUtility: " + ex); + // Return null to make the superclass perform its standard J2EE lookup, + // which will work on earlier OC4J versions. + return null; + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "OC4J's TransactionUtility.getOC4JUserTransaction() method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke OC4J's TransactionUtility.getOC4JUserTransaction() method", ex); + } + } + + private void loadOC4JTransactionClasses() throws TransactionSystemException { + // Find available OC4J API (in "oracle.j2ee.transaction" or "com.evermind.server") + Class transactionManagerClass = null; + Class transactionClass = null; + try { + transactionManagerClass = getClass().getClassLoader().loadClass(TRANSACTION_MANAGER_CLASS_NAME); + transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME); + } + catch (ClassNotFoundException ex) { + try { + transactionManagerClass = getClass().getClassLoader().loadClass(FALLBACK_TRANSACTION_MANAGER_CLASS_NAME); + transactionClass = getClass().getClassLoader().loadClass(FALLBACK_TRANSACTION_CLASS_NAME); + } + catch (ClassNotFoundException ex2) { + throw new TransactionSystemException( + "Could not initialize OC4JJtaTransactionManager because OC4J API classes are not available", ex); + } + } + + // Cache reflective Method references for later use. + if (transactionManagerClass.isInstance(getUserTransaction())) { + this.beginWithNameMethod = ClassUtils.getMethodIfAvailable( + transactionManagerClass, "begin", new Class[] {String.class}); + this.setTransactionIsolationMethod = ClassUtils.getMethodIfAvailable( + transactionClass, "setTransactionIsolation", new Class[] {int.class}); + logger.info("Support for OC4J transaction names and isolation levels available"); + } + else { + logger.info("Support for OC4J transaction names and isolation levels not available"); + } + } + + + protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) + throws NotSupportedException, SystemException { + + int timeout = determineTimeout(definition); + applyTimeout(txObject, timeout); + + // Apply transaction name, if any, through the extended OC4J transaction begin method. + if (this.beginWithNameMethod != null && definition.getName() != null) { + /* + oracle.j2ee.transaction.OC4JTransactionManager otm = (oracle.j2ee.transaction.OC4JTransactionManager) ut; + otm.begin(definition.getName()); + */ + try { + this.beginWithNameMethod.invoke(txObject.getUserTransaction(), new Object[] {definition.getName()}); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "OC4J's UserTransaction.begin(String) method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke OC4J's UserTransaction.begin(String) method", ex); + } + } + else { + // No OC4J UserTransaction available or no transaction name specified + // -> standard JTA begin call. + txObject.getUserTransaction().begin(); + } + + // Specify isolation level, if any, through the corresponding OC4J transaction method. + if (this.setTransactionIsolationMethod != null) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + try { + Transaction tx = getTransactionManager().getTransaction(); + /* + oracle.j2ee.transaction.OC4JTransaction otx = (oracle.j2ee.transaction.OC4JTransaction) tx; + otx.setTransactionIsolation(definition.getIsolationLevel()); + */ + Integer isolationLevel = new Integer(definition.getIsolationLevel()); + this.setTransactionIsolationMethod.invoke(tx, new Object[] {isolationLevel}); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "OC4J's Transaction.setTransactionIsolation(int) method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke OC4J's Transaction.setTransactionIsolation(int) method", ex); + } + } + } + else { + applyIsolationLevel(txObject, definition.getIsolationLevel()); + } + } + + + public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { + if (this.beginWithNameMethod != null && name != null) { + UserTransaction ut = getUserTransaction(); + if (timeout >= 0) { + ut.setTransactionTimeout(timeout); + } + try { + this.beginWithNameMethod.invoke(ut, new Object[] {name}); + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof NotSupportedException) { + throw (NotSupportedException) ex.getTargetException(); + } + else if (ex.getTargetException() instanceof SystemException) { + throw (SystemException) ex.getTargetException(); + } + else if (ex.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) ex.getTargetException(); + } + else { + throw new SystemException( + "OC4J's begin(String) method failed with an unexpected error: " + ex.getTargetException()); + } + } + catch (Exception ex) { + throw new SystemException("Could not invoke OC4J's UserTransaction.begin(String) method: " + ex); + } + return getTransactionManager().getTransaction(); + } + + else { + // No name specified - standard JTA is sufficient. + return super.createTransaction(name, timeout); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SimpleTransactionFactory.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SimpleTransactionFactory.java new file mode 100644 index 00000000000..51bfe5e9114 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SimpleTransactionFactory.java @@ -0,0 +1,61 @@ +/* + * 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.jta; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import org.springframework.util.Assert; + +/** + * Default implementation of the {@link TransactionFactory} strategy interface, + * simply wrapping a standard JTA {@link javax.transaction.TransactionManager}. + * + *

Does not support transaction names; simply ignores any specified name. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.transaction.TransactionManager#setTransactionTimeout(int) + * @see javax.transaction.TransactionManager#begin() + * @see javax.transaction.TransactionManager#getTransaction() + */ +public class SimpleTransactionFactory implements TransactionFactory { + + private final TransactionManager transactionManager; + + + /** + * Create a new SimpleTransactionFactory for the given TransactionManager + * @param transactionManager the JTA TransactionManager to wrap + */ + public SimpleTransactionFactory(TransactionManager transactionManager) { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + } + + + public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { + if (timeout >= 0) { + this.transactionManager.setTransactionTimeout(timeout); + } + this.transactionManager.begin(); + return this.transactionManager.getTransaction(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java new file mode 100644 index 00000000000..44e1d5ad41c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/SpringJtaSynchronizationAdapter.java @@ -0,0 +1,190 @@ +/* + * 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.jta; + +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + * Adapter that implements the JTA {@link javax.transaction.Synchronization} + * interface delegating to an underlying Spring + * {@link org.springframework.transaction.support.TransactionSynchronization}. + * + *

Useful for synchronizing Spring resource management code with plain + * JTA / EJB CMT transactions, despite the original code being built for + * Spring transaction synchronization. + * + * @author Juergen Hoeller + * @since 2.0 + * @see javax.transaction.Transaction#registerSynchronization + * @see org.springframework.transaction.support.TransactionSynchronization + */ +public class SpringJtaSynchronizationAdapter implements Synchronization { + + protected static final Log logger = LogFactory.getLog(SpringJtaSynchronizationAdapter.class); + + private final TransactionSynchronization springSynchronization; + + private UserTransaction jtaTransaction; + + private boolean beforeCompletionCalled = false; + + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + */ + public SpringJtaSynchronizationAdapter(TransactionSynchronization springSynchronization) { + Assert.notNull(springSynchronization, "TransactionSynchronization must not be null"); + this.springSynchronization = springSynchronization; + } + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + *

Note that this adapter will never perform a rollback-only call on WebLogic, + * since WebLogic Server is known to automatically mark the transaction as + * rollback-only in case of a beforeCompletion exception. Hence, + * on WLS, this constructor is equivalent to the single-arg constructor. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + * @param jtaUserTransaction the JTA UserTransaction to use for rollback-only + * setting in case of an exception thrown in beforeCompletion + * (can be omitted if the JTA provider itself marks the transaction rollback-only + * in such a scenario, which is required by the JTA specification as of JTA 1.1). + */ + public SpringJtaSynchronizationAdapter( + TransactionSynchronization springSynchronization, UserTransaction jtaUserTransaction) { + + this(springSynchronization); + if (jtaUserTransaction != null && !jtaUserTransaction.getClass().getName().startsWith("weblogic.")) { + this.jtaTransaction = jtaUserTransaction; + } + } + + /** + * Create a new SpringJtaSynchronizationAdapter for the given Spring + * TransactionSynchronization and JTA TransactionManager. + *

Note that this adapter will never perform a rollback-only call on WebLogic, + * since WebLogic Server is known to automatically mark the transaction as + * rollback-only in case of a beforeCompletion exception. Hence, + * on WLS, this constructor is equivalent to the single-arg constructor. + * @param springSynchronization the Spring TransactionSynchronization to delegate to + * @param jtaTransactionManager the JTA TransactionManager to use for rollback-only + * setting in case of an exception thrown in beforeCompletion + * (can be omitted if the JTA provider itself marks the transaction rollback-only + * in such a scenario, which is required by the JTA specification as of JTA 1.1) + */ + public SpringJtaSynchronizationAdapter( + TransactionSynchronization springSynchronization, TransactionManager jtaTransactionManager) { + + this(springSynchronization); + if (jtaTransactionManager != null && !jtaTransactionManager.getClass().getName().startsWith("weblogic.")) { + this.jtaTransaction = new UserTransactionAdapter(jtaTransactionManager); + } + } + + + /** + * JTA beforeCompletion callback: just invoked before commit. + *

In case of an exception, the JTA transaction will be marked as rollback-only. + * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit + */ + public void beforeCompletion() { + try { + boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); + this.springSynchronization.beforeCommit(readOnly); + } + catch (RuntimeException ex) { + setRollbackOnlyIfPossible(); + throw ex; + } + catch (Error err) { + setRollbackOnlyIfPossible(); + throw err; + } + finally { + // Process Spring's beforeCompletion early, in order to avoid issues + // with strict JTA implementations that issue warnings when doing JDBC + // operations after transaction completion (e.g. Connection.getWarnings). + this.beforeCompletionCalled = true; + this.springSynchronization.beforeCompletion(); + } + } + + /** + * Set the underlying JTA transaction to rollback-only. + */ + private void setRollbackOnlyIfPossible() { + if (this.jtaTransaction != null) { + try { + this.jtaTransaction.setRollbackOnly(); + } + catch (UnsupportedOperationException ex) { + // Probably Hibernate's WebSphereExtendedJTATransactionLookup pseudo JTA stuff... + logger.debug("JTA transaction handle does not support setRollbackOnly method - " + + "relying on JTA provider to mark the transaction as rollback-only based on " + + "the exception thrown from beforeCompletion", ex); + } + catch (Throwable ex) { + logger.error("Could not set JTA transaction rollback-only", ex); + } + } + else { + logger.debug("No JTA transaction handle available and/or running on WebLogic - " + + "relying on JTA provider to mark the transaction as rollback-only based on " + + "the exception thrown from beforeCompletion"); + } + } + + /** + * JTA afterCompletion callback: invoked after commit/rollback. + *

Needs to invoke the Spring synchronization's beforeCompletion + * at this late stage in case of a rollback, since there is no corresponding + * callback with JTA. + * @see org.springframework.transaction.support.TransactionSynchronization#beforeCompletion + * @see org.springframework.transaction.support.TransactionSynchronization#afterCompletion + */ + public void afterCompletion(int status) { + if (!this.beforeCompletionCalled) { + // beforeCompletion not called before (probably because of JTA rollback). + // Perform the cleanup here. + this.springSynchronization.beforeCompletion(); + } + // Call afterCompletion with the appropriate status indication. + switch (status) { + case Status.STATUS_COMMITTED: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_COMMITTED); + break; + case Status.STATUS_ROLLEDBACK: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK); + break; + default: + this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_UNKNOWN); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/TransactionFactory.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/TransactionFactory.java new file mode 100644 index 00000000000..eed1b5f8632 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/TransactionFactory.java @@ -0,0 +1,52 @@ +/* + * 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.jta; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; + +/** + * Strategy interface for creating JTA {@link javax.transaction.Transaction} + * objects based on specified transactional characteristics. + * + *

The default implementation, {@link SimpleTransactionFactory}, simply + * wraps a standard JTA {@link javax.transaction.TransactionManager}. + * This strategy interface allows for more sophisticated implementations + * that adapt to vendor-specific JTA extensions. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.transaction.TransactionManager#getTransaction() + * @see SimpleTransactionFactory + * @see JtaTransactionManager + */ +public interface TransactionFactory { + + /** + * Create an active Transaction object based on the given name and timeout. + * @param name the transaction name (may be null) + * @param timeout the transaction timeout (may be -1 for the default timeout) + * @return the active Transaction object (never null) + * @throws NotSupportedException if the transaction manager does not support + * a transaction of the specified type + * @throws SystemException if the transaction managed failed to create the + * transaction + */ + Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/UserTransactionAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/UserTransactionAdapter.java new file mode 100644 index 00000000000..8c18b89395e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/UserTransactionAdapter.java @@ -0,0 +1,95 @@ +/* + * 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.jta; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.springframework.util.Assert; + +/** + * Adapter for a JTA UserTransaction handle, taking a JTA + * {@link javax.transaction.TransactionManager} reference and creating + * a JTA {@link javax.transaction.UserTransaction} handle for it. + * + *

The JTA UserTransaction interface is an exact subset of the JTA + * TransactionManager interface. Unfortunately, it does not serve as + * super-interface of TransactionManager, though, which requires an + * adapter such as this class to be used when intending to talk to + * a TransactionManager handle through the UserTransaction interface. + * + *

Used internally by Spring's {@link JtaTransactionManager} for certain + * scenarios. Not intended for direct use in application code. + * + * @author Juergen Hoeller + * @since 1.1.5 + */ +public class UserTransactionAdapter implements UserTransaction { + + private final TransactionManager transactionManager; + + + /** + * Create a new UserTransactionAdapter for the given TransactionManager. + * @param transactionManager the JTA TransactionManager to wrap + */ + public UserTransactionAdapter(TransactionManager transactionManager) { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionManager = transactionManager; + } + + /** + * Return the JTA TransactionManager that this adapter delegates to. + */ + public final TransactionManager getTransactionManager() { + return this.transactionManager; + } + + + public void begin() throws NotSupportedException, SystemException { + this.transactionManager.begin(); + } + + public void commit() + throws RollbackException, HeuristicMixedException, HeuristicRollbackException, + SecurityException, IllegalStateException, SystemException { + + this.transactionManager.commit(); + } + + public int getStatus() throws SystemException { + return this.transactionManager.getStatus(); + } + + public void rollback() throws IllegalStateException, SecurityException, SystemException { + this.transactionManager.rollback(); + } + + public void setRollbackOnly() throws IllegalStateException, SystemException { + this.transactionManager.setRollbackOnly(); + } + + public void setTransactionTimeout(int timeout) throws SystemException { + this.transactionManager.setTransactionTimeout(timeout); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java new file mode 100644 index 00000000000..11f3d44bdc7 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java @@ -0,0 +1,372 @@ +/* + * 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.jta; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.transaction.InvalidTransactionException; +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionSystemException; + +/** + * Special {@link JtaTransactionManager} variant for BEA WebLogic (7.0, 8.1 and higher). + * Supports the full power of Spring's transaction definitions on WebLogic's + * transaction coordinator, beyond standard JTA: transaction names, + * per-transaction isolation levels, and proper resuming of transactions in all cases. + * + *

Uses WebLogic's special begin(name) method to start a JTA transaction, + * in order to make Spring-driven transactions visible in WebLogic's transaction + * monitor. In case of Spring's declarative transactions, the exposed name will + * (by default) be the fully-qualified class name + "." + method name. + * + *

Supports a per-transaction isolation level through WebLogic's corresponding + * JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation + * level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the + * given transaction. + * + *

Invokes WebLogic's special forceResume method if standard JTA resume + * failed, to also resume if the target transaction was marked rollback-only. + * If you're not relying on this feature of transaction suspension in the first + * place, Spring's standard JtaTransactionManager will behave properly too. + * + *

Automatically detects WebLogic Server 7.0 or 8.1+ and adapts accordingly. + * Usage on a WebLogic client is also supported, although with restricted + * functionality: transaction names cannot be applied there. + * + *

By default, the JTA UserTransaction and TransactionManager handles are + * fetched directly from WebLogic's TransactionHelper (on 8.1+) + * or TxHelper (on 7.0). This can be overridden by specifying + * "userTransaction"/"userTransactionName" and "transactionManager"/"transactionManagerName", + * passing in existing handles or specifying corresponding JNDI locations to look up. + * + * @author Juergen Hoeller + * @since 1.1 + * @see org.springframework.transaction.TransactionDefinition#getName + * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel + * @see weblogic.transaction.UserTransaction#begin(String) + * @see weblogic.transaction.Transaction#setProperty + * @see weblogic.transaction.TransactionManager#forceResume + * @see weblogic.transaction.TransactionHelper + * @see weblogic.transaction.TxHelper + */ +public class WebLogicJtaTransactionManager extends JtaTransactionManager { + + private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction"; + + private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager"; + + private static final String TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.TransactionManager"; + + private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction"; + + private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper"; + + private static final String TX_HELPER_CLASS_NAME = "weblogic.transaction.TxHelper"; + + private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL"; + + + private boolean weblogicUserTransactionAvailable; + + private Method beginWithNameMethod; + + private Method beginWithNameAndTimeoutMethod; + + private boolean weblogicTransactionManagerAvailable; + + private Method forceResumeMethod; + + private Method setPropertyMethod; + + private Class transactionHelperClass; + + private Object transactionHelper; + + + public void afterPropertiesSet() throws TransactionSystemException { + super.afterPropertiesSet(); + loadWebLogicTransactionClasses(); + } + + protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { + loadWebLogicTransactionHelperClass(); + try { + logger.debug("Retrieving JTA UserTransaction from WebLogic TransactionHelper/TxHelper"); + Method getUserTransactionMethod = + this.transactionHelperClass.getMethod("getUserTransaction", new Class[0]); + return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper, new Object[0]); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebLogic's TransactionHelper/TxHelper.getUserTransaction() method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke WebLogic's TransactionHelper/TxHelper.getUserTransaction() method", ex); + } + } + + protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { + loadWebLogicTransactionHelperClass(); + try { + logger.debug("Retrieving JTA TransactionManager from WebLogic TransactionHelper/TxHelper"); + Method getTransactionManagerMethod = + this.transactionHelperClass.getMethod("getTransactionManager", new Class[0]); + return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper, new Object[0]); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebLogic's TransactionHelper/TxHelper.getTransactionManager() method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke WebLogic's TransactionHelper/TxHelper.getTransactionManager() method", ex); + } + } + + + private void loadWebLogicTransactionHelperClass() throws TransactionSystemException { + if (this.transactionHelperClass == null) { + try { + try { + this.transactionHelperClass = + getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME); + Method getTransactionHelperMethod = + this.transactionHelperClass.getMethod("getTransactionHelper", new Class[0]); + this.transactionHelper = getTransactionHelperMethod.invoke(null, new Object[0]); + logger.debug("WebLogic 8.1+ TransactionHelper found"); + } + catch (ClassNotFoundException ex) { + this.transactionHelperClass = + getClass().getClassLoader().loadClass(TX_HELPER_CLASS_NAME); + logger.debug("WebLogic 7.0 TxHelper found"); + } + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", + ex); + } + } + } + + private void loadWebLogicTransactionClasses() throws TransactionSystemException { + try { + Class userTransactionClass = + getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME); + this.weblogicUserTransactionAvailable = + userTransactionClass.isInstance(getUserTransaction()); + if (this.weblogicUserTransactionAvailable) { + this.beginWithNameMethod = + userTransactionClass.getMethod("begin", new Class[] {String.class}); + this.beginWithNameAndTimeoutMethod = + userTransactionClass.getMethod("begin", new Class[] {String.class, int.class}); + logger.info("Support for WebLogic transaction names available"); + } + else { + logger.info("Support for WebLogic transaction names not available"); + } + + Class transactionManagerClass = null; + try { + // Try WebLogic 8.1 ClientTransactionManager interface. + transactionManagerClass = + getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME); + logger.debug("WebLogic 8.1+ ClientTransactionManager found"); + } + catch (ClassNotFoundException ex) { + // Fall back to WebLogic TransactionManager interface. + transactionManagerClass = + getClass().getClassLoader().loadClass(TRANSACTION_MANAGER_CLASS_NAME); + logger.debug("WebLogic 7.0 TransactionManager found"); + } + + this.weblogicTransactionManagerAvailable = + transactionManagerClass.isInstance(getTransactionManager()); + if (this.weblogicTransactionManagerAvailable) { + Class transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME); + this.forceResumeMethod = + transactionManagerClass.getMethod("forceResume", new Class[] {Transaction.class}); + this.setPropertyMethod = + transactionClass.getMethod("setProperty", new Class[] {String.class, Serializable.class}); + logger.debug("Support for WebLogic forceResume available"); + } + else { + logger.warn("Support for WebLogic forceResume not available"); + } + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", + ex); + } + } + + + protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) + throws NotSupportedException, SystemException { + + int timeout = determineTimeout(definition); + + // Apply transaction name (if any) to WebLogic transaction. + if (this.weblogicUserTransactionAvailable && definition.getName() != null) { + try { + if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { + /* + weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; + wut.begin(definition.getName(), timeout); + */ + this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), + new Object[] {definition.getName(), new Integer(timeout)}); + } + else { + /* + weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; + wut.begin(definition.getName()); + */ + this.beginWithNameMethod.invoke(txObject.getUserTransaction(), + new Object[] {definition.getName()}); + } + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebLogic's UserTransaction.begin() method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke WebLogic's UserTransaction.begin() method", ex); + } + } + else { + // No WebLogic UserTransaction available or no transaction name specified + // -> standard JTA begin call. + applyTimeout(txObject, timeout); + txObject.getUserTransaction().begin(); + } + + // Specify isolation level, if any, through corresponding WebLogic transaction property. + if (this.weblogicTransactionManagerAvailable) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + try { + Transaction tx = getTransactionManager().getTransaction(); + Integer isolationLevel = new Integer(definition.getIsolationLevel()); + /* + weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx; + wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel); + */ + this.setPropertyMethod.invoke(tx, new Object[] {ISOLATION_LEVEL_KEY, isolationLevel}); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex); + } + } + } + else { + applyIsolationLevel(txObject, definition.getIsolationLevel()); + } + } + + protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction) + throws InvalidTransactionException, SystemException { + + try { + getTransactionManager().resume((Transaction) suspendedTransaction); + } + catch (InvalidTransactionException ex) { + if (!this.weblogicTransactionManagerAvailable) { + throw ex; + } + + if (logger.isDebugEnabled()) { + logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() + + " - trying WebLogic JTA forceResume"); + } + /* + weblogic.transaction.TransactionManager wtm = + (weblogic.transaction.TransactionManager) getTransactionManager(); + wtm.forceResume(suspendedTransaction); + */ + try { + this.forceResumeMethod.invoke(getTransactionManager(), new Object[] {suspendedTransaction}); + } + catch (InvocationTargetException ex2) { + throw new TransactionSystemException( + "WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException()); + } + catch (Exception ex2) { + throw new TransactionSystemException( + "Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2); + } + } + } + + + public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { + if (this.weblogicUserTransactionAvailable && name != null) { + try { + if (timeout >= 0) { + this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), new Object[] {name, new Integer(timeout)}); + } + else { + this.beginWithNameMethod.invoke(getUserTransaction(), new Object[] {name}); + } + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof NotSupportedException) { + throw (NotSupportedException) ex.getTargetException(); + } + else if (ex.getTargetException() instanceof SystemException) { + throw (SystemException) ex.getTargetException(); + } + else if (ex.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) ex.getTargetException(); + } + else { + throw new SystemException( + "WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException()); + } + } + catch (Exception ex) { + throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex); + } + return getTransactionManager().getTransaction(); + } + + else { + // No name specified - standard JTA is sufficient. + return super.createTransaction(name, timeout); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereTransactionManagerFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereTransactionManagerFactoryBean.java new file mode 100644 index 00000000000..8c8c8672892 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereTransactionManagerFactoryBean.java @@ -0,0 +1,117 @@ +/* + * 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.jta; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.transaction.TransactionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.transaction.TransactionSystemException; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that retrieves + * the JTA TransactionManager for IBM's WebSphere application servers + * (versions 5.1, 6.0 and 6.1). + * + *

Uses WebSphere's static accessor methods to obtain the internal JTA + * TransactionManager. This is known to work reliably on all tested WebSphere + * versions; however, access to the internal TransactionManager facility + * is not officially supported by IBM. + * + *

In combination with Spring's JtaTransactionManager, this FactoryBean + * can be used to enable transaction suspension (PROPAGATION_REQUIRES_NEW, + * PROPAGATION_NOT_SUPPORTED) on WebSphere: + * + *

+ * <bean id="wsJtaTm" class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"/>
+ *
+ * <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
+ *   <property name="transactionManager ref="wsJtaTm"/>
+ * </bean>
+ * + * Note that Spring's JtaTransactionManager will continue to use the JTA + * UserTransaction for standard transaction demarcation, as defined by + * standard J2EE. It will only use the provided WebSphere TransactionManager + * in case of actual transaction suspension needs. If you do not require + * transaction suspension in the first place, do not bother with this FactoryBean. + * + *

NOTE: On recent WebSphere 6.0.x and 6.1.x versions, this class has + * been superseded by the {@link WebSphereUowTransactionManager} class, which + * uses IBM's official UOWManager API facility for transaction suspension. + * The WebSphereUowTransactionManager class is a direct replacement for a + * standard JtaTransactionManager definition, without further configuration. + * + * @author Juergen Hoeller + * @since 21.01.2004 + * @see JtaTransactionManager#setTransactionManager + * @see com.ibm.ws.Transaction.TransactionManagerFactory#getTransactionManager + * @see WebSphereUowTransactionManager + */ +public class WebSphereTransactionManagerFactoryBean implements FactoryBean { + + private static final String FACTORY_CLASS_5_1 = "com.ibm.ws.Transaction.TransactionManagerFactory"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private final TransactionManager transactionManager; + + + /** + * This constructor retrieves the WebSphere TransactionManager factory class, + * so we can get access to the JTA TransactionManager. + */ + public WebSphereTransactionManagerFactoryBean() throws TransactionSystemException { + try { + // Using the thread context class loader for compatibility with the WSAD test server. + Class clazz = Thread.currentThread().getContextClassLoader().loadClass(FACTORY_CLASS_5_1); + Method method = clazz.getMethod("getTransactionManager", (Class[]) null); + this.transactionManager = (TransactionManager) method.invoke(null, (Object[]) null); + } + catch (ClassNotFoundException ex) { + throw new TransactionSystemException( + "Could not find WebSphere 5.1/6.0/6.1 TransactionManager factory class", ex); + } + catch (InvocationTargetException ex) { + throw new TransactionSystemException( + "WebSphere's TransactionManagerFactory.getTransactionManager method failed", ex.getTargetException()); + } + catch (Exception ex) { + throw new TransactionSystemException( + "Could not access WebSphere's TransactionManagerFactory.getTransactionManager method", ex); + } + } + + + public Object getObject() { + return this.transactionManager; + } + + public Class getObjectType() { + return this.transactionManager.getClass(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereUowTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereUowTransactionManager.java new file mode 100644 index 00000000000..42f08f7ebcf --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/WebSphereUowTransactionManager.java @@ -0,0 +1,330 @@ +/* + * 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.jta; + +import java.util.List; + +import javax.naming.NamingException; + +import com.ibm.websphere.uow.UOWSynchronizationRegistry; +import com.ibm.wsspi.uow.UOWAction; +import com.ibm.wsspi.uow.UOWActionException; +import com.ibm.wsspi.uow.UOWException; +import com.ibm.wsspi.uow.UOWManager; + +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.InvalidTimeoutException; +import org.springframework.transaction.NestedTransactionNotSupportedException; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * WebSphere-specific PlatformTransactionManager implementation that delegates + * to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's + * JNDI environment. This allows Spring to leverage the full power of the WebSphere + * transaction coordinator, including transaction suspension, in a manner that is + * perfectly compliant with officially supported WebSphere API. + * + *

The {@link CallbackPreferringPlatformTransactionManager} interface + * implemented by this class indicates that callers should preferably pass in + * a {@link TransactionCallback} through the {@link #execute} method, which + * will be handled through the callback-based WebSphere UOWManager API instead + * of through standard JTA API (UserTransaction / TransactionManager). This avoids + * the use of the non-public javax.transaction.TransactionManager + * API on WebSphere, staying within supported WebSphere API boundaries. + * + *

This transaction manager implementation derives from Spring's standard + * {@link JtaTransactionManager}, inheriting the capability to support programmatic + * transaction demarcation via getTransaction / commit / + * rollback calls through a JTA UserTransaction handle, for callers + * that do not use the TransactionCallback-based {@link #execute} method. However, + * transaction suspension is not supported in this getTransaction + * style (unless you explicitly specify a {@link #setTransactionManager} reference, + * despite the official WebSphere recommendations). Use the {@link #execute} style + * for any code that might require transaction suspension. + * + *

This transaction manager is compatible with WebSphere 7.0 as well as recent + * WebSphere 6.0.x and 6.1.x versions. Check the documentation for your specific + * WebSphere version to find out whether UOWManager support is available. If it + * is not available, consider using Spring's standard {@link JtaTransactionManager} + * class, if necessary specifying the {@link WebSphereTransactionManagerFactoryBean} + * as "transactionManager" through the corresponding bean property. However, note + * that transaction suspension is not officially supported in such a scenario + * (despite it being known to work properly). + * + *

The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager". + * If the location happens to differ according to your WebSphere documentation, + * simply specify the actual location through this transaction manager's + * "uowManagerName" bean property. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setUowManager + * @see #setUowManagerName + * @see com.ibm.wsspi.uow.UOWManager + */ +public class WebSphereUowTransactionManager extends JtaTransactionManager + implements CallbackPreferringPlatformTransactionManager { + + /** + * Default JNDI location for the WebSphere UOWManager. + * @see #setUowManagerName + */ + public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager"; + + + private UOWManager uowManager; + + private String uowManagerName = DEFAULT_UOW_MANAGER_NAME; + + + /** + * Create a new WebSphereUowTransactionManager. + */ + public WebSphereUowTransactionManager() { + setAutodetectTransactionManager(false); + } + + /** + * Create a new WebSphereUowTransactionManager for the given UOWManager. + * @param uowManager the WebSphere UOWManager to use as direct reference + */ + public WebSphereUowTransactionManager(UOWManager uowManager) { + this(); + this.uowManager = uowManager; + } + + + /** + * Set the WebSphere UOWManager to use as direct reference. + *

Typically just used for test setups; in a J2EE environment, + * the UOWManager will always be fetched from JNDI. + * @see #setUserTransactionName + */ + public void setUowManager(UOWManager uowManager) { + this.uowManager = uowManager; + } + + /** + * Set the JNDI name of the WebSphere UOWManager. + * The default "java:comp/websphere/UOWManager" is used if not set. + * @see #DEFAULT_USER_TRANSACTION_NAME + * @see #setUowManager + */ + public void setUowManagerName(String uowManagerName) { + this.uowManagerName = uowManagerName; + } + + + public void afterPropertiesSet() throws TransactionSystemException { + initUserTransactionAndTransactionManager(); + + // Fetch UOWManager handle from JNDI, if necessary. + if (this.uowManager == null) { + if (this.uowManagerName != null) { + this.uowManager = lookupUowManager(this.uowManagerName); + } + else { + throw new IllegalStateException("'uowManager' or 'uowManagerName' is required"); + } + } + } + + /** + * Look up the WebSphere UOWManager in JNDI via the configured name. + * Called by afterPropertiesSet if no direct UOWManager reference was set. + * Can be overridden in subclasses to provide a different UOWManager object. + * @param uowManagerName the JNDI name of the UOWManager + * @return the UOWManager object + * @throws TransactionSystemException if the JNDI lookup failed + * @see #setJndiTemplate + * @see #setUowManagerName + */ + protected UOWManager lookupUowManager(String uowManagerName) throws TransactionSystemException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Retrieving WebSphere UOWManager from JNDI location [" + uowManagerName + "]"); + } + return (UOWManager) getJndiTemplate().lookup(uowManagerName, UOWManager.class); + } + catch (NamingException ex) { + throw new TransactionSystemException( + "WebSphere UOWManager is not available at JNDI location [" + uowManagerName + "]", ex); + } + } + + /** + * Registers the synchronizations as interposed JTA Synchronization on the UOWManager. + */ + protected void doRegisterAfterCompletionWithJtaTransaction(JtaTransactionObject txObject, List synchronizations) { + this.uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); + } + + + public Object execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { + if (definition == null) { + // Use defaults if no transaction definition given. + definition = new DefaultTransactionDefinition(); + } + + if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); + } + int pb = definition.getPropagationBehavior(); + boolean existingTx = (this.uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE && + this.uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION); + + int uowType = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION; + boolean joinTx = false; + boolean newSynch = false; + + if (existingTx) { + if (pb == TransactionDefinition.PROPAGATION_NEVER) { + throw new IllegalTransactionStateException( + "Transaction propagation 'never' but existing transaction found"); + } + if (pb == TransactionDefinition.PROPAGATION_NESTED) { + throw new NestedTransactionNotSupportedException( + "Transaction propagation 'nested' not supported for WebSphere UOW transactions"); + } + if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || + pb == TransactionDefinition.PROPAGATION_REQUIRED || pb == TransactionDefinition.PROPAGATION_MANDATORY) { + joinTx = true; + newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + } + else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { + uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; + newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + } + else { + newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + } + } + else { + if (pb == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "Transaction propagation 'mandatory' but no existing transaction found"); + } + if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || + pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED || pb == TransactionDefinition.PROPAGATION_NEVER) { + uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; + newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + } + else { + newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + } + } + + boolean debug = logger.isDebugEnabled(); + if (debug) { + logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); + } + SuspendedResourcesHolder suspendedResources = (existingTx && !joinTx ? suspend(null) : null); + try { + if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) { + this.uowManager.setUOWTimeout(uowType, definition.getTimeout()); + } + if (debug) { + logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx); + } + UOWActionAdapter action = new UOWActionAdapter( + definition, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug); + this.uowManager.runUnderUOW(uowType, joinTx, action); + if (debug) { + logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx); + } + return action.getResult(); + } + catch (UOWException ex) { + throw new TransactionSystemException("UOWManager transaction processing failed", ex); + } + catch (UOWActionException ex) { + throw new TransactionSystemException("UOWManager threw unexpected UOWActionException", ex); + } + finally { + if (suspendedResources != null) { + resume(null, suspendedResources); + } + } + } + + + /** + * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape. + */ + private class UOWActionAdapter implements UOWAction { + + private final TransactionDefinition definition; + + private final TransactionCallback callback; + + private final boolean actualTransaction; + + private final boolean newTransaction; + + private final boolean newSynchronization; + + private boolean debug; + + private Object result; + + public UOWActionAdapter(TransactionDefinition definition, TransactionCallback callback, + boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) { + this.definition = definition; + this.callback = callback; + this.actualTransaction = actualTransaction; + this.newTransaction = newTransaction; + this.newSynchronization = newSynchronization; + this.debug = debug; + } + + public void run() { + DefaultTransactionStatus status = newTransactionStatus( + this.definition, (this.actualTransaction ? this : null), + this.newTransaction, this.newSynchronization, this.debug, null); + try { + this.result = this.callback.doInTransaction(status); + triggerBeforeCommit(status); + } + finally { + if (status.isLocalRollbackOnly()) { + if (status.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + uowManager.setRollbackOnly(); + } + triggerBeforeCompletion(status); + if (status.isNewSynchronization()) { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + TransactionSynchronizationManager.clear(); + uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); + } + } + } + + public Object getResult() { + return this.result; + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/package.html new file mode 100644 index 00000000000..eaf26180c08 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/jta/package.html @@ -0,0 +1,7 @@ + + + +Transaction SPI implementation for JTA. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/package.html new file mode 100644 index 00000000000..2956ba61285 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/package.html @@ -0,0 +1,9 @@ + + + +Exception hierarchy for Spring's transaction infrastructure, +independent of any specific transaction management system. +Contains transaction manager, definition, and status interfaces. + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java new file mode 100644 index 00000000000..c6b80402db5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java @@ -0,0 +1,1282 @@ +/* + * Copyright 2002-2008 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.support; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.Constants; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.InvalidTimeoutException; +import org.springframework.transaction.NestedTransactionNotSupportedException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSuspensionNotSupportedException; +import org.springframework.transaction.UnexpectedRollbackException; + +/** + * Abstract base class that implements Spring's standard transaction workflow, + * serving as basis for concrete platform transaction managers like + * {@link org.springframework.transaction.jta.JtaTransactionManager} and + * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}. + * + *

This base class provides the following workflow handling: + *

+ * + *

Subclasses have to implement specific template methods for specific + * states of a transaction, e.g.: begin, suspend, resume, commit, rollback. + * The most important of them are abstract and must be provided by a concrete + * implementation; for the rest, defaults are provided, so overriding is optional. + * + *

Transaction synchronization is a generic mechanism for registering callbacks + * that get invoked at transaction completion time. This is mainly used internally + * by the data access support classes for JDBC, Hibernate, JDO, etc when running + * within a JTA transaction: They register resources that are opened within the + * transaction for closing at transaction completion time, allowing e.g. for reuse + * of the same Hibernate Session within the transaction. The same mechanism can + * also be leveraged for custom synchronization needs in an application. + * + *

The state of this class is serializable, to allow for serializing the + * transaction strategy along with proxies that carry a transaction interceptor. + * It is up to subclasses if they wish to make their state to be serializable too. + * They should implement the java.io.Serializable marker interface in + * that case, and potentially a private readObject() method (according + * to Java serialization rules) if they need to restore any transient state. + * + * @author Juergen Hoeller + * @since 28.03.2003 + * @see #setTransactionSynchronization + * @see TransactionSynchronizationManager + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + * @see org.springframework.orm.hibernate3.HibernateTransactionManager + */ +public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { + + /** + * Always activate transaction synchronization, even for "empty" transactions + * that result from PROPAGATION_SUPPORTS with no existing backend transaction. + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER + */ + public static final int SYNCHRONIZATION_ALWAYS = 0; + + /** + * Activate transaction synchronization only for actual transactions, + * that is, not for empty ones that result from PROPAGATION_SUPPORTS with + * no existing backend transaction. + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW + */ + public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1; + + /** + * Never active transaction synchronization, not even for actual transactions. + */ + public static final int SYNCHRONIZATION_NEVER = 2; + + + /** Constants instance for AbstractPlatformTransactionManager */ + private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class); + + + /** Transient to optimize serialization */ + protected transient Log logger = LogFactory.getLog(getClass()); + + private int transactionSynchronization = SYNCHRONIZATION_ALWAYS; + + private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT; + + private boolean nestedTransactionAllowed = false; + + private boolean validateExistingTransaction = false; + + private boolean globalRollbackOnParticipationFailure = true; + + private boolean failEarlyOnGlobalRollbackOnly = false; + + private boolean rollbackOnCommitFailure = false; + + + /** + * Set the transaction synchronization by the name of the corresponding constant + * in this class, e.g. "SYNCHRONIZATION_ALWAYS". + * @param constantName name of the constant + * @see #SYNCHRONIZATION_ALWAYS + */ + public final void setTransactionSynchronizationName(String constantName) { + setTransactionSynchronization(constants.asNumber(constantName).intValue()); + } + + /** + * Set when this transaction manager should activate the thread-bound + * transaction synchronization support. Default is "always". + *

Note that transaction synchronization isn't supported for + * multiple concurrent transactions by different transaction managers. + * Only one transaction manager is allowed to activate it at any time. + * @see #SYNCHRONIZATION_ALWAYS + * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + * @see #SYNCHRONIZATION_NEVER + * @see TransactionSynchronizationManager + * @see TransactionSynchronization + */ + public final void setTransactionSynchronization(int transactionSynchronization) { + this.transactionSynchronization = transactionSynchronization; + } + + /** + * Return if this transaction manager should activate the thread-bound + * transaction synchronization support. + */ + public final int getTransactionSynchronization() { + return this.transactionSynchronization; + } + + /** + * Specify the default timeout that this transaction manager should apply + * if there is no timeout specified at the transaction level, in seconds. + *

Default is the underlying transaction infrastructure's default timeout, + * e.g. typically 30 seconds in case of a JTA provider, indicated by the + * TransactionDefinition.TIMEOUT_DEFAULT value. + * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT + */ + public final void setDefaultTimeout(int defaultTimeout) { + if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout); + } + this.defaultTimeout = defaultTimeout; + } + + /** + * Return the default timeout that this transaction manager should apply + * if there is no timeout specified at the transaction level, in seconds. + *

Returns TransactionDefinition.TIMEOUT_DEFAULT to indicate + * the underlying transaction infrastructure's default timeout. + */ + public final int getDefaultTimeout() { + return this.defaultTimeout; + } + + /** + * Set whether nested transactions are allowed. Default is "false". + *

Typically initialized with an appropriate default by the + * concrete transaction manager subclass. + */ + public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) { + this.nestedTransactionAllowed = nestedTransactionAllowed; + } + + /** + * Return whether nested transactions are allowed. + */ + public final boolean isNestedTransactionAllowed() { + return this.nestedTransactionAllowed; + } + + /** + * Set whether existing transactions should be validated before participating + * in them. + *

When participating in an existing transaction (e.g. with + * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing + * transaction), this outer transaction's characteristics will apply even + * to the inner transaction scope. Validation will detect incompatible + * isolation level and read-only settings on the inner transaction definition + * and reject participation accordingly through throwing a corresponding exception. + *

Default is "false", leniently ignoring inner transaction settings, + * simply overriding them with the outer transaction's characteristics. + * Switch this flag to "true" in order to enforce strict validation. + */ + public final void setValidateExistingTransaction(boolean validateExistingTransaction) { + this.validateExistingTransaction = validateExistingTransaction; + } + + /** + * Return whether existing transactions should be validated before participating + * in them. + */ + public final boolean isValidateExistingTransaction() { + return this.validateExistingTransaction; + } + + /** + * Set whether to globally mark an existing transaction as rollback-only + * after a participating transaction failed. + *

Default is "true": If a participating transaction (e.g. with + * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing + * transaction) fails, the transaction will be globally marked as rollback-only. + * The only possible outcome of such a transaction is a rollback: The + * transaction originator cannot make the transaction commit anymore. + *

Switch this to "false" to let the transaction originator make the rollback + * decision. If a participating transaction fails with an exception, the caller + * can still decide to continue with a different path within the transaction. + * However, note that this will only work as long as all participating resources + * are capable of continuing towards a transaction commit even after a data access + * failure: This is generally not the case for a Hibernate Session, for example; + * neither is it for a sequence of JDBC insert/update/delete operations. + *

Note:This flag only applies to an explicit rollback attempt for a + * subtransaction, typically caused by an exception thrown by a data access operation + * (where TransactionInterceptor will trigger a PlatformTransactionManager.rollback() + * call according to a rollback rule). If the flag is off, the caller can handle the exception + * and decide on a rollback, independent of the rollback rules of the subtransaction. + * This flag does, however, not apply to explicit setRollbackOnly + * calls on a TransactionStatus, which will always cause an eventual + * global rollback (as it might not throw an exception after the rollback-only call). + *

The recommended solution for handling failure of a subtransaction + * is a "nested transaction", where the global transaction can be rolled + * back to a savepoint taken at the beginning of the subtransaction. + * PROPAGATION_NESTED provides exactly those semantics; however, it will + * only work when nested transaction support is available. This is the case + * with DataSourceTransactionManager, but not with JtaTransactionManager. + * @see #setNestedTransactionAllowed + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + * @see org.springframework.transaction.jta.JtaTransactionManager + */ + public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) { + this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure; + } + + /** + * Return whether to globally mark an existing transaction as rollback-only + * after a participating transaction failed. + */ + public final boolean isGlobalRollbackOnParticipationFailure() { + return this.globalRollbackOnParticipationFailure; + } + + /** + * Set whether to fail early in case of the transaction being globally marked + * as rollback-only. + *

Default is "false", only causing an UnexpectedRollbackException at the + * outermost transaction boundary. Switch this flag on to cause an + * UnexpectedRollbackException as early as the global rollback-only marker + * has been first detected, even from within an inner transaction boundary. + *

Note that, as of Spring 2.0, the fail-early behavior for global + * rollback-only markers has been unified: All transaction managers will by + * default only cause UnexpectedRollbackException at the outermost transaction + * boundary. This allows, for example, to continue unit tests even after an + * operation failed and the transaction will never be completed. All transaction + * managers will only fail earlier if this flag has explicitly been set to "true". + * @see org.springframework.transaction.UnexpectedRollbackException + */ + public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) { + this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly; + } + + /** + * Return whether to fail early in case of the transaction being globally marked + * as rollback-only. + */ + public final boolean isFailEarlyOnGlobalRollbackOnly() { + return this.failEarlyOnGlobalRollbackOnly; + } + + /** + * Set whether doRollback should be performed on failure of the + * doCommit call. Typically not necessary and thus to be avoided, + * as it can potentially override the commit exception with a subsequent + * rollback exception. + *

Default is "false". + * @see #doCommit + * @see #doRollback + */ + public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) { + this.rollbackOnCommitFailure = rollbackOnCommitFailure; + } + + /** + * Return whether doRollback should be performed on failure of the + * doCommit call. + */ + public final boolean isRollbackOnCommitFailure() { + return this.rollbackOnCommitFailure; + } + + + //--------------------------------------------------------------------- + // Implementation of PlatformTransactionManager + //--------------------------------------------------------------------- + + /** + * This implementation handles propagation behavior. Delegates to + * doGetTransaction, isExistingTransaction + * and doBegin. + * @see #doGetTransaction + * @see #isExistingTransaction + * @see #doBegin + */ + public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + Object transaction = doGetTransaction(); + + // Cache debug flag to avoid repeated checks. + boolean debugEnabled = logger.isDebugEnabled(); + + if (definition == null) { + // Use defaults if no transaction definition given. + definition = new DefaultTransactionDefinition(); + } + + if (isExistingTransaction(transaction)) { + // Existing transaction found -> check propagation behavior to find out how to behave. + return handleExistingTransaction(definition, transaction, debugEnabled); + } + + // Check definition settings for new transaction. + if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); + } + + // No existing transaction found -> check propagation behavior to find out how to proceed. + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "No existing transaction found for transaction marked with propagation 'mandatory'"); + } + else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || + definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || + definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + SuspendedResourcesHolder suspendedResources = suspend(null); + if (debugEnabled) { + logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); + } + try { + doBegin(transaction, definition); + } + catch (RuntimeException ex) { + resume(null, suspendedResources); + throw ex; + } + catch (Error err) { + resume(null, suspendedResources); + throw err; + } + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + } + else { + // Create "empty" transaction: no actual transaction, but potentially synchronization. + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return newTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); + } + } + + /** + * Create a TransactionStatus for an existing transaction. + */ + private TransactionStatus handleExistingTransaction( + TransactionDefinition definition, Object transaction, boolean debugEnabled) + throws TransactionException { + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { + throw new IllegalTransactionStateException( + "Existing transaction found for transaction marked with propagation 'never'"); + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { + if (debugEnabled) { + logger.debug("Suspending current transaction"); + } + Object suspendedResources = suspend(transaction); + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return newTransactionStatus( + definition, null, false, newSynchronization, debugEnabled, suspendedResources); + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { + if (debugEnabled) { + logger.debug("Suspending current transaction, creating new transaction with name [" + + definition.getName() + "]"); + } + SuspendedResourcesHolder suspendedResources = suspend(transaction); + try { + doBegin(transaction, definition); + } + catch (RuntimeException beginEx) { + resumeAfterBeginException(transaction, suspendedResources, beginEx); + throw beginEx; + } + catch (Error beginErr) { + resumeAfterBeginException(transaction, suspendedResources, beginErr); + throw beginErr; + } + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + } + + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + if (!isNestedTransactionAllowed()) { + throw new NestedTransactionNotSupportedException( + "Transaction manager does not allow nested transactions by default - " + + "specify 'nestedTransactionAllowed' property with value 'true'"); + } + if (debugEnabled) { + logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); + } + if (useSavepointForNestedTransaction()) { + // Create savepoint within existing Spring-managed transaction, + // through the SavepointManager API implemented by TransactionStatus. + // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. + DefaultTransactionStatus status = + newTransactionStatus(definition, transaction, false, false, debugEnabled, null); + status.createAndHoldSavepoint(); + return status; + } + else { + // Nested transaction through nested begin and commit/rollback calls. + // Usually only for JTA: Spring synchronization might get activated here + // in case of a pre-existing JTA transaction. + doBegin(transaction, definition); + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null); + } + } + + // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. + if (debugEnabled) { + logger.debug("Participating in existing transaction"); + } + if (isValidateExistingTransaction()) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + if (currentIsolationLevel == null || currentIsolationLevel.intValue() != definition.getIsolationLevel()) { + Constants isoConstants = DefaultTransactionDefinition.constants; + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] specifies isolation level which is incompatible with existing transaction: " + + (currentIsolationLevel != null ? + isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : + "(unknown)")); + } + } + if (!definition.isReadOnly()) { + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] is not marked as read-only but existing transaction is"); + } + } + } + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return newTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); + } + + /** + * Create a new TransactionStatus for the given arguments, + * initializing transaction synchronization as appropriate. + */ + protected DefaultTransactionStatus newTransactionStatus( + TransactionDefinition definition, Object transaction, boolean newTransaction, + boolean newSynchronization, boolean debug, Object suspendedResources) { + + boolean actualNewSynchronization = newSynchronization && + !TransactionSynchronizationManager.isSynchronizationActive(); + if (actualNewSynchronization) { + TransactionSynchronizationManager.setActualTransactionActive(transaction != null); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( + (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ? + new Integer(definition.getIsolationLevel()) : null); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); + TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); + TransactionSynchronizationManager.initSynchronization(); + } + return new DefaultTransactionStatus( + transaction, newTransaction, actualNewSynchronization, + definition.isReadOnly(), debug, suspendedResources); + } + + /** + * Determine the actual timeout to use for the given definition. + * Will fall back to this manager's default timeout if the + * transaction definition doesn't specify a non-default value. + * @param definition the transaction definition + * @return the actual timeout to use + * @see org.springframework.transaction.TransactionDefinition#getTimeout() + * @see #setDefaultTimeout + */ + protected int determineTimeout(TransactionDefinition definition) { + if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { + return definition.getTimeout(); + } + return this.defaultTimeout; + } + + + /** + * Suspend the given transaction. Suspends transaction synchronization first, + * then delegates to the doSuspend template method. + * @param transaction the current transaction object + * (or null to just suspend active synchronizations, if any) + * @return an object that holds suspended resources + * (or null if neither transaction nor synchronization active) + * @see #doSuspend + * @see #resume + */ + protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + List suspendedSynchronizations = doSuspendSynchronization(); + try { + Object suspendedResources = null; + if (transaction != null) { + suspendedResources = doSuspend(transaction); + } + String name = TransactionSynchronizationManager.getCurrentTransactionName(); + TransactionSynchronizationManager.setCurrentTransactionName(null); + boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); + Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); + boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); + TransactionSynchronizationManager.setActualTransactionActive(false); + return new SuspendedResourcesHolder( + suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); + } + catch (RuntimeException ex) { + // doSuspend failed - original transaction is still active... + doResumeSynchronization(suspendedSynchronizations); + throw ex; + } + catch (Error err) { + // doSuspend failed - original transaction is still active... + doResumeSynchronization(suspendedSynchronizations); + throw err; + } + } + else if (transaction != null) { + // Transaction active but no synchronization active. + Object suspendedResources = doSuspend(transaction); + return new SuspendedResourcesHolder(suspendedResources); + } + else { + // Neither transaction nor synchronization active. + return null; + } + } + + /** + * Resume the given transaction. Delegates to the doResume + * template method first, then resuming transaction synchronization. + * @param transaction the current transaction object + * @param resourcesHolder the object that holds suspended resources, + * as returned by suspend (or null to just + * resume synchronizations, if any) + * @see #doResume + * @see #suspend + */ + protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) + throws TransactionException { + + if (resourcesHolder != null) { + Object suspendedResources = resourcesHolder.suspendedResources; + if (suspendedResources != null) { + doResume(transaction, suspendedResources); + } + List suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; + if (suspendedSynchronizations != null) { + TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive); + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); + TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); + TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); + doResumeSynchronization(suspendedSynchronizations); + } + } + } + + /** + * Resume outer transaction after inner transaction begin failed. + */ + private void resumeAfterBeginException( + Object transaction, SuspendedResourcesHolder suspendedResources, Throwable beginEx) { + + String exMessage = "Inner transaction begin exception overridden by outer transaction resume exception"; + try { + resume(transaction, suspendedResources); + } + catch (RuntimeException resumeEx) { + logger.error(exMessage, beginEx); + throw resumeEx; + } + catch (Error resumeErr) { + logger.error(exMessage, beginEx); + throw resumeErr; + } + } + + /** + * Suspend all current synchronizations and deactivate transaction + * synchronization for the current thread. + * @return the List of suspended TransactionSynchronization objects + */ + private List doSuspendSynchronization() { + List suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations(); + for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) { + ((TransactionSynchronization) it.next()).suspend(); + } + TransactionSynchronizationManager.clearSynchronization(); + return suspendedSynchronizations; + } + + /** + * Reactivate transaction synchronization for the current thread + * and resume all given synchronizations. + * @param suspendedSynchronizations List of TransactionSynchronization objects + */ + private void doResumeSynchronization(List suspendedSynchronizations) { + TransactionSynchronizationManager.initSynchronization(); + for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) { + TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); + synchronization.resume(); + TransactionSynchronizationManager.registerSynchronization(synchronization); + } + } + + + /** + * This implementation of commit handles participating in existing + * transactions and programmatic rollback requests. + * Delegates to isRollbackOnly, doCommit + * and rollback. + * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() + * @see #doCommit + * @see #rollback + */ + public final void commit(TransactionStatus status) throws TransactionException { + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + if (defStatus.isLocalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + processRollback(defStatus); + return; + } + if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); + } + processRollback(defStatus); + // Throw UnexpectedRollbackException only at outermost transaction boundary + // or if explicitly asked to. + if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + return; + } + + processCommit(defStatus); + } + + /** + * Process an actual commit. + * Rollback-only flags have already been checked and applied. + * @param status object representing the transaction + * @throws TransactionException in case of commit failure + */ + private void processCommit(DefaultTransactionStatus status) throws TransactionException { + try { + boolean beforeCompletionInvoked = false; + try { + prepareForCommit(status); + triggerBeforeCommit(status); + triggerBeforeCompletion(status); + beforeCompletionInvoked = true; + boolean globalRollbackOnly = false; + if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { + globalRollbackOnly = status.isGlobalRollbackOnly(); + } + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Releasing transaction savepoint"); + } + status.releaseHeldSavepoint(); + } + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction commit"); + } + doCommit(status); + } + // Throw UnexpectedRollbackException if we have a global rollback-only + // marker but still didn't get a corresponding exception from commit. + if (globalRollbackOnly) { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } + catch (UnexpectedRollbackException ex) { + // can only be caused by doCommit + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + throw ex; + } + catch (TransactionException ex) { + // can only be caused by doCommit + if (isRollbackOnCommitFailure()) { + doRollbackOnCommitException(status, ex); + } + else { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + } + throw ex; + } + catch (RuntimeException ex) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, ex); + throw ex; + } + catch (Error err) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, err); + throw err; + } + + // Trigger afterCommit callbacks, with an exception thrown there + // propagated to callers but the transaction still considered as committed. + try { + triggerAfterCommit(status); + } + finally { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + } + + } + finally { + cleanupAfterCompletion(status); + } + } + + /** + * This implementation of rollback handles participating in existing + * transactions. Delegates to doRollback and + * doSetRollbackOnly. + * @see #doRollback + * @see #doSetRollbackOnly + */ + public final void rollback(TransactionStatus status) throws TransactionException { + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + processRollback(defStatus); + } + + /** + * Process an actual rollback. + * The completed flag has already been checked. + * @param status object representing the transaction + * @throws TransactionException in case of rollback failure + */ + private void processRollback(DefaultTransactionStatus status) { + try { + try { + triggerBeforeCompletion(status); + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Rolling back transaction to savepoint"); + } + status.rollbackToHeldSavepoint(); + } + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback"); + } + doRollback(status); + } + else if (status.hasTransaction()) { + if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug( + "Participating transaction failed - marking existing transaction as rollback-only"); + } + doSetRollbackOnly(status); + } + else { + if (status.isDebug()) { + logger.debug( + "Participating transaction failed - letting transaction originator decide on rollback"); + } + } + } + else { + logger.debug("Should roll back transaction but cannot - no transaction available"); + } + } + catch (RuntimeException ex) { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw ex; + } + catch (Error err) { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw err; + } + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + } + finally { + cleanupAfterCompletion(status); + } + } + + /** + * Invoke doRollback, handling rollback exceptions properly. + * @param status object representing the transaction + * @param ex the thrown application exception or error + * @throws TransactionException in case of rollback failure + * @see #doRollback + */ + private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) + throws TransactionException { + try { + if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback after commit exception", ex); + } + doRollback(status); + } + else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug("Marking existing transaction as rollback-only after commit exception", ex); + } + doSetRollbackOnly(status); + } + } + catch (RuntimeException rbex) { + logger.error("Commit exception overridden by rollback exception", ex); + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw rbex; + } + catch (Error rberr) { + logger.error("Commit exception overridden by rollback exception", ex); + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw rberr; + } + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + } + + + /** + * Trigger beforeCommit callbacks. + * @param status object representing the transaction + */ + protected final void triggerBeforeCommit(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering beforeCommit synchronization"); + } + TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly()); + } + } + + /** + * Trigger beforeCompletion callbacks. + * @param status object representing the transaction + */ + protected final void triggerBeforeCompletion(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering beforeCompletion synchronization"); + } + TransactionSynchronizationUtils.triggerBeforeCompletion(); + } + } + + /** + * Trigger afterCommit callbacks. + * @param status object representing the transaction + */ + private void triggerAfterCommit(DefaultTransactionStatus status) { + if (status.isNewSynchronization()) { + if (status.isDebug()) { + logger.trace("Triggering afterCommit synchronization"); + } + TransactionSynchronizationUtils.triggerAfterCommit(); + } + } + + /** + * Trigger afterCompletion callbacks. + * @param status object representing the transaction + * @param completionStatus completion status according to TransactionSynchronization constants + */ + private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) { + if (status.isNewSynchronization()) { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + if (!status.hasTransaction() || status.isNewTransaction()) { + if (status.isDebug()) { + logger.trace("Triggering afterCompletion synchronization"); + } + // No transaction or new transaction for the current scope -> + // invoke the afterCompletion callbacks immediately + invokeAfterCompletion(synchronizations, completionStatus); + } + else { + // Existing transaction that we participate in, controlled outside + // of the scope of this Spring transaction manager -> try to register + // an afterCompletion callback with the existing (JTA) transaction. + registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations); + } + } + } + + /** + * Actually invoke the afterCompletion methods of the + * given Spring TransactionSynchronization objects. + *

To be called by this abstract manager itself, or by special implementations + * of the registerAfterCompletionWithExistingTransaction callback. + * @param synchronizations List of TransactionSynchronization objects + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + protected final void invokeAfterCompletion(List synchronizations, int completionStatus) { + TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus); + } + + /** + * Clean up after completion, clearing synchronization if necessary, + * and invoking doCleanupAfterCompletion. + * @param status object representing the transaction + * @see #doCleanupAfterCompletion + */ + private void cleanupAfterCompletion(DefaultTransactionStatus status) { + status.setCompleted(); + if (status.isNewSynchronization()) { + TransactionSynchronizationManager.clear(); + } + if (status.isNewTransaction()) { + doCleanupAfterCompletion(status.getTransaction()); + } + if (status.getSuspendedResources() != null) { + if (status.isDebug()) { + logger.debug("Resuming suspended transaction"); + } + resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources()); + } + } + + + //--------------------------------------------------------------------- + // Template methods to be implemented in subclasses + //--------------------------------------------------------------------- + + /** + * Return a transaction object for the current transaction state. + *

The returned object will usually be specific to the concrete transaction + * manager implementation, carrying corresponding transaction state in a + * modifiable fashion. This object will be passed into the other template + * methods (e.g. doBegin and doCommit), either directly or as part of a + * DefaultTransactionStatus instance. + *

The returned object should contain information about any existing + * transaction, that is, a transaction that has already started before the + * current getTransaction call on the transaction manager. + * Consequently, a doGetTransaction implementation will usually + * look for an existing transaction and store corresponding state in the + * returned transaction object. + * @return the current transaction object + * @throws org.springframework.transaction.CannotCreateTransactionException + * if transaction support is not available + * @throws TransactionException in case of lookup or system errors + * @see #doBegin + * @see #doCommit + * @see #doRollback + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract Object doGetTransaction() throws TransactionException; + + /** + * Check if the given transaction object indicates an existing transaction + * (that is, a transaction which has already started). + *

The result will be evaluated according to the specified propagation + * behavior for the new transaction. An existing transaction might get + * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction + * might participate in the existing one (in case of PROPAGATION_REQUIRED). + *

The default implementation returns false, assuming that + * participating in existing transactions is generally not supported. + * Subclasses are of course encouraged to provide such support. + * @param transaction transaction object returned by doGetTransaction + * @return if there is an existing transaction + * @throws TransactionException in case of system errors + * @see #doGetTransaction + */ + protected boolean isExistingTransaction(Object transaction) throws TransactionException { + return false; + } + + /** + * Return whether to use a savepoint for a nested transaction. + *

Default is true, which causes delegation to DefaultTransactionStatus + * for creating and holding a savepoint. If the transaction object does not implement + * the SavepointManager interface, a NestedTransactionNotSupportedException will be + * thrown. Else, the SavepointManager will be asked to create a new savepoint to + * demarcate the start of the nested transaction. + *

Subclasses can override this to return false, causing a further + * call to doBegin - within the context of an already existing transaction. + * The doBegin implementation needs to handle this accordingly in such + * a scenario. This is appropriate for JTA, for example. + * @see DefaultTransactionStatus#createAndHoldSavepoint + * @see DefaultTransactionStatus#rollbackToHeldSavepoint + * @see DefaultTransactionStatus#releaseHeldSavepoint + * @see #doBegin + */ + protected boolean useSavepointForNestedTransaction() { + return true; + } + + /** + * Begin a new transaction with semantics according to the given transaction + * definition. Does not have to care about applying the propagation behavior, + * as this has already been handled by this abstract manager. + *

This method gets called when the transaction manager has decided to actually + * start a new transaction. Either there wasn't any transaction before, or the + * previous transaction has been suspended. + *

A special scenario is a nested transaction without savepoint: If + * useSavepointForNestedTransaction() returns "false", this method + * will be called to start a nested transaction when necessary. In such a context, + * there will be an active transaction: The implementation of this method has + * to detect this and start an appropriate nested transaction. + * @param transaction transaction object returned by doGetTransaction + * @param definition TransactionDefinition instance, describing propagation + * behavior, isolation level, read-only flag, timeout, and transaction name + * @throws TransactionException in case of creation or system errors + */ + protected abstract void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException; + + /** + * Suspend the resources of the current transaction. + * Transaction synchronization will already have been suspended. + *

The default implementation throws a TransactionSuspensionNotSupportedException, + * assuming that transaction suspension is generally not supported. + * @param transaction transaction object returned by doGetTransaction + * @return an object that holds suspended resources + * (will be kept unexamined for passing it into doResume) + * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException + * if suspending is not supported by the transaction manager implementation + * @throws TransactionException in case of system errors + * @see #doResume + */ + protected Object doSuspend(Object transaction) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); + } + + /** + * Resume the resources of the current transaction. + * Transaction synchronization will be resumed afterwards. + *

The default implementation throws a TransactionSuspensionNotSupportedException, + * assuming that transaction suspension is generally not supported. + * @param transaction transaction object returned by doGetTransaction + * @param suspendedResources the object that holds suspended resources, + * as returned by doSuspend + * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException + * if resuming is not supported by the transaction manager implementation + * @throws TransactionException in case of system errors + * @see #doSuspend + */ + protected void doResume(Object transaction, Object suspendedResources) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); + } + + /** + * Return whether to call doCommit on a transaction that has been + * marked as rollback-only in a global fashion. + *

Does not apply if an application locally sets the transaction to rollback-only + * via the TransactionStatus, but only to the transaction itself being marked as + * rollback-only by the transaction coordinator. + *

Default is "false": Local transaction strategies usually don't hold the rollback-only + * marker in the transaction itself, therefore they can't handle rollback-only transactions + * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger + * a rollback in that case, throwing an UnexpectedRollbackException afterwards. + *

Override this to return "true" if the concrete transaction manager expects a + * doCommit call even for a rollback-only transaction, allowing for + * special handling there. This will, for example, be the case for JTA, where + * UserTransaction.commit will check the read-only flag itself and + * throw a corresponding RollbackException, which might include the specific reason + * (such as a transaction timeout). + *

If this method returns "true" but the doCommit implementation does not + * throw an exception, this transaction manager will throw an UnexpectedRollbackException + * itself. This should not be the typical case; it is mainly checked to cover misbehaving + * JTA providers that silently roll back even when the rollback has not been requested + * by the calling code. + * @see #doCommit + * @see DefaultTransactionStatus#isGlobalRollbackOnly() + * @see DefaultTransactionStatus#isLocalRollbackOnly() + * @see org.springframework.transaction.TransactionStatus#setRollbackOnly() + * @see org.springframework.transaction.UnexpectedRollbackException + * @see javax.transaction.UserTransaction#commit() + * @see javax.transaction.RollbackException + */ + protected boolean shouldCommitOnGlobalRollbackOnly() { + return false; + } + + /** + * Make preparations for commit, to be performed before the + * beforeCommit synchronization callbacks occur. + *

Note that exceptions will get propagated to the commit caller + * and cause a rollback of the transaction. + * @param status the status representation of the transaction + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + */ + protected void prepareForCommit(DefaultTransactionStatus status) { + } + + /** + * Perform an actual commit of the given transaction. + *

An implementation does not need to check the "new transaction" flag + * or the rollback-only flag; this will already have been handled before. + * Usually, a straight commit will be performed on the transaction object + * contained in the passed-in status. + * @param status the status representation of the transaction + * @throws TransactionException in case of commit or system errors + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException; + + /** + * Perform an actual rollback of the given transaction. + *

An implementation does not need to check the "new transaction" flag; + * this will already have been handled before. Usually, a straight rollback + * will be performed on the transaction object contained in the passed-in status. + * @param status the status representation of the transaction + * @throws TransactionException in case of system errors + * @see DefaultTransactionStatus#getTransaction + */ + protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException; + + /** + * Set the given transaction rollback-only. Only called on rollback + * if the current transaction participates in an existing one. + *

The default implementation throws an IllegalTransactionStateException, + * assuming that participating in existing transactions is generally not + * supported. Subclasses are of course encouraged to provide such support. + * @param status the status representation of the transaction + * @throws TransactionException in case of system errors + */ + protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { + throw new IllegalTransactionStateException( + "Participating in existing transactions is not supported - when 'isExistingTransaction' " + + "returns true, appropriate 'doSetRollbackOnly' behavior must be provided"); + } + + /** + * Register the given list of transaction synchronizations with the existing transaction. + *

Invoked when the control of the Spring transaction manager and thus all Spring + * transaction synchronizations end, without the transaction being completed yet. This + * is for example the case when participating in an existing JTA or EJB CMT transaction. + *

The default implementation simply invokes the afterCompletion methods + * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no + * chance to determine the actual outcome of the outer transaction. + * @param transaction transaction object returned by doGetTransaction + * @param synchronizations List of TransactionSynchronization objects + * @throws TransactionException in case of system errors + * @see #invokeAfterCompletion(java.util.List, int) + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations) + throws TransactionException { + + logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " + + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); + invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); + } + + /** + * Cleanup resources after transaction completion. + *

Called after doCommit and doRollback execution, + * on any outcome. The default implementation does nothing. + *

Should not throw any exceptions but just issue warnings on errors. + * @param transaction transaction object returned by doGetTransaction + */ + protected void doCleanupAfterCompletion(Object transaction) { + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Initialize transient fields. + this.logger = LogFactory.getLog(getClass()); + } + + + /** + * Holder for suspended resources. + * Used internally by suspend and resume. + */ + protected static class SuspendedResourcesHolder { + + private final Object suspendedResources; + private List suspendedSynchronizations; + private String name; + private boolean readOnly; + private Integer isolationLevel; + private boolean wasActive; + + private SuspendedResourcesHolder(Object suspendedResources) { + this.suspendedResources = suspendedResources; + } + + private SuspendedResourcesHolder( + Object suspendedResources, List suspendedSynchronizations, + String name, boolean readOnly, Integer isolationLevel, boolean wasActive) { + this.suspendedResources = suspendedResources; + this.suspendedSynchronizations = suspendedSynchronizations; + this.name = name; + this.readOnly = readOnly; + this.isolationLevel = isolationLevel; + this.wasActive = wasActive; + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java new file mode 100644 index 00000000000..7e25c9c4c33 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/AbstractTransactionStatus.java @@ -0,0 +1,204 @@ +/* + * 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.support; + +import org.springframework.transaction.NestedTransactionNotSupportedException; +import org.springframework.transaction.SavepointManager; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionUsageException; + +/** + * Abstract base implementation of the + * {@link org.springframework.transaction.TransactionStatus} interface. + * + *

Pre-implements the handling of local rollback-only and completed flags, and + * delegation to an underlying {@link org.springframework.transaction.SavepointManager}. + * Also offers the option of a holding a savepoint within the transaction. + * + *

Does not assume any specific internal transaction handling, such as an + * underlying transaction object, and no transaction synchronization mechanism. + * + * @author Juergen Hoeller + * @since 1.2.3 + * @see #setRollbackOnly() + * @see #isRollbackOnly() + * @see #setCompleted() + * @see #isCompleted() + * @see #getSavepointManager() + * @see SimpleTransactionStatus + * @see DefaultTransactionStatus + */ +public abstract class AbstractTransactionStatus implements TransactionStatus { + + private boolean rollbackOnly = false; + + private boolean completed = false; + + private Object savepoint; + + + //--------------------------------------------------------------------- + // Handling of current transaction state + //--------------------------------------------------------------------- + + public void setRollbackOnly() { + this.rollbackOnly = true; + } + + /** + * Determine the rollback-only flag via checking both the local rollback-only flag + * of this TransactionStatus and the global rollback-only flag of the underlying + * transaction, if any. + * @see #isLocalRollbackOnly() + * @see #isGlobalRollbackOnly() + */ + public boolean isRollbackOnly() { + return (isLocalRollbackOnly() || isGlobalRollbackOnly()); + } + + /** + * Determine the rollback-only flag via checking this TransactionStatus. + *

Will only return "true" if the application called setRollbackOnly + * on this TransactionStatus object. + */ + public boolean isLocalRollbackOnly() { + return this.rollbackOnly; + } + + /** + * Template method for determining the global rollback-only flag of the + * underlying transaction, if any. + *

This implementation always returns false. + */ + public boolean isGlobalRollbackOnly() { + return false; + } + + /** + * Mark this transaction as completed, that is, committed or rolled back. + */ + public void setCompleted() { + this.completed = true; + } + + public boolean isCompleted() { + return this.completed; + } + + + //--------------------------------------------------------------------- + // Handling of current savepoint state + //--------------------------------------------------------------------- + + /** + * Set a savepoint for this transaction. Useful for PROPAGATION_NESTED. + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NESTED + */ + protected void setSavepoint(Object savepoint) { + this.savepoint = savepoint; + } + + /** + * Get the savepoint for this transaction, if any. + */ + protected Object getSavepoint() { + return this.savepoint; + } + + public boolean hasSavepoint() { + return (this.savepoint != null); + } + + /** + * Create a savepoint and hold it for the transaction. + * @throws org.springframework.transaction.NestedTransactionNotSupportedException + * if the underlying transaction does not support savepoints + */ + public void createAndHoldSavepoint() throws TransactionException { + setSavepoint(getSavepointManager().createSavepoint()); + } + + /** + * Roll back to the savepoint that is held for the transaction. + */ + public void rollbackToHeldSavepoint() throws TransactionException { + if (!hasSavepoint()) { + throw new TransactionUsageException("No savepoint associated with current transaction"); + } + getSavepointManager().rollbackToSavepoint(getSavepoint()); + setSavepoint(null); + } + + /** + * Release the savepoint that is held for the transaction. + */ + public void releaseHeldSavepoint() throws TransactionException { + if (!hasSavepoint()) { + throw new TransactionUsageException("No savepoint associated with current transaction"); + } + getSavepointManager().releaseSavepoint(getSavepoint()); + setSavepoint(null); + } + + + //--------------------------------------------------------------------- + // Implementation of SavepointManager + //--------------------------------------------------------------------- + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @see #getSavepointManager() + * @see org.springframework.transaction.SavepointManager + */ + public Object createSavepoint() throws TransactionException { + return getSavepointManager().createSavepoint(); + } + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @throws org.springframework.transaction.NestedTransactionNotSupportedException + * @see #getSavepointManager() + * @see org.springframework.transaction.SavepointManager + */ + public void rollbackToSavepoint(Object savepoint) throws TransactionException { + getSavepointManager().rollbackToSavepoint(savepoint); + } + + /** + * This implementation delegates to a SavepointManager for the + * underlying transaction, if possible. + * @see #getSavepointManager() + * @see org.springframework.transaction.SavepointManager + */ + public void releaseSavepoint(Object savepoint) throws TransactionException { + getSavepointManager().releaseSavepoint(savepoint); + } + + /** + * Return a SavepointManager for the underlying transaction, if possible. + *

Default implementation always throws a NestedTransactionNotSupportedException. + * @throws org.springframework.transaction.NestedTransactionNotSupportedException + * if the underlying transaction does not support savepoints + */ + protected SavepointManager getSavepointManager() { + throw new NestedTransactionNotSupportedException("This transaction does not support savepoints"); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java new file mode 100644 index 00000000000..1613d3a7172 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/CallbackPreferringPlatformTransactionManager.java @@ -0,0 +1,59 @@ +/* + * 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.support; + +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; + +/** + * Extension of the {@link org.springframework.transaction.PlatformTransactionManager} + * interface, exposing a method for executing a given callback within a transaction. + * + *

Implementors of this interface automatically express a preference for + * callbacks over programmatic getTransaction, commit + * and rollback calls. Calling code may check whether a given + * transaction manager implements this interface to choose to prepare a + * callback instead of explicit transaction demarcation control. + * + *

Spring's {@link TransactionTemplate} and + * {@link org.springframework.transaction.interceptor.TransactionInterceptor} + * detect and use this PlatformTransactionManager variant automatically. + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.transaction.support.TransactionTemplate + * @see org.springframework.transaction.interceptor.TransactionInterceptor + */ +public interface CallbackPreferringPlatformTransactionManager extends PlatformTransactionManager { + + /** + * Execute the action specified by the given callback object within a transaction. + *

Allows for returning a result object created within the transaction, that is, + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as a fatal exception that enforces a rollback. + * Such an exception gets propagated to the caller of the template. + * @param definition the definition for the transaction to wrap the callback in + * @param callback the callback object that specifies the transactional action + * @return a result object returned by the callback, or null if none + * @throws TransactionException in case of initialization, rollback, or system errors + * @throws RuntimeException if thrown by the TransactionCallback + */ + Object execute(TransactionDefinition definition, TransactionCallback callback) + throws TransactionException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java new file mode 100644 index 00000000000..83bcf9687c3 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java @@ -0,0 +1,268 @@ +/* + * 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.support; + +import java.io.Serializable; + +import org.springframework.core.Constants; +import org.springframework.transaction.TransactionDefinition; + +/** + * Default implementation of the {@link TransactionDefinition} interface, + * offering bean-style configuration and sensible default values + * (PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, readOnly=false). + * + *

Base class for both {@link TransactionTemplate} and + * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}. + * + * @author Juergen Hoeller + * @since 08.05.2003 + */ +public class DefaultTransactionDefinition implements TransactionDefinition, Serializable { + + /** Prefix for the propagation constants defined in TransactionDefinition */ + public static final String PREFIX_PROPAGATION = "PROPAGATION_"; + + /** Prefix for the isolation constants defined in TransactionDefinition */ + public static final String PREFIX_ISOLATION = "ISOLATION_"; + + /** Prefix for transaction timeout values in description strings */ + public static final String PREFIX_TIMEOUT = "timeout_"; + + /** Marker for read-only transactions in description strings */ + public static final String READ_ONLY_MARKER = "readOnly"; + + + /** Constants instance for TransactionDefinition */ + static final Constants constants = new Constants(TransactionDefinition.class); + + private int propagationBehavior = PROPAGATION_REQUIRED; + + private int isolationLevel = ISOLATION_DEFAULT; + + private int timeout = TIMEOUT_DEFAULT; + + private boolean readOnly = false; + + private String name; + + + /** + * Create a new DefaultTransactionDefinition, with default settings. + * Can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionDefinition() { + } + + /** + * Copy constructor. Definition can be modified through bean property setters. + * @see #setPropagationBehavior + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + * @see #setName + */ + public DefaultTransactionDefinition(TransactionDefinition other) { + this.propagationBehavior = other.getPropagationBehavior(); + this.isolationLevel = other.getIsolationLevel(); + this.timeout = other.getTimeout(); + this.readOnly = other.isReadOnly(); + this.name = other.getName(); + } + + /** + * Create a new DefaultTransactionDefinition with the the given + * propagation behavior. Can be modified through bean property setters. + * @param propagationBehavior one of the propagation constants in the + * TransactionDefinition interface + * @see #setIsolationLevel + * @see #setTimeout + * @see #setReadOnly + */ + public DefaultTransactionDefinition(int propagationBehavior) { + this.propagationBehavior = propagationBehavior; + } + + + /** + * Set the propagation behavior by the name of the corresponding constant in + * TransactionDefinition, e.g. "PROPAGATION_REQUIRED". + * @param constantName name of the constant + * @exception IllegalArgumentException if the supplied value is not resolvable + * to one of the PROPAGATION_ constants or is null + * @see #setPropagationBehavior + * @see #PROPAGATION_REQUIRED + */ + public final void setPropagationBehaviorName(String constantName) throws IllegalArgumentException { + if (constantName == null || !constantName.startsWith(PREFIX_PROPAGATION)) { + throw new IllegalArgumentException("Only propagation constants allowed"); + } + setPropagationBehavior(constants.asNumber(constantName).intValue()); + } + + /** + * Set the propagation behavior. Must be one of the propagation constants + * in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED. + * @exception IllegalArgumentException if the supplied value is not + * one of the PROPAGATION_ constants + * @see #PROPAGATION_REQUIRED + */ + public final void setPropagationBehavior(int propagationBehavior) { + if (!constants.getValues(PREFIX_PROPAGATION).contains(new Integer(propagationBehavior))) { + throw new IllegalArgumentException("Only values of propagation constants allowed"); + } + this.propagationBehavior = propagationBehavior; + } + + public final int getPropagationBehavior() { + return this.propagationBehavior; + } + + /** + * Set the isolation level by the name of the corresponding constant in + * TransactionDefinition, e.g. "ISOLATION_DEFAULT". + * @param constantName name of the constant + * @exception IllegalArgumentException if the supplied value is not resolvable + * to one of the ISOLATION_ constants or is null + * @see #setIsolationLevel + * @see #ISOLATION_DEFAULT + */ + public final void setIsolationLevelName(String constantName) throws IllegalArgumentException { + if (constantName == null || !constantName.startsWith(PREFIX_ISOLATION)) { + throw new IllegalArgumentException("Only isolation constants allowed"); + } + setIsolationLevel(constants.asNumber(constantName).intValue()); + } + + /** + * Set the isolation level. Must be one of the isolation constants + * in the TransactionDefinition interface. Default is ISOLATION_DEFAULT. + * @exception IllegalArgumentException if the supplied value is not + * one of the ISOLATION_ constants + * @see #ISOLATION_DEFAULT + */ + public final void setIsolationLevel(int isolationLevel) { + if (!constants.getValues(PREFIX_ISOLATION).contains(new Integer(isolationLevel))) { + throw new IllegalArgumentException("Only values of isolation constants allowed"); + } + this.isolationLevel = isolationLevel; + } + + public final int getIsolationLevel() { + return this.isolationLevel; + } + + /** + * Set the timeout to apply, as number of seconds. + * Default is TIMEOUT_DEFAULT (-1). + * @see #TIMEOUT_DEFAULT + */ + public final void setTimeout(int timeout) { + if (timeout < TIMEOUT_DEFAULT) { + throw new IllegalArgumentException("Timeout must be a positive integer or TIMEOUT_DEFAULT"); + } + this.timeout = timeout; + } + + public final int getTimeout() { + return this.timeout; + } + + /** + * Set whether to optimize as read-only transaction. + * Default is "false". + */ + public final void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public final boolean isReadOnly() { + return this.readOnly; + } + + /** + * Set the name of this transaction. Default is none. + *

This will be used as transaction name to be shown in a + * transaction monitor, if applicable (for example, WebLogic's). + */ + public final void setName(String name) { + this.name = name; + } + + public final String getName() { + return this.name; + } + + + /** + * This implementation compares the toString() results. + * @see #toString() + */ + public boolean equals(Object other) { + return (other instanceof TransactionDefinition && toString().equals(other.toString())); + } + + /** + * This implementation returns toString()'s hash code. + * @see #toString() + */ + public int hashCode() { + return toString().hashCode(); + } + + /** + * Return an identifying description for this transaction definition. + *

The format matches the one used by + * {@link org.springframework.transaction.interceptor.TransactionAttributeEditor}, + * to be able to feed toString results into bean properties of type + * {@link org.springframework.transaction.interceptor.TransactionAttribute}. + *

Has to be overridden in subclasses for correct equals + * and hashCode behavior. Alternatively, {@link #equals} + * and {@link #hashCode} can be overridden themselves. + * @see #getDefinitionDescription() + * @see org.springframework.transaction.interceptor.TransactionAttributeEditor + */ + public String toString() { + return getDefinitionDescription().toString(); + } + + /** + * Return an identifying description for this transaction definition. + *

Available to subclasses, for inclusion in their toString() result. + */ + protected final StringBuffer getDefinitionDescription() { + StringBuffer desc = new StringBuffer(); + desc.append(constants.toCode(new Integer(this.propagationBehavior), PREFIX_PROPAGATION)); + desc.append(','); + desc.append(constants.toCode(new Integer(this.isolationLevel), PREFIX_ISOLATION)); + if (this.timeout != TIMEOUT_DEFAULT) { + desc.append(','); + desc.append(PREFIX_TIMEOUT + this.timeout); + } + if (this.readOnly) { + desc.append(','); + desc.append(READ_ONLY_MARKER); + } + return desc; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java new file mode 100644 index 00000000000..e8ec782f2ea --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java @@ -0,0 +1,180 @@ +/* + * 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.support; + +import org.springframework.transaction.NestedTransactionNotSupportedException; +import org.springframework.transaction.SavepointManager; + +/** + * Default implementation of the {@link org.springframework.transaction.TransactionStatus} + * interface, used by {@link AbstractPlatformTransactionManager}. Based on the concept + * of an underlying "transaction object". + * + *

Holds all status information that {@link AbstractPlatformTransactionManager} + * needs internally, including a generic transaction object determined by the + * concrete transaction manager implementation. + * + *

Supports delegating savepoint-related methods to a transaction object + * that implements the {@link SavepointManager} interface. + * + *

NOTE: This is not intended to be used for other + * PlatformTransactionManager implementations, in particular not for + * mock transaction managers. Use {@link SimpleTransactionStatus} or + * a mock for the plain TransactionStatus interface instead. + * + * @author Juergen Hoeller + * @since 19.01.2004 + * @see AbstractPlatformTransactionManager + * @see org.springframework.transaction.SavepointManager + * @see #getTransaction + * @see #createSavepoint + * @see #rollbackToSavepoint + * @see #releaseSavepoint + * @see SimpleTransactionStatus + */ +public class DefaultTransactionStatus extends AbstractTransactionStatus { + + private final Object transaction; + + private final boolean newTransaction; + + private final boolean newSynchronization; + + private final boolean readOnly; + + private final boolean debug; + + private final Object suspendedResources; + + + /** + * Create a new DefaultTransactionStatus instance. + * @param transaction underlying transaction object that can hold + * state for the internal transaction implementation + * @param newTransaction if the transaction is new, + * else participating in an existing transaction + * @param newSynchronization if a new transaction synchronization + * has been opened for the given transaction + * @param readOnly whether the transaction is read-only + * @param debug should debug logging be enabled for the handling of this transaction? + * Caching it in here can prevent repeated calls to ask the logging system whether + * debug logging should be enabled. + * @param suspendedResources a holder for resources that have been suspended + * for this transaction, if any + */ + public DefaultTransactionStatus( + Object transaction, boolean newTransaction, boolean newSynchronization, + boolean readOnly, boolean debug, Object suspendedResources) { + + this.transaction = transaction; + this.newTransaction = newTransaction; + this.newSynchronization = newSynchronization; + this.readOnly = readOnly; + this.debug = debug; + this.suspendedResources = suspendedResources; + } + + /** + * Return the underlying transaction object. + */ + public Object getTransaction() { + return this.transaction; + } + + /** + * Return whether there is an actual transaction active. + */ + public boolean hasTransaction() { + return (this.transaction != null); + } + + public boolean isNewTransaction() { + return (hasTransaction() && this.newTransaction); + } + + /** + * Return if a new transaction synchronization has been opened + * for this transaction. + */ + public boolean isNewSynchronization() { + return this.newSynchronization; + } + + /** + * Return if this transaction is defined as read-only transaction. + */ + public boolean isReadOnly() { + return this.readOnly; + } + + /** + * Return whether the progress of this transaction is debugged. This is used + * by AbstractPlatformTransactionManager as an optimization, to prevent repeated + * calls to logger.isDebug(). Not really intended for client code. + */ + public boolean isDebug() { + return this.debug; + } + + /** + * Return the holder for resources that have been suspended for this transaction, + * if any. + */ + public Object getSuspendedResources() { + return this.suspendedResources; + } + + + //--------------------------------------------------------------------- + // Enable functionality through underlying transaction object + //--------------------------------------------------------------------- + + /** + * Determine the rollback-only flag via checking both the transaction object, + * provided that the latter implements the SmartTransactionObject interface. + *

Will return "true" if the transaction itself has been marked rollback-only + * by the transaction coordinator, for example in case of a timeout. + * @see SmartTransactionObject#isRollbackOnly + */ + public boolean isGlobalRollbackOnly() { + return ((this.transaction instanceof SmartTransactionObject) && + ((SmartTransactionObject) this.transaction).isRollbackOnly()); + } + + /** + * This implementation exposes the SavepointManager interface + * of the underlying transaction object, if any. + */ + protected SavepointManager getSavepointManager() { + if (!isTransactionSavepointManager()) { + throw new NestedTransactionNotSupportedException( + "Transaction object [" + getTransaction() + "] does not support savepoints"); + } + return (SavepointManager) getTransaction(); + } + + /** + * Return whether the underlying transaction implements the + * SavepointManager interface. + * @see #getTransaction + * @see org.springframework.transaction.SavepointManager + */ + public boolean isTransactionSavepointManager() { + return (getTransaction() instanceof SavepointManager); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolder.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolder.java new file mode 100644 index 00000000000..07ada793743 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2008 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.support; + +/** + * Generic interface to be implemented by resource holders. + * Allows Spring's transaction infrastructure to introspect + * and reset the holder when necessary. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see ResourceHolderSupport + * @see ResourceHolderSynchronization + */ +public interface ResourceHolder { + + /** + * Reset the transactional state of this holder. + */ + void reset(); + + /** + * Notify this holder that it has been unbound from transaction synchronization. + */ + void unbound(); + + /** + * Determine whether this holder is considere as 'void', + * i.e. as a leftover from a previous thread. + */ + boolean isVoid(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSupport.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSupport.java new file mode 100644 index 00000000000..4120e5b0b3b --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSupport.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.Date; + +import org.springframework.transaction.TransactionTimedOutException; + +/** + * Convenient base class for resource holders. + * + *

Features rollback-only support for nested transactions. + * Can expire after a certain number of seconds or milliseconds, + * to determine transactional timeouts. + * + * @author Juergen Hoeller + * @since 02.02.2004 + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin + * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout + */ +public abstract class ResourceHolderSupport implements ResourceHolder { + + private boolean synchronizedWithTransaction = false; + + private boolean rollbackOnly = false; + + private Date deadline; + + private int referenceCount = 0; + + private boolean isVoid = false; + + + /** + * Mark the resource as synchronized with a transaction. + */ + public void setSynchronizedWithTransaction(boolean synchronizedWithTransaction) { + this.synchronizedWithTransaction = synchronizedWithTransaction; + } + + /** + * Return whether the resource is synchronized with a transaction. + */ + public boolean isSynchronizedWithTransaction() { + return this.synchronizedWithTransaction; + } + + /** + * Mark the resource transaction as rollback-only. + */ + public void setRollbackOnly() { + this.rollbackOnly = true; + } + + /** + * Return whether the resource transaction is marked as rollback-only. + */ + public boolean isRollbackOnly() { + return this.rollbackOnly; + } + + /** + * Set the timeout for this object in seconds. + * @param seconds number of seconds until expiration + */ + public void setTimeoutInSeconds(int seconds) { + setTimeoutInMillis(seconds * 1000); + } + + /** + * Set the timeout for this object in milliseconds. + * @param millis number of milliseconds until expiration + */ + public void setTimeoutInMillis(long millis) { + this.deadline = new Date(System.currentTimeMillis() + millis); + } + + /** + * Return whether this object has an associated timeout. + */ + public boolean hasTimeout() { + return (this.deadline != null); + } + + /** + * Return the expiration deadline of this object. + * @return the deadline as Date object + */ + public Date getDeadline() { + return this.deadline; + } + + /** + * Return the time to live for this object in seconds. + * Rounds up eagerly, e.g. 9.00001 still to 10. + * @return number of seconds until expiration + * @throws TransactionTimedOutException if the deadline has already been reached + */ + public int getTimeToLiveInSeconds() { + double diff = ((double) getTimeToLiveInMillis()) / 1000; + int secs = (int) Math.ceil(diff); + checkTransactionTimeout(secs <= 0); + return secs; + } + + /** + * Return the time to live for this object in milliseconds. + * @return number of millseconds until expiration + * @throws TransactionTimedOutException if the deadline has already been reached + */ + public long getTimeToLiveInMillis() throws TransactionTimedOutException{ + if (this.deadline == null) { + throw new IllegalStateException("No timeout specified for this resource holder"); + } + long timeToLive = this.deadline.getTime() - System.currentTimeMillis(); + checkTransactionTimeout(timeToLive <= 0); + return timeToLive; + } + + /** + * Set the transaction rollback-only if the deadline has been reached, + * and throw a TransactionTimedOutException. + */ + private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException { + if (deadlineReached) { + setRollbackOnly(); + throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline); + } + } + + /** + * Increase the reference count by one because the holder has been requested + * (i.e. someone requested the resource held by it). + */ + public void requested() { + this.referenceCount++; + } + + /** + * Decrease the reference count by one because the holder has been released + * (i.e. someone released the resource held by it). + */ + public void released() { + this.referenceCount--; + } + + /** + * Return whether there are still open references to this holder. + */ + public boolean isOpen() { + return (this.referenceCount > 0); + } + + /** + * Clear the transactional state of this resource holder. + */ + public void clear() { + this.synchronizedWithTransaction = false; + this.rollbackOnly = false; + this.deadline = null; + } + + /** + * Reset this resource holder - transactional state as well as reference count. + */ + public void reset() { + clear(); + this.referenceCount = 0; + } + + public void unbound() { + this.isVoid = true; + } + + public boolean isVoid() { + return this.isVoid; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSynchronization.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSynchronization.java new file mode 100644 index 00000000000..d72fbfd65e7 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceHolderSynchronization.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2008 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.support; + +/** + * {@link TransactionSynchronization} implementation that manages a + * {@link ResourceHolder} bound through {@link TransactionSynchronizationManager}. + * + * @author Juergen Hoeller + * @since 2.5.5 + */ +public class ResourceHolderSynchronization implements TransactionSynchronization { + + private final ResourceHolder resourceHolder; + + private final Object resourceKey; + + private volatile boolean holderActive = true; + + + /** + * Create a new ResourceHolderSynchronization for the given holder. + * @param resourceHolder the ResourceHolder to manage + * @param resourceKey the key to bind the ResourceHolder for + * @see TransactionSynchronizationManager#bindResource + */ + public ResourceHolderSynchronization(ResourceHolder resourceHolder, Object resourceKey) { + this.resourceHolder = resourceHolder; + this.resourceKey = resourceKey; + } + + + public void suspend() { + if (this.holderActive) { + TransactionSynchronizationManager.unbindResource(this.resourceKey); + } + } + + public void resume() { + if (this.holderActive) { + TransactionSynchronizationManager.bindResource(this.resourceKey, this.resourceHolder); + } + } + + public void beforeCommit(boolean readOnly) { + } + + public void beforeCompletion() { + if (shouldUnbindAtCompletion()) { + TransactionSynchronizationManager.unbindResource(this.resourceKey); + this.holderActive = false; + if (shouldReleaseBeforeCompletion()) { + releaseResource(this.resourceHolder, this.resourceKey); + } + } + } + + public void afterCommit() { + if (!shouldReleaseBeforeCompletion()) { + processResourceAfterCommit(this.resourceHolder); + } + } + + public void afterCompletion(int status) { + if (shouldUnbindAtCompletion()) { + boolean releaseNecessary = false; + if (this.holderActive) { + // The thread-bound resource holder might not be available anymore, + // since afterCompletion might get called from a different thread. + this.holderActive = false; + TransactionSynchronizationManager.unbindResourceIfPossible(this.resourceKey); + this.resourceHolder.unbound(); + releaseNecessary = true; + } + else { + releaseNecessary = !shouldReleaseBeforeCompletion(); + } + if (releaseNecessary) { + releaseResource(this.resourceHolder, this.resourceKey); + } + } + else { + // Probably a pre-bound resource... + cleanupResource(this.resourceHolder, this.resourceKey, (status == STATUS_COMMITTED)); + } + this.resourceHolder.reset(); + } + + + /** + * Return whether this holder should be unbound at completion + * (or should rather be left bound to the thread after the transaction). + *

The default implementation returns true. + */ + protected boolean shouldUnbindAtCompletion() { + return true; + } + + /** + * Return whether this holder's resource should be released before + * transaction completion (true) or rather after + * transaction completion (false). + *

Note that resources will only be released when they are + * unbound from the thread ({@link #shouldUnbindAtCompletion()}). + *

The default implementation returns true. + * @see #releaseResource + */ + protected boolean shouldReleaseBeforeCompletion() { + return true; + } + + /** + * After-commit callback for the given resource holder. + * Only called when the resource hasn't been released yet + * ({@link #shouldReleaseBeforeCompletion()}). + * @param resourceHolder the resource holder to process + */ + protected void processResourceAfterCommit(ResourceHolder resourceHolder) { + } + + /** + * Release the given resource (after it has been unbound from the thread). + * @param resourceHolder the resource holder to process + * @param resourceKey the key that the ResourceHolder was bound for + */ + protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) { + } + + /** + * Perform a cleanup on the given resource (which is left bound to the thread). + * @param resourceHolder the resource holder to process + * @param resourceKey the key that the ResourceHolder was bound for + * @param committed whether the transaction has committed (true) + * or rolled back (false) + */ + protected void cleanupResource(ResourceHolder resourceHolder, Object resourceKey, boolean committed) { + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceTransactionManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceTransactionManager.java new file mode 100644 index 00000000000..039650d396f --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/ResourceTransactionManager.java @@ -0,0 +1,49 @@ +/* + * 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.support; + +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Extension of the {@link org.springframework.transaction.PlatformTransactionManager} + * interface, indicating a native resource transaction manager, operating on a single + * target resource. Such transaction managers differ from JTA transaction managers in + * that they do not use XA transaction enlistment for an open number of resources but + * rather focus on leveraging the native power and simplicity of a single target resource. + * + *

This interface is mainly used for abstract introspection of a transaction manager, + * giving clients a hint on what kind of transaction manager they have been given + * and on what concrete resource the transaction manager is operating on. + * + * @author Juergen Hoeller + * @since 2.0.4 + * @see TransactionSynchronizationManager + */ +public interface ResourceTransactionManager extends PlatformTransactionManager { + + /** + * Return the resource factory that this transaction manager operates on, + * e.g. a JDBC DataSource or a JMS ConnectionFactory. + *

This target resource factory is usually used as resource key for + * {@link TransactionSynchronizationManager}'s resource bindings per thread. + * @return the target resource factory (never null) + * @see TransactionSynchronizationManager#bindResource + * @see TransactionSynchronizationManager#getResource + */ + Object getResourceFactory(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SimpleTransactionStatus.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SimpleTransactionStatus.java new file mode 100644 index 00000000000..f36d6209aeb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SimpleTransactionStatus.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2006 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.support; + +/** + * A simple {@link org.springframework.transaction.TransactionStatus} + * implementation. + * + *

Derives from {@link AbstractTransactionStatus} and adds an explicit + * {@link #isNewTransaction() "newTransaction"} flag. + * + *

This class is not used by any of Spring's pre-built + * {@link org.springframework.transaction.PlatformTransactionManager} + * implementations. It is mainly provided as a start for custom transaction + * manager implementations and as a static mock for testing transactional + * code (either as part of a mock PlatformTransactionManager or + * as argument passed into a {@link TransactionCallback} to be tested). + * + * @author Juergen Hoeller + * @since 1.2.3 + * @see #SimpleTransactionStatus(boolean) + * @see TransactionCallback + */ +public class SimpleTransactionStatus extends AbstractTransactionStatus { + + private final boolean newTransaction; + + + /** + * Creates a new instance of the {@link SimpleTransactionStatus} class, + * indicating a new transaction. + */ + public SimpleTransactionStatus() { + this(true); + } + + /** + * Creates a new instance of the {@link SimpleTransactionStatus} class. + * @param newTransaction whether to indicate a new transaction + */ + public SimpleTransactionStatus(boolean newTransaction) { + this.newTransaction = newTransaction; + } + + + public boolean isNewTransaction() { + return newTransaction; + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java new file mode 100644 index 00000000000..2c7a00d8fd5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/SmartTransactionObject.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2005 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.support; + +/** + * Interface to be implemented by transaction objects that are able to + * return an internal rollback-only marker, typically from a another + * transaction that has participated and marked it as rollback-only. + * + *

Autodetected by DefaultTransactionStatus, to always return a + * current rollbackOnly flag even if not resulting from the current + * TransactionStatus. + * + * @author Juergen Hoeller + * @since 1.1 + * @see DefaultTransactionStatus#isRollbackOnly + */ +public interface SmartTransactionObject { + + /** + * Return whether the transaction is internally marked as rollback-only. + * Can, for example, check the JTA UserTransaction. + * @see javax.transaction.UserTransaction#getStatus + * @see javax.transaction.Status#STATUS_MARKED_ROLLBACK + */ + boolean isRollbackOnly(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallback.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallback.java new file mode 100644 index 00000000000..85f1f9efb10 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallback.java @@ -0,0 +1,55 @@ +/* + * 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.support; + +import org.springframework.transaction.TransactionStatus; + +/** + * Callback interface for transactional code. Used with {@link TransactionTemplate}'s + * execute method, often as anonymous class within a method implementation. + * + *

Typically used to assemble various calls to transaction-unaware data access + * services into a higher-level service method with transaction demarcation. As an + * alternative, consider the use of declarative transaction demarcation (e.g. through + * Spring's {@link org.springframework.transaction.annotation.Transactional} annotation). + * + * @author Juergen Hoeller + * @since 17.03.2003 + * @see TransactionTemplate + * @see CallbackPreferringPlatformTransactionManager + */ +public interface TransactionCallback { + + /** + * Gets called by {@link TransactionTemplate#execute} within a transactional context. + * Does not need to care about transactions itself, although it can retrieve + * and influence the status of the current transaction via the given status + * object, e.g. setting rollback-only. + * + *

Allows for returning a result object created within the transaction, i.e. + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as application exception that enforces a rollback. + * An exception gets propagated to the caller of the template. + * + * @param status associated transaction status + * @return a result object, or null + * @see TransactionTemplate#execute + * @see CallbackPreferringPlatformTransactionManager#execute + */ + Object doInTransaction(TransactionStatus status); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallbackWithoutResult.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallbackWithoutResult.java new file mode 100644 index 00000000000..866ed94ce4c --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionCallbackWithoutResult.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2005 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.support; + +import org.springframework.transaction.TransactionStatus; + +/** + * Simple convenience class for TransactionCallback implementation. + * Allows for implementing a doInTransaction version without result, + * i.e. without the need for a return statement. + * + * @author Juergen Hoeller + * @since 28.03.2003 + * @see TransactionTemplate + */ +public abstract class TransactionCallbackWithoutResult implements TransactionCallback { + + public final Object doInTransaction(TransactionStatus status) { + doInTransactionWithoutResult(status); + return null; + } + + /** + * Gets called by TransactionTemplate.execute within a transactional context. + * Does not need to care about transactions itself, although it can retrieve + * and influence the status of the current transaction via the given status + * object, e.g. setting rollback-only. + * + *

A RuntimeException thrown by the callback is treated as application + * exception that enforces a rollback. An exception gets propagated to the + * caller of the template. + * + *

Note when using JTA: JTA transactions only work with transactional + * JNDI resources, so implementations need to use such resources if they + * want transaction support. + * + * @param status associated transaction status + * @see TransactionTemplate#execute + */ + protected abstract void doInTransactionWithoutResult(TransactionStatus status); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionOperations.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionOperations.java new file mode 100644 index 00000000000..2361e029a88 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionOperations.java @@ -0,0 +1,44 @@ +/* + * 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.support; + +import org.springframework.transaction.TransactionException; + +/** + * Interface specifying basic transaction execution operations. + * Implemented by {@link TransactionTemplate}. Not often used directly, + * but a useful option to enhance testability, as it can easily be + * mocked or stubbed. + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public interface TransactionOperations { + + /** + * Execute the action specified by the given callback object within a transaction. + *

Allows for returning a result object created within the transaction, that is, + * a domain object or a collection of domain objects. A RuntimeException thrown + * by the callback is treated as a fatal exception that enforces a rollback. + * Such an exception gets propagated to the caller of the template. + * @param action the callback object that specifies the transactional action + * @return a result object returned by the callback, or null if none + * @throws TransactionException in case of initialization, rollback, or system errors + */ + Object execute(TransactionCallback action) throws TransactionException; + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronization.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronization.java new file mode 100644 index 00000000000..499ba022f65 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronization.java @@ -0,0 +1,129 @@ +/* + * 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.support; + +/** + * Interface for transaction synchronization callbacks. + * Supported by AbstractPlatformTransactionManager. + * + *

TransactionSynchronization implementations can implement the Ordered interface + * to influence their execution order. A synchronization that does not implement the + * Ordered interface is appended to the end of the synchronization chain. + * + *

System synchronizations performed by Spring itself use specific order values, + * allowing for fine-grained interaction with their execution order (if necessary). + * + * @author Juergen Hoeller + * @since 02.06.2003 + * @see TransactionSynchronizationManager + * @see AbstractPlatformTransactionManager + * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER + * @see org.springframework.orm.hibernate3.SessionFactoryUtils#SESSION_SYNCHRONIZATION_ORDER + */ +public interface TransactionSynchronization { + + /** Completion status in case of proper commit */ + int STATUS_COMMITTED = 0; + + /** Completion status in case of proper rollback */ + int STATUS_ROLLED_BACK = 1; + + /** Completion status in case of heuristic mixed completion or system errors */ + int STATUS_UNKNOWN = 2; + + + /** + * Suspend this synchronization. + * Supposed to unbind resources from TransactionSynchronizationManager if managing any. + * @see TransactionSynchronizationManager#unbindResource + */ + void suspend(); + + /** + * Resume this synchronization. + * Supposed to rebind resources to TransactionSynchronizationManager if managing any. + * @see TransactionSynchronizationManager#bindResource + */ + void resume(); + + /** + * Invoked before transaction commit (before "beforeCompletion"). + * Can e.g. flush transactional O/R Mapping sessions to the database. + *

This callback does not mean that the transaction will actually be committed. + * A rollback decision can still occur after this method has been called. This callback + * is rather meant to perform work that's only relevant if a commit still has a chance + * to happen, such as flushing SQL statements to the database. + *

Note that exceptions will get propagated to the commit caller and cause a + * rollback of the transaction. + * @param readOnly whether the transaction is defined as read-only transaction + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + * @see #beforeCompletion + */ + void beforeCommit(boolean readOnly); + + /** + * Invoked before transaction commit/rollback. + * Can perform resource cleanup before transaction completion. + *

This method will be invoked after beforeCommit, even when + * beforeCommit threw an exception. This callback allows for + * closing resources before transaction completion, for any outcome. + * @throws RuntimeException in case of errors; will be logged but not propagated + * (note: do not throw TransactionException subclasses here!) + * @see #beforeCommit + * @see #afterCompletion + */ + void beforeCompletion(); + + /** + * Invoked after transaction commit. Can perform further operations right + * after the main transaction has successfully committed. + *

Can e.g. commit further operations that are supposed to follow on a successful + * commit of the main transaction, like confirmation messages or emails. + *

NOTE: The transaction will have been committed already, but the + * transactional resources might still be active and accessible. As a consequence, + * any data access code triggered at this point will still "participate" in the + * original transaction, allowing to perform some cleanup (with no commit following + * anymore!), unless it explicitly declares that it needs to run in a separate + * transaction. Hence: Use PROPAGATION_REQUIRES_NEW for any + * transactional operation that is called from here. + * @throws RuntimeException in case of errors; will be propagated to the caller + * (note: do not throw TransactionException subclasses here!) + */ + void afterCommit(); + + /** + * Invoked after transaction commit/rollback. + * Can perform resource cleanup after transaction completion. + *

NOTE: The transaction will have been committed or rolled back already, + * but the transactional resources might still be active and accessible. As a + * consequence, any data access code triggered at this point will still "participate" + * in the original transaction, allowing to perform some cleanup (with no commit + * following anymore!), unless it explicitly declares that it needs to run in a + * separate transaction. Hence: Use PROPAGATION_REQUIRES_NEW + * for any transactional operation that is called from here. + * @param status completion status according to the STATUS_* constants + * @throws RuntimeException in case of errors; will be logged but not propagated + * (note: do not throw TransactionException subclasses here!) + * @see #STATUS_COMMITTED + * @see #STATUS_ROLLED_BACK + * @see #STATUS_UNKNOWN + * @see #beforeCompletion + */ + void afterCompletion(int status); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationAdapter.java new file mode 100644 index 00000000000..7a1c4fd13f6 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationAdapter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2006 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.support; + +import org.springframework.core.Ordered; + +/** + * Simple {@link TransactionSynchronization} adapter containing empty + * method implementations, for easier overriding of single methods. + * + *

Also implements the {@link Ordered} interface to enable the execution + * order of synchronizations to be controlled declaratively. The default + * {@link #getOrder() order} is {@link Ordered#LOWEST_PRECEDENCE}, indicating + * late execution; return a lower value for earlier execution. + * + * @author Juergen Hoeller + * @since 22.01.2004 + */ +public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered { + + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + public void suspend() { + } + + public void resume() { + } + + public void beforeCommit(boolean readOnly) { + } + + public void beforeCompletion() { + } + + public void afterCommit() { + } + + public void afterCompletion(int status) { + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java new file mode 100644 index 00000000000..0d82deef071 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -0,0 +1,458 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.NamedThreadLocal; +import org.springframework.core.OrderComparator; +import org.springframework.util.Assert; + +/** + * Central helper that manages resources and transaction synchronizations per thread. + * To be used by resource management code but not by typical application code. + * + *

Supports one resource per key without overwriting, that is, a resource needs + * to be removed before a new one can be set for the same key. + * Supports a list of transaction synchronizations if synchronization is active. + * + *

Resource management code should check for thread-bound resources, e.g. JDBC + * Connections or Hibernate Sessions, via getResource. Such code is + * normally not supposed to bind resources to threads, as this is the responsibility + * of transaction managers. A further option is to lazily bind on first use if + * transaction synchronization is active, for performing transactions that span + * an arbitrary number of resources. + * + *

Transaction synchronization must be activated and deactivated by a transaction + * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}. + * This is automatically supported by {@link AbstractPlatformTransactionManager}, + * and thus by all standard Spring transaction managers, such as + * {@link org.springframework.transaction.jta.JtaTransactionManager} and + * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}. + * + *

Resource management code should only register synchronizations when this + * manager is active, which can be checked via {@link #isSynchronizationActive}; + * it should perform immediate resource cleanup else. If transaction synchronization + * isn't active, there is either no current transaction, or the transaction manager + * doesn't support transaction synchronization. + * + *

Synchronization is for example used to always return the same resources + * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for + * any given DataSource or SessionFactory, respectively. + * + * @author Juergen Hoeller + * @since 02.06.2003 + * @see #isSynchronizationActive + * @see #registerSynchronization + * @see TransactionSynchronization + * @see AbstractPlatformTransactionManager#setTransactionSynchronization + * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection + */ +public abstract class TransactionSynchronizationManager { + + private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); + + private static final Comparator synchronizationComparator = new OrderComparator(); + + private static final ThreadLocal resources = + new NamedThreadLocal("Transactional resources"); + + private static final ThreadLocal synchronizations = + new NamedThreadLocal("Transaction synchronizations"); + + private static final ThreadLocal currentTransactionName = + new NamedThreadLocal("Current transaction name"); + + private static final ThreadLocal currentTransactionReadOnly = + new NamedThreadLocal("Current transaction read-only status"); + + private static final ThreadLocal currentTransactionIsolationLevel = + new NamedThreadLocal("Current transaction isolation level"); + + private static final ThreadLocal actualTransactionActive = + new NamedThreadLocal("Actual transaction active"); + + + //------------------------------------------------------------------------- + // Management of transaction-associated resource handles + //------------------------------------------------------------------------- + + /** + * Return all resources that are bound to the current thread. + *

Mainly for debugging purposes. Resource managers should always invoke + * hasResource for a specific resource key that they are interested in. + * @return a Map with resource keys (usually the resource factory) and resource + * values (usually the active resource object), or an empty Map if there are + * currently no resources bound + * @see #hasResource + */ + public static Map getResourceMap() { + Map map = (Map) resources.get(); + return (map != null ? Collections.unmodifiableMap(map) : Collections.EMPTY_MAP); + } + + /** + * Check if there is a resource for the given key bound to the current thread. + * @param key the key to check (usually the resource factory) + * @return if there is a value bound to the current thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static boolean hasResource(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doGetResource(actualKey); + return (value != null); + } + + /** + * Retrieve a resource for the given key that is bound to the current thread. + * @param key the key to check (usually the resource factory) + * @return a value bound to the current thread (usually the active + * resource object), or null if none + * @see ResourceTransactionManager#getResourceFactory() + */ + public static Object getResource(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doGetResource(actualKey); + if (value != null && logger.isTraceEnabled()) { + logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + + Thread.currentThread().getName() + "]"); + } + return value; + } + + /** + * Actually check the value of the resource that is bound for the given key. + */ + private static Object doGetResource(Object actualKey) { + Map map = (Map) resources.get(); + if (map == null) { + return null; + } + Object value = map.get(actualKey); + // Transparently remove ResourceHolder that was marked as void... + if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { + map.remove(actualKey); + value = null; + } + return value; + } + + /** + * Bind the given resource for the given key to the current thread. + * @param key the key to bind the value to (usually the resource factory) + * @param value the value to bind (usually the active resource object) + * @throws IllegalStateException if there is already a value bound to the thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static void bindResource(Object key, Object value) throws IllegalStateException { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Assert.notNull(value, "Value must not be null"); + Map map = (Map) resources.get(); + // set ThreadLocal Map if none found + if (map == null) { + map = new HashMap(); + resources.set(map); + } + if (map.put(actualKey, value) != null) { + throw new IllegalStateException("Already value [" + map.get(actualKey) + "] for key [" + + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); + } + if (logger.isTraceEnabled()) { + logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + + Thread.currentThread().getName() + "]"); + } + } + + /** + * Unbind a resource for the given key from the current thread. + * @param key the key to unbind (usually the resource factory) + * @return the previously bound value (usually the active resource object) + * @throws IllegalStateException if there is no value bound to the thread + * @see ResourceTransactionManager#getResourceFactory() + */ + public static Object unbindResource(Object key) throws IllegalStateException { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doUnbindResource(actualKey); + if (value == null) { + throw new IllegalStateException( + "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); + } + return value; + } + + /** + * Unbind a resource for the given key from the current thread. + * @param key the key to unbind (usually the resource factory) + * @return the previously bound value, or null if none bound + */ + public static Object unbindResourceIfPossible(Object key) { + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + return doUnbindResource(actualKey); + } + + /** + * Actually remove the value of the resource that is bound for the given key. + */ + private static Object doUnbindResource(Object actualKey) { + Map map = (Map) resources.get(); + if (map == null) { + return null; + } + Object value = map.remove(actualKey); + // Remove entire ThreadLocal if empty... + if (map.isEmpty()) { + resources.set(null); + } + if (value != null && logger.isTraceEnabled()) { + logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + + Thread.currentThread().getName() + "]"); + } + return value; + } + + + //------------------------------------------------------------------------- + // Management of transaction synchronizations + //------------------------------------------------------------------------- + + /** + * Return if transaction synchronization is active for the current thread. + * Can be called before register to avoid unnecessary instance creation. + * @see #registerSynchronization + */ + public static boolean isSynchronizationActive() { + return (synchronizations.get() != null); + } + + /** + * Activate transaction synchronization for the current thread. + * Called by a transaction manager on transaction begin. + * @throws IllegalStateException if synchronization is already active + */ + public static void initSynchronization() throws IllegalStateException { + if (isSynchronizationActive()) { + throw new IllegalStateException("Cannot activate transaction synchronization - already active"); + } + logger.trace("Initializing transaction synchronization"); + synchronizations.set(new LinkedList()); + } + + /** + * Register a new transaction synchronization for the current thread. + * Typically called by resource management code. + *

Note that synchronizations can implement the + * {@link org.springframework.core.Ordered} interface. + * They will be executed in an order according to their order value (if any). + * @param synchronization the synchronization object to register + * @throws IllegalStateException if transaction synchronization is not active + * @see org.springframework.core.Ordered + */ + public static void registerSynchronization(TransactionSynchronization synchronization) + throws IllegalStateException { + + Assert.notNull(synchronization, "TransactionSynchronization must not be null"); + if (!isSynchronizationActive()) { + throw new IllegalStateException("Transaction synchronization is not active"); + } + List synchs = (List) synchronizations.get(); + synchs.add(synchronization); + } + + /** + * Return an unmodifiable snapshot list of all registered synchronizations + * for the current thread. + * @return unmodifiable List of TransactionSynchronization instances + * @throws IllegalStateException if synchronization is not active + * @see TransactionSynchronization + */ + public static List getSynchronizations() throws IllegalStateException { + if (!isSynchronizationActive()) { + throw new IllegalStateException("Transaction synchronization is not active"); + } + List synchs = (List) synchronizations.get(); + // Sort lazily here, not in registerSynchronization. + Collections.sort(synchs, synchronizationComparator); + // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions + // while iterating and invoking synchronization callbacks that in turn + // might register further synchronizations. + return Collections.unmodifiableList(new ArrayList(synchs)); + } + + /** + * Deactivate transaction synchronization for the current thread. + * Called by the transaction manager on transaction cleanup. + * @throws IllegalStateException if synchronization is not active + */ + public static void clearSynchronization() throws IllegalStateException { + if (!isSynchronizationActive()) { + throw new IllegalStateException("Cannot deactivate transaction synchronization - not active"); + } + logger.trace("Clearing transaction synchronization"); + synchronizations.set(null); + } + + + //------------------------------------------------------------------------- + // Exposure of transaction characteristics + //------------------------------------------------------------------------- + + /** + * Expose the name of the current transaction, if any. + * Called by the transaction manager on transaction begin and on cleanup. + * @param name the name of the transaction, or null to reset it + * @see org.springframework.transaction.TransactionDefinition#getName() + */ + public static void setCurrentTransactionName(String name) { + currentTransactionName.set(name); + } + + /** + * Return the name of the current transaction, or null if none set. + * To be called by resource management code for optimizations per use case, + * for example to optimize fetch strategies for specific named transactions. + * @see org.springframework.transaction.TransactionDefinition#getName() + */ + public static String getCurrentTransactionName() { + return (String) currentTransactionName.get(); + } + + /** + * Expose a read-only flag for the current transaction. + * Called by the transaction manager on transaction begin and on cleanup. + * @param readOnly true to mark the current transaction + * as read-only; false to reset such a read-only marker + * @see org.springframework.transaction.TransactionDefinition#isReadOnly() + */ + public static void setCurrentTransactionReadOnly(boolean readOnly) { + currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null); + } + + /** + * Return whether the current transaction is marked as read-only. + * To be called by resource management code when preparing a newly + * created resource (for example, a Hibernate Session). + *

Note that transaction synchronizations receive the read-only flag + * as argument for the beforeCommit callback, to be able + * to suppress change detection on commit. The present method is meant + * to be used for earlier read-only checks, for example to set the + * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront. + * @see org.springframework.transaction.TransactionDefinition#isReadOnly() + * @see TransactionSynchronization#beforeCommit(boolean) + * @see org.hibernate.Session#flush + * @see org.hibernate.Session#setFlushMode + * @see org.hibernate.FlushMode#NEVER + */ + public static boolean isCurrentTransactionReadOnly() { + return (currentTransactionReadOnly.get() != null); + } + + /** + * Expose an isolation level for the current transaction. + * Called by the transaction manager on transaction begin and on cleanup. + * @param isolationLevel the isolation level to expose, according to the + * JDBC Connection constants (equivalent to the corresponding Spring + * TransactionDefinition constants), or null to reset it + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE + * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() + */ + public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) { + currentTransactionIsolationLevel.set(isolationLevel); + } + + /** + * Return the isolation level for the current transaction, if any. + * To be called by resource management code when preparing a newly + * created resource (for example, a JDBC Connection). + * @return the currently exposed isolation level, according to the + * JDBC Connection constants (equivalent to the corresponding Spring + * TransactionDefinition constants), or null if none + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ + * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE + * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() + */ + public static Integer getCurrentTransactionIsolationLevel() { + return (Integer) currentTransactionIsolationLevel.get(); + } + + /** + * Expose whether there currently is an actual transaction active. + * Called by the transaction manager on transaction begin and on cleanup. + * @param active true to mark the current thread as being associated + * with an actual transaction; false to reset that marker + */ + public static void setActualTransactionActive(boolean active) { + actualTransactionActive.set(active ? Boolean.TRUE : null); + } + + /** + * Return whether there currently is an actual transaction active. + * This indicates whether the current thread is associated with an actual + * transaction rather than just with active transaction synchronization. + *

To be called by resource management code that wants to discriminate + * between active transaction synchronization (with or without backing + * resource transaction; also on PROPAGATION_SUPPORTS) and an actual + * transaction being active (with backing resource transaction; + * on PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etc). + * @see #isSynchronizationActive() + */ + public static boolean isActualTransactionActive() { + return (actualTransactionActive.get() != null); + } + + + /** + * Clear the entire transaction synchronization state for the current thread: + * registered synchronizations as well as the various transaction characteristics. + * @see #clearSynchronization() + * @see #setCurrentTransactionName + * @see #setCurrentTransactionReadOnly + * @see #setCurrentTransactionIsolationLevel + * @see #setActualTransactionActive + */ + public static void clear() { + clearSynchronization(); + setCurrentTransactionName(null); + setCurrentTransactionReadOnly(false); + setCurrentTransactionIsolationLevel(null); + setActualTransactionActive(false); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationUtils.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationUtils.java new file mode 100644 index 00000000000..8175b8f0bdf --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionSynchronizationUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.InfrastructureProxy; +import org.springframework.util.Assert; + +/** + * Utility methods for triggering specific {@link TransactionSynchronization} + * callback methods on all currently registered synchronizations. + * + * @author Juergen Hoeller + * @since 2.0 + * @see TransactionSynchronization + * @see TransactionSynchronizationManager#getSynchronizations() + */ +public abstract class TransactionSynchronizationUtils { + + private static final Log logger = LogFactory.getLog(TransactionSynchronizationUtils.class); + + + /** + * Check whether the given resource transaction managers refers to the given + * (underlying) resource factory. + * @see ResourceTransactionManager#getResourceFactory() + * @see org.springframework.core.InfrastructureProxy#getWrappedObject() + */ + public static boolean sameResourceFactory(ResourceTransactionManager tm, Object resourceFactory) { + return unwrapResourceIfNecessary(tm.getResourceFactory()).equals(unwrapResourceIfNecessary(resourceFactory)); + } + + /** + * Unwrap the given resource handle if necessary; otherwise return + * the given handle as-is. + * @see org.springframework.core.InfrastructureProxy#getWrappedObject() + */ + static Object unwrapResourceIfNecessary(Object resource) { + Assert.notNull(resource, "Resource must not be null"); + return (resource instanceof InfrastructureProxy ? ((InfrastructureProxy) resource).getWrappedObject() : resource); + } + + + /** + * Trigger beforeCommit callbacks on all currently registered synchronizations. + * @param readOnly whether the transaction is defined as read-only transaction + * @throws RuntimeException if thrown by a beforeCommit callback + * @see TransactionSynchronization#beforeCommit(boolean) + */ + public static void triggerBeforeCommit(boolean readOnly) { + for (Iterator it = TransactionSynchronizationManager.getSynchronizations().iterator(); it.hasNext();) { + TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); + synchronization.beforeCommit(readOnly); + } + } + + /** + * Trigger beforeCompletion callbacks on all currently registered synchronizations. + * @see TransactionSynchronization#beforeCompletion() + */ + public static void triggerBeforeCompletion() { + for (Iterator it = TransactionSynchronizationManager.getSynchronizations().iterator(); it.hasNext();) { + TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); + try { + synchronization.beforeCompletion(); + } + catch (Throwable tsex) { + logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex); + } + } + } + + /** + * Trigger afterCommit callbacks on all currently registered synchronizations. + * @throws RuntimeException if thrown by a afterCommit callback + * @see TransactionSynchronizationManager#getSynchronizations() + * @see TransactionSynchronization#afterCommit() + */ + public static void triggerAfterCommit() { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + invokeAfterCommit(synchronizations); + } + + /** + * Actually invoke the afterCommit methods of the + * given Spring TransactionSynchronization objects. + * @param synchronizations List of TransactionSynchronization objects + * @see TransactionSynchronization#afterCommit() + */ + public static void invokeAfterCommit(List synchronizations) { + if (synchronizations != null) { + for (Iterator it = synchronizations.iterator(); it.hasNext();) { + TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); + try { + synchronization.afterCommit(); + } + catch (AbstractMethodError tserr) { + if (logger.isDebugEnabled()) { + logger.debug("Spring 2.0's TransactionSynchronization.afterCommit method not implemented in " + + "synchronization class [" + synchronization.getClass().getName() + "]", tserr); + } + } + } + } + } + + /** + * Trigger afterCompletion callbacks on all currently registered synchronizations. + * @see TransactionSynchronizationManager#getSynchronizations() + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + public static void triggerAfterCompletion(int completionStatus) { + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + invokeAfterCompletion(synchronizations, completionStatus); + } + + /** + * Actually invoke the afterCompletion methods of the + * given Spring TransactionSynchronization objects. + * @param synchronizations List of TransactionSynchronization objects + * @param completionStatus the completion status according to the + * constants in the TransactionSynchronization interface + * @see TransactionSynchronization#afterCompletion(int) + * @see TransactionSynchronization#STATUS_COMMITTED + * @see TransactionSynchronization#STATUS_ROLLED_BACK + * @see TransactionSynchronization#STATUS_UNKNOWN + */ + public static void invokeAfterCompletion(List synchronizations, int completionStatus) { + if (synchronizations != null) { + for (Iterator it = synchronizations.iterator(); it.hasNext();) { + TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); + try { + synchronization.afterCompletion(completionStatus); + } + catch (Throwable tsex) { + logger.error("TransactionSynchronization.afterCompletion threw exception", tsex); + } + } + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionTemplate.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionTemplate.java new file mode 100644 index 00000000000..ec26db77a88 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/TransactionTemplate.java @@ -0,0 +1,171 @@ +/* + * 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSystemException; + +/** + * Template class that simplifies programmatic transaction demarcation and + * transaction exception handling. + * + *

The central method is {@link #execute}, supporting transactional code that + * implements the {@link TransactionCallback} interface. This template handles + * the transaction lifecycle and possible exceptions such that neither the + * TransactionCallback implementation nor the calling code needs to explicitly + * handle transactions. + * + *

Typical usage: Allows for writing low-level data access objects that use + * resources such as JDBC DataSources but are not transaction-aware themselves. + * Instead, they can implicitly participate in transactions handled by higher-level + * application services utilizing this class, making calls to the low-level + * services via an inner-class callback object. + * + *

Can be used within a service implementation via direct instantiation with + * a transaction manager reference, or get prepared in an application context + * and passed to services as bean reference. Note: The transaction manager should + * always be configured as bean in the application context: in the first case given + * to the service directly, in the second case given to the prepared template. + * + *

Supports setting the propagation behavior and the isolation level by name, + * for convenient configuration in context definitions. + * + * @author Juergen Hoeller + * @since 17.03.2003 + * @see #execute + * @see #setTransactionManager + * @see org.springframework.transaction.PlatformTransactionManager + */ +public class TransactionTemplate extends DefaultTransactionDefinition + implements TransactionOperations, InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private PlatformTransactionManager transactionManager; + + + /** + * Construct a new TransactionTemplate for bean usage. + *

Note: The PlatformTransactionManager needs to be set before + * any execute calls. + * @see #setTransactionManager + */ + public TransactionTemplate() { + } + + /** + * Construct a new TransactionTemplate using the given transaction manager. + * @param transactionManager the transaction management strategy to be used + */ + public TransactionTemplate(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Construct a new TransactionTemplate using the given transaction manager, + * taking its default settings from the given transaction definition. + * @param transactionManager the transaction management strategy to be used + * @param transactionDefinition the transaction definition to copy the + * default settings from. Local properties can still be set to change values. + */ + public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) { + super(transactionDefinition); + this.transactionManager = transactionManager; + } + + + /** + * Set the transaction management strategy to be used. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Return the transaction management strategy to be used. + */ + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + + public void afterPropertiesSet() { + if (this.transactionManager == null) { + throw new IllegalArgumentException("Property 'transactionManager' is required"); + } + } + + + public Object execute(TransactionCallback action) throws TransactionException { + if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { + return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); + } + else { + TransactionStatus status = this.transactionManager.getTransaction(this); + Object result = null; + try { + result = action.doInTransaction(status); + } + catch (RuntimeException ex) { + // Transactional code threw application exception -> rollback + rollbackOnException(status, ex); + throw ex; + } + catch (Error err) { + // Transactional code threw error -> rollback + rollbackOnException(status, err); + throw err; + } + this.transactionManager.commit(status); + return result; + } + } + + /** + * Perform a rollback, handling rollback exceptions properly. + * @param status object representing the transaction + * @param ex the thrown application exception or error + * @throws TransactionException in case of a rollback error + */ + private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { + logger.debug("Initiating transaction rollback on application exception", ex); + try { + this.transactionManager.rollback(status); + } + catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } + catch (RuntimeException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + catch (Error err) { + logger.error("Application exception overridden by rollback error", ex); + throw err; + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/support/package.html b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/package.html new file mode 100644 index 00000000000..8e8e16c81e5 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/support/package.html @@ -0,0 +1,9 @@ + + + +Support classes for the org.springframework.transaction package. +Provides an abstract base class for transaction manager implementations, +and a template plus callback for transaction demarcation. + + + diff --git a/org.springframework.transaction/src/main/java/overview.html b/org.springframework.transaction/src/main/java/overview.html new file mode 100644 index 00000000000..1eb7a2e8c19 --- /dev/null +++ b/org.springframework.transaction/src/main/java/overview.html @@ -0,0 +1,7 @@ + + +

+The Spring Data Binding framework, an internal library used by Spring Web Flow. +

+ + \ No newline at end of file diff --git a/org.springframework.transaction/src/test/resources/log4j.xml b/org.springframework.transaction/src/test/resources/log4j.xml new file mode 100644 index 00000000000..767b96d6206 --- /dev/null +++ b/org.springframework.transaction/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.transaction/template.mf b/org.springframework.transaction/template.mf new file mode 100644 index 00000000000..7a415657c1c --- /dev/null +++ b/org.springframework.transaction/template.mf @@ -0,0 +1,26 @@ +Bundle-SymbolicName: org.springframework.transaction +Bundle-Name: Spring Transaction +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Package: + org.springframework.stereotype;version="[2.5.5.A, 2.5.5.A]" +Import-Template: + com.ibm.wsspi.uow.*;version="[6.0.2.17, 6.1.0)";resolution:=optional, + javax.ejb.*;version="[3.0.0, 4.0.0)";resolution:=optional, + javax.resource.*;version="[1.5.0, 2.0.0)";resolution:=optional, + javax.transaction.*;version="[1.0.1, 2.0.0)";resolution:=optional, + org.aopalliance.*;version="[1.0.0, 2.0.0)", + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.objectweb.jotm.*;version="[2.0.10, 3.0.0)";resolution:=optional, + org.springframework.aop.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.beans.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.context.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.core.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.jndi.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional, + org.springframework.metadata.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.scheduling.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional, + org.springframework.util.*;version="[2.5.5.A, 2.5.5.A]" +Unversioned-Imports: + javax.management.*, + javax.naming.*, + org.w3c.dom.*