diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/AutomaticManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/AutomaticManagedRuntime.java index 3f57664c5..f8f4bd645 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/AutomaticManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/AutomaticManagedRuntime.java @@ -190,4 +190,26 @@ public class AutomaticManagedRuntime public void endConfiguration() { } + + public void setRollbackOnly(Throwable cause) + throws Exception { + // check to see if the runtime is cached + if (_runtime == null) + getTransactionManager(); + + if (_runtime != null) + _runtime.setRollbackOnly(cause); + } + + public Throwable getRollbackCause() + throws Exception { + // check to see if the runtime is cached + if (_runtime == null) + getTransactionManager(); + + if (_runtime != null) + return _runtime.getRollbackCause(); + + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/InvocationManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/InvocationManagedRuntime.java index 5bb254e89..fefa4623c 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/InvocationManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/InvocationManagedRuntime.java @@ -76,4 +76,16 @@ public class InvocationManagedRuntime public void endConfiguration() { } + + public void setRollbackOnly(Throwable cause) + throws Exception { + // there is no generic support for setting the rollback cause + getTransactionManager().getTransaction().setRollbackOnly(); + } + + public Throwable getRollbackCause() + throws Exception { + // there is no generic support for setting the rollback cause + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/JNDIManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/JNDIManagedRuntime.java index cfe5caf08..18792ad06 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/JNDIManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/JNDIManagedRuntime.java @@ -61,4 +61,16 @@ public class JNDIManagedRuntime } return _tm; } + + public void setRollbackOnly(Throwable cause) + throws Exception { + // there is no generic support for setting the rollback cause + getTransactionManager().getTransaction().setRollbackOnly(); + } + + public Throwable getRollbackCause() + throws Exception { + // there is no generic support for setting the rollback cause + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/ManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/ManagedRuntime.java index 8971007ee..d5822d827 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/ManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/ManagedRuntime.java @@ -33,4 +33,24 @@ public interface ManagedRuntime { */ public TransactionManager getTransactionManager() throws Exception; + + /** + * Sets the rollback only flag on the current transaction. If the + * TransactionManager is capable of tracking the cause of the + * rollback-only flag, it will also pass along cause information. + * + * @param cause the Throwable that caused the transaction to be + * marked for rollback, or null of none is known + */ + public void setRollbackOnly(Throwable cause) + throws Exception; + + /** + * Returns the Throwable that caused the current transaction to be + * marked for rollback, provided that any exists. + * + * @return the Throwable cause, or null if none + */ + public Throwable getRollbackCause() + throws Exception; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/SunOneManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/SunOneManagedRuntime.java index e6fa420d1..50c1c9426 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/SunOneManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/SunOneManagedRuntime.java @@ -43,4 +43,16 @@ public class SunOneManagedRuntime Object sw = _switchMeth.invoke(null, (Object[]) null); return (TransactionManager) _txManagerMeth.invoke(sw, (Object[]) null); } + + public void setRollbackOnly(Throwable cause) + throws Exception { + // there is no generic support for setting the rollback cause + getTransactionManager().getTransaction().setRollbackOnly(); + } + + public Throwable getRollbackCause() + throws Exception { + // there is no generic support for setting the rollback cause + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WASManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WASManagedRuntime.java index 3b25aecdd..0dbf09fad 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WASManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WASManagedRuntime.java @@ -1,373 +1,373 @@ -/* - * Copyright 2006 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.openjpa.ee; - -import java.io.IOException; +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openjpa.ee; + +import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Method; - -import javax.naming.Context; -import javax.naming.InitialContext; -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.xa.XAResource; - -import org.apache.openjpa.conf.OpenJPAConfiguration; -import org.apache.openjpa.lib.conf.Configurable; -import org.apache.openjpa.lib.conf.Configuration; -import org.apache.openjpa.lib.log.Log; -import org.apache.openjpa.lib.util.Localizer; -import org.apache.openjpa.util.InvalidStateException; -import org.apache.openjpa.util.NoTransactionException; - -import serp.bytecode.BCClass; -import serp.bytecode.Project; - -/** - * {@link ManagedRuntime} implementation that allows synchronization with a - * WebSphere managed transaction. - * - *

- * WebSphere Application Server does not expose the TransactionManager to an - * application. Instead it provides a proprietary interface to register for - * synchronization and obtain transaction ids. - * - *

- * WASManagedRuntime provides the wrapper classes needed to interact with the - * WAS proprietary interface and the OpenJPA kernel. - * - * @author Michael Dick, Kevin Sutter - */ -public class WASManagedRuntime implements ManagedRuntime, Configurable { - - private static Localizer _loc = - Localizer.forPackage(WASManagedRuntime.class); - - private Object _extendedTransaction = null; - private Method _getGlobalId = null; - private Method _registerSync = null; - private OpenJPAConfiguration _conf = null; - private Log _log = null; - - /** - * Gets an extendedJTATransaction from JNDI and creates a transaction - * wrapper - */ - public javax.transaction.TransactionManager getTransactionManager() - throws Exception { - return new WASTransaction(); - } - - /** - * Transaction wrapper for WebSphere. WebSphere exposes a subset of the - * Transaction and TransactionManager interfaces to the customer. Any - * methods which are not exposed by WebSphere will throw an - * IllegalStateException to the caller. - * - *

- * Methods supported by WAS are - *

- */ - class WASTransaction implements javax.transaction.TransactionManager, - javax.transaction.Transaction { - - public int getStatus() throws SystemException { - int rval = Status.STATUS_UNKNOWN; - try { - if (getGlobalId() != null) { - rval = Status.STATUS_ACTIVE; - } else { - rval = Status.STATUS_NO_TRANSACTION; - } - } catch (Exception e) { - throw new NoTransactionException(_loc - .get("was-transaction-id-exception")).setCause(e); - } - return rval; - } - - /** - * Provides a Transaction wrapper. The transaction wrapper mayb only be - * used to determine the status of the current transaction. WebSphere - * does not allow direct control of container transactions. - * - * @return A WebSphere transaction wrapper. - */ - public Transaction getTransaction() throws SystemException { - return this; - } - - /** - * Register for synchronization with a WebSphere managed transaction via - * the extendedJTATransaction interface. - */ - public void registerSynchronization(Synchronization arg0) - throws IllegalStateException, RollbackException, SystemException { - if (_extendedTransaction != null) { - try { - _registerSync.invoke(_extendedTransaction, - new Object[] { new WASSynchronization(arg0) }); - } catch (Exception e) { - throw new InvalidStateException(_loc - .get("was-reflection-exception")).setCause(e); - } - } else { - throw new InvalidStateException(_loc.get("was-lookup-error")); - } - } - - /** - * Gets the GlobalTransaction ID of the WebSphere managed transaction. - * If no Global Transaction is active null will be returned. - * - * @return Null if a global transaction is not active or if an error - * occurs. byte[] id if a global transaction is active. - */ - private byte[] getGlobalId() { - byte[] rval = null; - try { - rval = (byte[]) _getGlobalId.invoke(_extendedTransaction, null); - } catch (Exception e) { - throw new InvalidStateException(_loc - .get("was-reflection-exception")).setCause(e); - } - return rval; - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public void begin() throws NotSupportedException, SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "begin")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public void commit() throws HeuristicMixedException, - HeuristicRollbackException, IllegalStateException, - RollbackException, SecurityException, SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "commit")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public void resume(Transaction arg0) throws IllegalStateException, - InvalidTransactionException, SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "resume")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Log a - * trace instead of throwing an exception. Rollback may be used in - * some error paths, throwing another exception may result in the - * original exception being lost. - */ - public void rollback() throws IllegalStateException, SecurityException, - SystemException { - if (_log.isTraceEnabled()) { - _log.trace(_loc.get("was-unsupported-op", "rollback")); - } - } - - /** - * Unimplemented, WAS does not provide this level of control. Log a - * trace instead of throwing an exception. SetRollbackOnly may be used - * in some error paths, throwing another exception may result in the - * original exception being lost. - */ - public void setRollbackOnly() throws IllegalStateException, - SystemException { - if (_log.isTraceEnabled()) { - _log.trace(_loc.get("was-unsupported-op", "setRollbackOnly")); - } - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public void setTransactionTimeout(int arg0) throws SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "setTransactionTimeout")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public Transaction suspend() throws SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "suspend")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public boolean delistResource(XAResource arg0, int arg1) - throws IllegalStateException, SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "delistResource")); - } - - /** - * Unimplemented, WAS does not provide this level of control. Throws an - * IllegalStateException - */ - public boolean enlistResource(XAResource arg0) - throws IllegalStateException, RollbackException, SystemException { - throw new InvalidStateException(_loc.get("was-unsupported-op", - "enlistResource")); - } - } - - /** - * WASSynchronization wrapper. This class translates the WAS proprietary - * synchronization callback methods to javax.transaction.Synchronization - * methods. - * - *

- * This class implements the - * com.ibm.websphere.jtaextensions.SynchronizationCallback interface. Since - * SynchronizationCallback is not available at compile time we use Serp to - * add the interface to the class after all classes have been compiled. - * - *

- * SynchronizationCallback is expected to be available whenever this class - * is instantiated, therefore this class should only be used when running in - * WebSphere. - * - * @see org.apache.openjpa.util.WASTransformer - */ - static class WASSynchronization { - - Synchronization _sync = null; - - WASSynchronization(Synchronization sync) { - _sync = sync; - } - - /** - * AfterCompletion wrapper. Translates the WAS proprietary call to a - * javax.transaction.Synchronization call. - */ - public void afterCompletion(int localTransactionId, - byte[] globalTransactionId, boolean committed) { - if (_sync != null) { - if (committed) { - _sync.afterCompletion(Status.STATUS_COMMITTED); - } else { - _sync.afterCompletion(Status.STATUS_UNKNOWN); - } - } - } - - /** - * BeforeCompletion wrapper. Translates WAS proprietary call to a - * javax.transaction.Synchronization call. - */ - public void beforeCompletion(int arg0, byte[] arg1) { - if (_sync != null) { - _sync.beforeCompletion(); - } - } - } - - /** - * Caches a copy of the configuration. The configuration is used to obtain - * the logger and classloader. - */ - public void setConfiguration(Configuration conf) { - _conf = (OpenJPAConfiguration) conf; - _log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); - } - - /** - * EndConfiguration stub. - */ - public void endConfiguration() { - try { - Context ctx = new InitialContext(); - try { - _extendedTransaction = - ctx.lookup("java:comp/websphere/ExtendedJTATransaction"); - } finally { - ctx.close(); - } - - ClassLoader loader = _conf.getClassResolverInstance() - .getClassLoader(getClass(), null); - - Class extendedJTATransaction = Class.forName( - "com.ibm.websphere.jtaextensions.ExtendedJTATransaction", true, - loader); - - _registerSync = extendedJTATransaction.getMethod( - "registerSynchronizationCallbackForCurrentTran", - new Class[] { Class.forName( - "com.ibm.websphere.jtaextensions.SynchronizationCallback", - true, loader) }); - _getGlobalId = extendedJTATransaction. - getMethod("getGlobalId", null); - } catch (Exception e) { - throw new InvalidStateException(_loc - .get("was-reflection-exception"), e).setFatal(true); - } - } - - /** - * StartConfiguration stub. - */ - public void startConfiguration() { - // Nothing to do - } - - /** - * Class that will be modified - */ - static final String CLASS = - "org.apache.openjpa.ee.WASManagedRuntime$WASSynchronization"; - - /** - * Interface which will be added - */ - static final String INTERFACE = - "com.ibm.websphere.jtaextensions.SynchronizationCallback"; - - public static void main(String[] args) - throws IOException { - Project project = new Project(); +import java.lang.reflect.Method; + +import javax.naming.Context; +import javax.naming.InitialContext; +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.xa.XAResource; + +import org.apache.openjpa.conf.OpenJPAConfiguration; +import org.apache.openjpa.lib.conf.Configurable; +import org.apache.openjpa.lib.conf.Configuration; +import org.apache.openjpa.lib.log.Log; +import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.util.InvalidStateException; +import org.apache.openjpa.util.NoTransactionException; + +import serp.bytecode.BCClass; +import serp.bytecode.Project; + +/** + * {@link ManagedRuntime} implementation that allows synchronization with a + * WebSphere managed transaction. + * + *

+ * WebSphere Application Server does not expose the TransactionManager to an + * application. Instead it provides a proprietary interface to register for + * synchronization and obtain transaction ids. + * + *

+ * WASManagedRuntime provides the wrapper classes needed to interact with the + * WAS proprietary interface and the OpenJPA kernel. + * + * @author Michael Dick, Kevin Sutter + */ +public class WASManagedRuntime implements ManagedRuntime, Configurable { + + private static Localizer _loc = + Localizer.forPackage(WASManagedRuntime.class); + + private Object _extendedTransaction = null; + private Method _getGlobalId = null; + private Method _registerSync = null; + private OpenJPAConfiguration _conf = null; + private Log _log = null; + + /** + * Gets an extendedJTATransaction from JNDI and creates a transaction + * wrapper + */ + public javax.transaction.TransactionManager getTransactionManager() + throws Exception { + return new WASTransaction(); + } + + /** + * Transaction wrapper for WebSphere. WebSphere exposes a subset of the + * Transaction and TransactionManager interfaces to the customer. Any + * methods which are not exposed by WebSphere will throw an + * IllegalStateException to the caller. + * + *

+ * Methods supported by WAS are + *

+ */ + class WASTransaction implements javax.transaction.TransactionManager, + javax.transaction.Transaction { + + public int getStatus() throws SystemException { + int rval = Status.STATUS_UNKNOWN; + try { + if (getGlobalId() != null) { + rval = Status.STATUS_ACTIVE; + } else { + rval = Status.STATUS_NO_TRANSACTION; + } + } catch (Exception e) { + throw new NoTransactionException(_loc + .get("was-transaction-id-exception")).setCause(e); + } + return rval; + } + + /** + * Provides a Transaction wrapper. The transaction wrapper mayb only be + * used to determine the status of the current transaction. WebSphere + * does not allow direct control of container transactions. + * + * @return A WebSphere transaction wrapper. + */ + public Transaction getTransaction() throws SystemException { + return this; + } + + /** + * Register for synchronization with a WebSphere managed transaction via + * the extendedJTATransaction interface. + */ + public void registerSynchronization(Synchronization arg0) + throws IllegalStateException, RollbackException, SystemException { + if (_extendedTransaction != null) { + try { + _registerSync.invoke(_extendedTransaction, + new Object[] { new WASSynchronization(arg0) }); + } catch (Exception e) { + throw new InvalidStateException(_loc + .get("was-reflection-exception")).setCause(e); + } + } else { + throw new InvalidStateException(_loc.get("was-lookup-error")); + } + } + + /** + * Gets the GlobalTransaction ID of the WebSphere managed transaction. + * If no Global Transaction is active null will be returned. + * + * @return Null if a global transaction is not active or if an error + * occurs. byte[] id if a global transaction is active. + */ + private byte[] getGlobalId() { + byte[] rval = null; + try { + rval = (byte[]) _getGlobalId.invoke(_extendedTransaction, null); + } catch (Exception e) { + throw new InvalidStateException(_loc + .get("was-reflection-exception")).setCause(e); + } + return rval; + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public void begin() throws NotSupportedException, SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "begin")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public void commit() throws HeuristicMixedException, + HeuristicRollbackException, IllegalStateException, + RollbackException, SecurityException, SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "commit")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public void resume(Transaction arg0) throws IllegalStateException, + InvalidTransactionException, SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "resume")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Log a + * trace instead of throwing an exception. Rollback may be used in + * some error paths, throwing another exception may result in the + * original exception being lost. + */ + public void rollback() throws IllegalStateException, SecurityException, + SystemException { + if (_log.isTraceEnabled()) { + _log.trace(_loc.get("was-unsupported-op", "rollback")); + } + } + + /** + * Unimplemented, WAS does not provide this level of control. Log a + * trace instead of throwing an exception. SetRollbackOnly may be used + * in some error paths, throwing another exception may result in the + * original exception being lost. + */ + public void setRollbackOnly() throws IllegalStateException, + SystemException { + if (_log.isTraceEnabled()) { + _log.trace(_loc.get("was-unsupported-op", "setRollbackOnly")); + } + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public void setTransactionTimeout(int arg0) throws SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "setTransactionTimeout")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public Transaction suspend() throws SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "suspend")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public boolean delistResource(XAResource arg0, int arg1) + throws IllegalStateException, SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "delistResource")); + } + + /** + * Unimplemented, WAS does not provide this level of control. Throws an + * IllegalStateException + */ + public boolean enlistResource(XAResource arg0) + throws IllegalStateException, RollbackException, SystemException { + throw new InvalidStateException(_loc.get("was-unsupported-op", + "enlistResource")); + } + } + + /** + * WASSynchronization wrapper. This class translates the WAS proprietary + * synchronization callback methods to javax.transaction.Synchronization + * methods. + * + *

+ * This class implements the + * com.ibm.websphere.jtaextensions.SynchronizationCallback interface. Since + * SynchronizationCallback is not available at compile time we use Serp to + * add the interface to the class after all classes have been compiled. + * + *

+ * SynchronizationCallback is expected to be available whenever this class + * is instantiated, therefore this class should only be used when running in + * WebSphere. + * + * @see org.apache.openjpa.util.WASTransformer + */ + static class WASSynchronization { + + Synchronization _sync = null; + + WASSynchronization(Synchronization sync) { + _sync = sync; + } + + /** + * AfterCompletion wrapper. Translates the WAS proprietary call to a + * javax.transaction.Synchronization call. + */ + public void afterCompletion(int localTransactionId, + byte[] globalTransactionId, boolean committed) { + if (_sync != null) { + if (committed) { + _sync.afterCompletion(Status.STATUS_COMMITTED); + } else { + _sync.afterCompletion(Status.STATUS_UNKNOWN); + } + } + } + + /** + * BeforeCompletion wrapper. Translates WAS proprietary call to a + * javax.transaction.Synchronization call. + */ + public void beforeCompletion(int arg0, byte[] arg1) { + if (_sync != null) { + _sync.beforeCompletion(); + } + } + } + + /** + * Caches a copy of the configuration. The configuration is used to obtain + * the logger and classloader. + */ + public void setConfiguration(Configuration conf) { + _conf = (OpenJPAConfiguration) conf; + _log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); + } + + /** + * EndConfiguration stub. + */ + public void endConfiguration() { + try { + Context ctx = new InitialContext(); + try { + _extendedTransaction = + ctx.lookup("java:comp/websphere/ExtendedJTATransaction"); + } finally { + ctx.close(); + } + + ClassLoader loader = _conf.getClassResolverInstance() + .getClassLoader(getClass(), null); + + Class extendedJTATransaction = Class.forName( + "com.ibm.websphere.jtaextensions.ExtendedJTATransaction", true, + loader); + + _registerSync = extendedJTATransaction.getMethod( + "registerSynchronizationCallbackForCurrentTran", + new Class[] { Class.forName( + "com.ibm.websphere.jtaextensions.SynchronizationCallback", + true, loader) }); + _getGlobalId = extendedJTATransaction. + getMethod("getGlobalId", null); + } catch (Exception e) { + throw new InvalidStateException(_loc + .get("was-reflection-exception"), e).setFatal(true); + } + } + + /** + * StartConfiguration stub. + */ + public void startConfiguration() { + // Nothing to do + } + + /** + * Class that will be modified + */ + static final String CLASS = + "org.apache.openjpa.ee.WASManagedRuntime$WASSynchronization"; + + /** + * Interface which will be added + */ + static final String INTERFACE = + "com.ibm.websphere.jtaextensions.SynchronizationCallback"; + + public static void main(String[] args) + throws IOException { + Project project = new Project(); InputStream in = WASManagedRuntime.class.getClassLoader() .getResourceAsStream(CLASS.replace('.', '/') + ".class"); @@ -382,7 +382,19 @@ public class WASManagedRuntime implements ManagedRuntime, Configurable { } } } - bcClass.declareInterface(INTERFACE); - bcClass.write(); - } -} + bcClass.declareInterface(INTERFACE); + bcClass.write(); + } + + public void setRollbackOnly(Throwable cause) + throws Exception { + // there is no generic support for setting the rollback cause + getTransactionManager().getTransaction().setRollbackOnly(); + } + + public Throwable getRollbackCause() + throws Exception { + // there is no generic support for setting the rollback cause + return null; + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WLSManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WLSManagedRuntime.java index abedad1f7..44404624b 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WLSManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/ee/WLSManagedRuntime.java @@ -16,6 +16,7 @@ package org.apache.openjpa.ee; import java.lang.reflect.Method; +import javax.transaction.Transaction; import javax.transaction.TransactionManager; /** @@ -45,4 +46,34 @@ public class WLSManagedRuntime Object o = _txHelperMeth.invoke(null, null); return (TransactionManager) _txManagerMeth.invoke(o, null); } + + public void setRollbackOnly(Throwable cause) + throws Exception { + Transaction transaction = getTransactionManager().getTransaction(); + try { + // try to use reflection to call the setRollbackOnly(Throwable) + // method in weblogic.transaction.Transaction + transaction.getClass(). + getMethod("setRollbackOnly", new Class[] { Throwable.class }). + invoke(transaction, new Object[] { cause }); + } catch (Throwable e) { + // some problem occurred: fall back to the traditional call + transaction.setRollbackOnly(); + } + } + + public Throwable getRollbackCause() + throws Exception { + Transaction transaction = getTransactionManager().getTransaction(); + try { + // try to use reflection to call the getRollbackReason() + // method in weblogic.transaction.Transaction + return (Throwable) transaction.getClass(). + getMethod("getRollbackReason", new Class[0]). + invoke(transaction, new Object[0]); + } catch (Throwable e) { + // some problem occurred: just return null + return null; + } + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Broker.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Broker.java index 18d716efd..4756cdf18 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Broker.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Broker.java @@ -339,6 +339,24 @@ public interface Broker */ public void setRollbackOnly(); + /** + * Mark the current transaction for rollback with the specified cause + * of the rollback. + * + * @since 0.9.7 + */ + public void setRollbackOnly(Throwable cause); + + /** + * Returns the Throwable that caused the transaction to be + * marked for rollback. + * + * @return the Throwable, or null if none was given + * + * @since 0.9.7 + */ + public Throwable getRollbackCause(); + /** * Set a transactional savepoint where operations after this savepoint * will be rolled back. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index ed73a1749..f7f1bd7a0 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -682,7 +682,7 @@ public class BrokerImpl setNestedThrowables(exceps); if ((mode & CALLBACK_ROLLBACK) != 0 && (_flags & FLAG_ACTIVE) != 0) { ce.setFatal(true); - setRollbackOnlyInternal(); + setRollbackOnlyInternal(ce); } if ((mode & CALLBACK_LOG) != 0 && _log.isWarnEnabled()) _log.warn(ce); @@ -1205,12 +1205,12 @@ public class BrokerImpl } catch (OpenJPAException ke) { // if we already started the transaction, don't let it commit if ((_flags & FLAG_ACTIVE) != 0) - setRollbackOnlyInternal(); + setRollbackOnlyInternal(ke); throw ke.setFatal(true); } catch (RuntimeException re) { // if we already started the transaction, don't let it commit if ((_flags & FLAG_ACTIVE) != 0) - setRollbackOnlyInternal(); + setRollbackOnlyInternal(re); throw new StoreException(re).setFatal(true); } @@ -1408,11 +1408,38 @@ public class BrokerImpl } } + public Throwable getRollbackCause() { + beginOperation(true); + try { + if ((_flags & FLAG_ACTIVE) == 0) + return null; + + javax.transaction.Transaction trans = + _runtime.getTransactionManager().getTransaction(); + if (trans == null) + return null; + if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK) + return _runtime.getRollbackCause(); + + return null; + } catch (OpenJPAException ke) { + throw ke; + } catch (Exception e) { + throw new GeneralException(e); + } finally { + endOperation(); + } + } + public void setRollbackOnly() { + setRollbackOnly(new UserException()); + } + + public void setRollbackOnly(Throwable cause) { beginOperation(true); try { assertTransactionOperation(); - setRollbackOnlyInternal(); + setRollbackOnlyInternal(cause); } finally { endOperation(); } @@ -1421,13 +1448,13 @@ public class BrokerImpl /** * Mark the current transaction as rollback-only. */ - private void setRollbackOnlyInternal() { + private void setRollbackOnlyInternal(Throwable cause) { try { javax.transaction.Transaction trans = _runtime.getTransactionManager().getTransaction(); if (trans == null) throw new InvalidStateException(_loc.get("null-trans")); - trans.setRollbackOnly(); + _runtime.setRollbackOnly(cause); } catch (OpenJPAException ke) { throw ke; } catch (Exception e) { @@ -1613,11 +1640,11 @@ public class BrokerImpl _flags |= FLAG_FLUSHED; } catch (OpenJPAException ke) { // rollback on flush error; objects may be in inconsistent state - setRollbackOnly(); + setRollbackOnly(ke); throw ke.setFatal(true); } catch (RuntimeException re) { // rollback on flush error; objects may be in inconsistent state - setRollbackOnly(); + setRollbackOnly(re); throw new StoreException(re).setFatal(true); } } @@ -3145,7 +3172,7 @@ public class BrokerImpl try { return new AttachManager(this, copyNew, call).attach(obj); } catch (OptimisticException oe) { - setRollbackOnly(); + setRollbackOnly(oe); throw oe.setFatal(true); } catch (OpenJPAException ke) { throw ke; @@ -3172,7 +3199,7 @@ public class BrokerImpl try { return new AttachManager(this, copyNew, call).attachAll(objs); } catch (OptimisticException oe) { - setRollbackOnly(); + setRollbackOnly(oe); throw oe.setFatal(true); } catch (OpenJPAException ke) { throw ke; @@ -3609,7 +3636,7 @@ public class BrokerImpl // transaction to complete before we have a chance to set the // rollback only flag if ((_flags & FLAG_STORE_FLUSHING) != 0) - setRollbackOnlyInternal(); + setRollbackOnlyInternal(new UserException()); return _store.cancelAll(); } catch (OpenJPAException ke) { throw ke; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java index 1eb597a5d..b27c16a20 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java @@ -906,6 +906,22 @@ public class DelegatingBroker } } + public void setRollbackOnly(Throwable cause) { + try { + _broker.setRollbackOnly(cause); + } catch (RuntimeException re) { + throw translate(re); + } + } + + public Throwable getRollbackCause() { + try { + return _broker.getRollbackCause(); + } catch (RuntimeException re) { + throw translate(re); + } + } + public boolean getRollbackOnly() { try { return _broker.getRollbackOnly(); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/LocalManagedRuntime.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/LocalManagedRuntime.java index d54e1e815..9a25aa28f 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/LocalManagedRuntime.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/LocalManagedRuntime.java @@ -28,6 +28,7 @@ import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.StoreException; +import org.apache.openjpa.util.UserException; /** * Uses a local implementation of the {@link TransactionManager} interface. @@ -45,7 +46,7 @@ class LocalManagedRuntime private Synchronization _broker = null; private Synchronization _factorySync = null; private boolean _active = false; - private boolean _rollbackOnly = false; + private Throwable _rollbackOnly = null; /** * Constructor. Provide broker that will be requesting managed @@ -71,20 +72,20 @@ class LocalManagedRuntime // try to invoke before completion in preparation for commit RuntimeException err = null; - if (!_rollbackOnly) { + if (_rollbackOnly == null) { try { _broker.beforeCompletion(); if (_factorySync != null) _factorySync.beforeCompletion(); } catch (RuntimeException re) { - _rollbackOnly = true; + _rollbackOnly = re; err = re; } } else // previously marked rollback only err = new StoreException(_loc.get("marked-rollback")). - setFatal(true); + setCause(_rollbackOnly).setFatal(true); - if (!_rollbackOnly) { + if (_rollbackOnly == null) { try { _broker.afterCompletion(Status.STATUS_COMMITTED); notifyAfterCompletion(Status.STATUS_COMMITTED); @@ -145,17 +146,25 @@ class LocalManagedRuntime if (_factorySync != null) _factorySync.afterCompletion(status); } finally { - _rollbackOnly = false; + _rollbackOnly = null; _factorySync = null; } } public synchronized void setRollbackOnly() { - _rollbackOnly = true; + setRollbackOnly(new UserException()); + } + + public void setRollbackOnly(Throwable cause) { + _rollbackOnly = cause; + } + + public Throwable getRollbackCause() { + return _rollbackOnly; } public synchronized int getStatus() { - if (_rollbackOnly) + if (_rollbackOnly != null) return Status.STATUS_MARKED_ROLLBACK; if (_active) return Status.STATUS_ACTIVE; diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties index f86a47ff7..7063c1cf4 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties @@ -59,7 +59,8 @@ bad-ds-oid: The type "{0}" declares datastore identity but the value \ passed to lookup of type "{1}" is not a OpenJPA id instance. null-oids: Some of the object ids passed to getObjectsById were null. marked-rollback: The transaction cannot be committed, because it was already \ - marked for rollback only. The transaction will be rolled back instead. + marked for rollback only. The transaction will be rolled back instead. \ + The cause of the rollback-only status is reported in the embedded stack. refresh-flushed: You cannot refresh an instance that has been flushed to the \ data store. pc-loader-different: Attempt to cast instance "{0}" to PersistenceCapable failed. \ diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java index 61632b7a9..ae5e25521 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java @@ -430,6 +430,14 @@ public class EntityManagerImpl _broker.rollbackAndResume(); } + public Throwable getRollbackCause() { + if (!isActive()) + throw new IllegalStateException(_loc.get("no-transaction") + .getMessage()); + + return _broker.getRollbackCause(); + } + public boolean getRollbackOnly() { if (!isActive()) throw new IllegalStateException(_loc.get("no-transaction") @@ -442,6 +450,10 @@ public class EntityManagerImpl _broker.setRollbackOnly(); } + public void setRollbackOnly(Throwable cause) { + _broker.setRollbackOnly(cause); + } + public void setSavepoint(String name) { assertNotCloseInvoked(); _broker.setSavepoint(name); diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java index 2fec8f707..1b347af8c 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java @@ -407,6 +407,24 @@ public interface OpenJPAEntityManager */ public void setRollbackOnly(); + /** + * Mark the current transaction for rollback with the specified cause + * of the rollback. + * + * @since 0.9.7 + */ + public void setRollbackOnly(Throwable cause); + + /** + * Returns the Throwable that caused the transaction to be + * marked for rollback. + * + * @return the Throwable, or null if none was given + * + * @since 0.9.7 + */ + public Throwable getRollbackCause(); + /** * Return whether the current transaction has been marked for rollback. */ diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceExceptions.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceExceptions.java index 2f3b96d89..210ea4460 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceExceptions.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceExceptions.java @@ -61,7 +61,7 @@ public class PersistenceExceptions try { throwing = true; if (em.isOpen() && em.isActive()) - em.setRollbackOnly(); + em.setRollbackOnly(re); } finally { // handle re-entrancy throwing = false;