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 @@
+
+
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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 @@
+
+
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 @@
+
+
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 @@
+
+
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 theorg.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 @@
+
+
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 @@
+
+
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 @@
+
+
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 This implementation delegates to configured
+ * {@link TransactionAnnotationParser TransactionAnnotationParsers}
+ * for parsing known annotations into Spring's metadata attribute class.
+ * Returns 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 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 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 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 This essentially parses a known transaction annotation into Spring's
+ * metadata attribute class. Returns 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;
+
+ /**
+ * Defaults to 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 extends Throwable>[] 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 extends Throwable>[] 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 @@
+
+ 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
+ * ' 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
+ * 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 Must not produce same key for overloaded methods.
+ * Must produce same key for different instances of the same method.
+ * @param method the method (never The default implementation returns 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 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 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 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 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 This can be a substring, with no wildcard support at present. A value
+ * of "ServletException" would match
+ * 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
+ * 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 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
+ * Uses the Strategy design pattern. A
+ * A transaction aspect is serializable if it's
+ * A TransactionInfo will be returned even if no transaction was created.
+ * The To find out about specific transaction characteristics, consider using
+ * TransactionSynchronizationManager's 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 Call this in all cases: exception or normal return!
+ * @param txInfo information about the current transaction (may be where only propagation code is required. For example:
+ * 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 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: For example: 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 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 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.
+ *
+ * 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 @@
+
+ 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:
+ *
+ * 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 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 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 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 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 Called by 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 The default implementation simply returns The default implementation simply returns The default implementation simply returns 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
+ * 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 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
+ * 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
+ * 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 Supports a per-transaction isolation level through OC4J's corresponding
+ * 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 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 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 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 Needs to invoke the Spring synchronization's 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 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 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 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 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:
+ *
+ * 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 This transaction manager implementation derives from Spring's standard
+ * {@link JtaTransactionManager}, inheriting the capability to support programmatic
+ * transaction demarcation via 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 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 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
+ * Returns 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 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 Default is "false".
+ * @see #doCommit
+ * @see #doRollback
+ */
+ public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) {
+ this.rollbackOnCommitFailure = rollbackOnCommitFailure;
+ }
+
+ /**
+ * Return whether To be called by this abstract manager itself, or by special implementations
+ * of the 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 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 Default is Subclasses can override this to return 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
+ * The default implementation throws a TransactionSuspensionNotSupportedException,
+ * assuming that transaction suspension is generally not supported.
+ * @param transaction transaction object returned by The default implementation throws a TransactionSuspensionNotSupportedException,
+ * assuming that transaction suspension is generally not supported.
+ * @param transaction transaction object returned by 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
+ * If this method returns "true" but the 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 Called after Should not throw any exceptions but just issue warnings on errors.
+ * @param transaction transaction object returned by 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 This implementation always returns 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 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 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 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 The format matches the one used by
+ * {@link org.springframework.transaction.interceptor.TransactionAttributeEditor},
+ * to be able to feed Has to be overridden in subclasses for correct Available to subclasses, for inclusion in their 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 Note that resources will only be released when they are
+ * unbound from the thread ({@link #shouldUnbindAtCompletion()}).
+ * The default implementation returns 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 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 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
+ * 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 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 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 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 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 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 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
+ * 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 Note that transaction synchronizations receive the read-only flag
+ * as argument for the 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 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
+The Spring Data Binding framework, an internal library used by Spring Web Flow.
+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 LinkedHashSetnull if it's not transactional.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ *
+ * true if the transaction is read-only.
+ * 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.
+ * 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 NamespaceHandler allowing for the configuration of
+ * declarative transaction management using either XML or using annotations.
+ *
+ * <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 @@
+
+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.
+ * 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?
+ * 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.
+ *
+ * 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).
+ * 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.
+ * 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.
+ *
+ * RollbackRuleAttribute class.
+ * 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.
+ * javax.servlet.ServletException and subclasses, for example.
+ * 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).
+ *
+ * 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.
+ *
+ * TransactionAttribute, the exposed name will be the
+ * fully-qualified class name + "." + method name
+ * (by default).
+ *
+ * PlatformTransactionManager implementation will perform the
+ * actual transaction management, and a TransactionAttributeSource
+ * is used for determining transaction definitions.
+ *
+ * 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.
+ * TransactionInfo.hasTransaction() method can be used to query this.
+ * 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".
+ * 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.
+ * 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
+ * PROPAGATION_MANDATORY,ISOLATION_DEFAULT
+ *
+ * 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.
+ *
+ *
+ * FQCN.methodName=<transaction attribute string>
+ *
+ *
+ * com.mycompany.mycode.MyClass.myMethod=PROPAGATION_MANDATORY,ISOLATION_DEFAULT
+ *
+ * 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}).
+ *
+ * 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.
+ *
+ *
+ *
+ *
+ *
+ * <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".
+ *
+ * <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.
+ *
+ * null if an already active JOTM instance is used.
+ * 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.
+ *
+ *
+ *
+ *
+ * tx:jta-transaction-manager configuration
+ * element for automatically picking the appropriate JTA platform transaction manager
+ * (automatically detecting WebLogic, WebSphere and OC4J).
+ *
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * afterCompletion on the given Spring TransactionSynchronizations.
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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.
+ * afterCompletion callback: invoked after commit/rollback.
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ *
+ * <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.
+ *
+ * javax.transaction.TransactionManager
+ * API on WebSphere, staying within supported WebSphere API boundaries.
+ *
+ * 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.
+ *
+ * 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 @@
+
+
+ *
+ *
+ * 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".
+ * 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.
+ * 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".
+ * 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).
+ * 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.
+ * 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.
+ * 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.
+ * 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).
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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).
+ * 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.
+ * 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.
+ * doCommit and doRollback execution,
+ * on any outcome. The default implementation does nothing.
+ * 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.
+ *
+ * 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.
+ * 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.
+ * 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.
+ *
+ * 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).
+ *
+ * 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.
+ * 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.
+ * toString results into bean properties of type
+ * {@link org.springframework.transaction.interceptor.TransactionAttribute}.
+ * 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.
+ * 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".
+ *
+ * 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).
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * execute method, often as anonymous class within a method implementation.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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.
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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).
+ * 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.
+ * 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.
+ *
+ * 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 @@
+
+