diff --git a/base/src/org/compiere/util/Trx.java b/base/src/org/compiere/util/Trx.java index 52c32ed4f8..2203c4eda6 100644 --- a/base/src/org/compiere/util/Trx.java +++ b/base/src/org/compiere/util/Trx.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.UUID; import java.util.logging.Level; +import org.adempiere.exceptions.AdempiereException; import org.compiere.db.CConnection; import org.compiere.db.ServerConnection; import org.compiere.interfaces.Server; @@ -46,7 +47,8 @@ import org.compiere.interfaces.Server; * - added rollback(boolean) and commit(boolean) [20070105] * - remove unnecessary use of savepoint * - use UUID for safer transaction name generation - * @version $Id$ + * @author Teo Sarca, http://www.arhipac.ro + *
  • FR [ 2080217 ] Implement TrxRunnable */ public class Trx implements VetoableChangeListener { @@ -676,4 +678,80 @@ public class Trx implements VetoableChangeListener return trxs; } + /** + * @see #run(String, TrxRunnable) + */ + public static void run(TrxRunnable r) + { + run(null, r); + } + + /** + * Execute runnable object using provided transaction. + * If execution fails, database operations will be rolled back. + *

    + * Example:

    +	 * Trx.run(null, new {@link TrxRunnable}() {
    +	 *     public void run(String trxName) {
    +	 *         // do something using trxName
    +	 *     }
    +	 * )};
    +	 * 
    + * + * @param trxName transaction name (if null, a new transaction will be created) + * @param r runnable object + */ + public static void run(String trxName, TrxRunnable r) + { + boolean localTrx = false; + if (trxName == null) { + trxName = Trx.createTrxName("TrxRun"); + localTrx = true; + } + Trx trx = Trx.get(trxName, true); + Savepoint savepoint = null; + try + { + if (!localTrx) + savepoint = trx.setSavepoint(null); + + r.run(trxName); + + if (localTrx) + trx.commit(true); + } + catch (Throwable e) + { + // Rollback transaction + if (localTrx) + { + trx.rollback(); + } + else if (savepoint != null) + { + try { + trx.rollback(savepoint); + } + catch (SQLException e2) {;} + } + trx = null; + // Throw exception + if (e instanceof RuntimeException) + { + throw (RuntimeException)e; + } + else + { + throw new AdempiereException(e); + } + } + finally { + if (localTrx && trx != null) + { + trx.close(); + trx = null; + } + } + } + } // Trx diff --git a/base/src/org/compiere/util/TrxRunnable.java b/base/src/org/compiere/util/TrxRunnable.java new file mode 100644 index 0000000000..5071965f26 --- /dev/null +++ b/base/src/org/compiere/util/TrxRunnable.java @@ -0,0 +1,25 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2008 SC ARHIPAC SERVICE SRL. All Rights Reserved. * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.util; + +/** + * Defines an object that can be ran into an transaction, + * using {@link Trx#run(TrxRunnable)} or {@link Trx#run(String, TrxRunnable)} methods. + * + * @author Teo Sarca, http://www.arhipac.ro + */ +public interface TrxRunnable +{ + public void run(String trxName); +} diff --git a/extend/src/test/functional/FunctionalTestSuite.java b/extend/src/test/functional/FunctionalTestSuite.java index e0ead7114a..e4173fb4d4 100644 --- a/extend/src/test/functional/FunctionalTestSuite.java +++ b/extend/src/test/functional/FunctionalTestSuite.java @@ -18,6 +18,7 @@ public class FunctionalTestSuite { suite.addTestSuite(MStorageTest.class); suite.addTestSuite(MSysConfigTest.class); suite.addTestSuite(QueryTest.class); + suite.addTestSuite(TrxTest.class); suite.addTestSuite(MRefListTest.class); suite.addTestSuite(MUOMTest.class); //$JUnit-END$ diff --git a/extend/src/test/functional/TrxTest.java b/extend/src/test/functional/TrxTest.java new file mode 100644 index 0000000000..d652f9db34 --- /dev/null +++ b/extend/src/test/functional/TrxTest.java @@ -0,0 +1,114 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2008 SC ARHIPAC SERVICE SRL. All Rights Reserved. * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package test.functional; + +import org.adempiere.exceptions.AdempiereException; +import org.compiere.model.MTest; +import org.compiere.model.Query; +import org.compiere.util.Trx; +import org.compiere.util.TrxRunnable; + +import test.AdempiereTestCase; + +/** + * Test {@link Trx} class + * @author Teo Sarca, http://www.arhipac.ro + */ +public class TrxTest extends AdempiereTestCase { + private int m_id2 = -1; + + /** + * Test {@link Trx#run(TrxRunnable)} and {@link Trx#run(String, TrxRunnable)} methods + */ + public void testRunTrxRunnable() throws Exception + { + // + // Create test outside trx - success + m_id2 = -1; + Trx.run(new TrxRunnable() { + public void run(String trxName) { + m_id2 = createTest(trxName).get_ID(); + } + }); + assertTestExists(m_id2, true, null); + new MTest(getCtx(), m_id2, null).deleteEx(true); + + // + // Create test outside trx - fail + m_id2 = -1; + try { + Trx.run(new TrxRunnable() { + public void run(String trxName) { + m_id2 = createTest(trxName).get_ID(); + throw new AdempiereException("FORCE"); + } + }); + // + assertTrue("Should not happen because previous code is throwing exception", false); + } + catch (AdempiereException e) { + } + assertTestExists(m_id2, false, null); + + // + // Create test1 + String trxName = getTrxName(); + MTest test1 = createTest(trxName); + + // + // Fail creating test2 + m_id2 = -1; + try { + Trx.run(trxName, new TrxRunnable() { + public void run(String trxName) { + m_id2 = createTest(trxName).get_ID(); + throw new AdempiereException("FORCE"); + } + }); + // + assertTrue("Should not happen because previous code is throwing exception", false); + } + catch (AdempiereException e) { + } + assertTestExists(m_id2, false, trxName); + assertTestExists(test1.get_ID(), true, trxName); + + // + // Success creating test2 + m_id2 = -1; + Trx.run(trxName, new TrxRunnable() { + public void run(String trxName) { + m_id2 = createTest(trxName).get_ID(); + } + }); + assertTestExists(m_id2, true, trxName); + assertTestExists(test1.get_ID(), true, trxName); + } + + private final MTest createTest(String trxName) { + MTest test = new MTest (getCtx(), "test-"+getClass(), 10); + test.set_TrxName(trxName); + test.saveEx(); + return test; + } + + private final void assertTestExists(int test_id, boolean existsTarget, String trxName) + { + String whereClause = MTest.COLUMNNAME_Test_ID+"=?"; + boolean exists = new Query(getCtx(), MTest.Table_Name, whereClause, trxName) + .setParameters(new Object[]{test_id}) + .match(); + assertEquals("Test "+test_id+" [trxName="+trxName+"] - existance issue", existsTarget, exists); + } +}