From eaf452dcc266505f148c28b3558f5e052df6d89f Mon Sep 17 00:00:00 2001 From: Pinaki Poddar Date: Thu, 14 Feb 2008 02:26:59 +0000 Subject: [PATCH] OPENJPA-515 Add basic Tests for distributed database support. git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@627636 13f79535-47bb-0310-9956-ffa450edef68 --- openjpa-slice/pom.xml | 109 ++++++++ openjpa-slice/src/main/ant/enhancer.xml | 84 ++++++ .../openjpa/slice/DistributedBrokerImpl.java | 14 +- .../openjpa/slice/ExecutorServiceValue.java | 3 +- .../DistributedJDBCConfigurationImpl.java | 52 ++-- .../slice/jdbc/DistributedStoreManager.java | 27 +- .../DistributedTransactionManager.java | 105 +++++--- .../transaction/DistributedXATransaction.java | 15 +- .../transaction/NaiveTransactionManager.java | 15 +- .../openjpa/slice/jdbc/localizer.properties | 5 +- .../slice/transaction/localizer.properties | 4 +- .../org/apache/openjpa/slice/Address.java | 82 ++++++ .../org/apache/openjpa/slice/PObject.java | 50 ++++ .../openjpa/slice/PersistenceTestCase.java | 243 ++++++++++++++++++ .../java/org/apache/openjpa/slice/Person.java | 73 ++++++ .../openjpa/slice/SingleEMFTestCase.java | 76 ++++++ .../apache/openjpa/slice/SliceTestCase.java | 41 +++ .../org/apache/openjpa/slice/TestBasic.java | 161 ++++++++++++ .../openjpa/slice/TestConfiguration.java | 75 ++++++ .../org/apache/openjpa/slice/TestQuery.java | 71 +++++ .../java/org/apache/openjpa/slice/TestXA.java | 57 ++++ .../policy/EvenOddDistributionPolicy.java | 36 +++ .../slice/policy/UserDistributionPolicy.java | 78 ++++++ .../test/resources/META-INF/persistence.xml | 118 +++++++++ 24 files changed, 1524 insertions(+), 70 deletions(-) create mode 100644 openjpa-slice/src/main/ant/enhancer.xml create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/Address.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/PObject.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/PersistenceTestCase.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/Person.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/SingleEMFTestCase.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/SliceTestCase.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/TestBasic.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/TestConfiguration.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/TestQuery.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/TestXA.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/policy/EvenOddDistributionPolicy.java create mode 100644 openjpa-slice/src/test/java/org/apache/openjpa/slice/policy/UserDistributionPolicy.java create mode 100644 openjpa-slice/src/test/resources/META-INF/persistence.xml diff --git a/openjpa-slice/pom.xml b/openjpa-slice/pom.xml index 120c041db..7644f66e0 100644 --- a/openjpa-slice/pom.xml +++ b/openjpa-slice/pom.xml @@ -32,6 +32,13 @@ openjpa-parent 1.1.0-SNAPSHOT + + INFO + + -Xmx500m + org.apache.openjpa @@ -45,7 +52,68 @@ ${pom.version} compile + + org.apache.geronimo.specs + geronimo-jpa_3.0_spec + test + + + org.apache.openjpa + openjpa-persistence + ${pom.version} + test + + + mysql + mysql-connector-java + 5.1.5 + test + + + + + test-derby + + false + test-derby + + + + org.apache.derby + derby + test + + + + org.apache.derby.jdbc.EmbeddedDriver + jdbc:derby:target/database/slice-derby-One;create=true + + + + + + test-mysql + + false + test-mysql + + + + mysql + mysql-connector-java + 5.1.5 + + + + com.mysql.jdbc.Driver + ${openjpa.mysql.url} + ${openjpa.mysql.username} + ${openjpa.mysql.password} + + + + @@ -56,6 +124,47 @@ 1.5 + + maven-antrun-plugin + + + test-compile + + + + + + + + + + + + + run + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + + **/DummyTest.java + + + **/Test*.java + + + + diff --git a/openjpa-slice/src/main/ant/enhancer.xml b/openjpa-slice/src/main/ant/enhancer.xml new file mode 100644 index 000000000..76c43ecc2 --- /dev/null +++ b/openjpa-slice/src/main/ant/enhancer.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + running enhancer + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/DistributedBrokerImpl.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/DistributedBrokerImpl.java index b092e4812..a2ff7c247 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/DistributedBrokerImpl.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/DistributedBrokerImpl.java @@ -18,7 +18,7 @@ */ package org.apache.openjpa.slice; -import org.apache.openjpa.kernel.BrokerImpl; +import org.apache.openjpa.kernel.FinalizingBrokerImpl; import org.apache.openjpa.kernel.OpCallbacks; import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.lib.util.Localizer; @@ -35,7 +35,7 @@ import org.apache.openjpa.util.UserException; * */ @SuppressWarnings("serial") -public class DistributedBrokerImpl extends BrokerImpl { +public class DistributedBrokerImpl extends FinalizingBrokerImpl { private transient String slice; private static final Localizer _loc = @@ -79,4 +79,14 @@ public class DistributedBrokerImpl extends BrokerImpl { slice, pc, conf.getActiveSliceNames() })); return slice; } + + @Override + public boolean endOperation() { + try { + return super.endOperation(); + } catch (Exception ex) { + + } + return true; + } } diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/ExecutorServiceValue.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/ExecutorServiceValue.java index d39f2acb9..f8a56ddff 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/ExecutorServiceValue.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/ExecutorServiceValue.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -97,7 +98,7 @@ public class ExecutorServiceValue extends PluginValue { } obj = new ThreadPoolExecutor(defaultSize, defaultSize, keepAliveTime, TimeUnit.SECONDS, - new PriorityBlockingQueue(), factory); + new SynchronousQueue(), factory); Configurations.configureInstance(obj, conf, opts, getProperty()); } diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedJDBCConfigurationImpl.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedJDBCConfigurationImpl.java index 0f1569472..6f1aedcf1 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedJDBCConfigurationImpl.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedJDBCConfigurationImpl.java @@ -37,7 +37,6 @@ import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; import org.apache.openjpa.jdbc.schema.DataSourceFactory; import org.apache.openjpa.lib.conf.BooleanValue; import org.apache.openjpa.lib.conf.ConfigurationProvider; -import org.apache.openjpa.lib.conf.Configurations; import org.apache.openjpa.lib.conf.PluginValue; import org.apache.openjpa.lib.conf.StringListValue; import org.apache.openjpa.lib.conf.StringValue; @@ -55,6 +54,8 @@ import org.apache.openjpa.util.UserException; /** * Implements a distributed configuration of JDBCStoreManagers. + * The original configuration properties are analyzed to create a set of + * Slice specific properties with defaulting rules. * * @author Pinaki Poddar * @@ -67,6 +68,7 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl private Slice _master; private DecoratingDataSource virtualDataSource; + protected BooleanValue lenientPlugin; protected StringValue masterPlugin; protected StringListValue namesPlugin; @@ -90,7 +92,6 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl Map p = cp.getProperties(); String pUnit = getPersistenceUnitName(p); setDiagnosticContext(pUnit); - Log log = getConfigurationLog(); brokerPlugin.setString(DistributedBrokerImpl.class.getName()); @@ -189,8 +190,6 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl public DistributionPolicy getDistributionPolicyInstance() { if (distributionPolicyPlugin.get() == null) { -// Configurations.getProperty(distributionPolicyPlugin.getProperty(), m) -// distributionPolicyPlugin.setString(toProperties(false).get(key)) distributionPolicyPlugin.instantiate(DistributionPolicy.class, this, true); } @@ -239,6 +238,8 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl handleBadConnection(isLenient, slice, ex); } } + if (dataSources.isEmpty()) + throw new UserException(_loc.get("no-slice")); DistributedDataSource result = new DistributedDataSource(dataSources); return result; } @@ -324,29 +325,29 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl for (String key : sliceNames) { JDBCConfiguration child = new JDBCConfigurationImpl(); child.fromProperties(createSliceProperties(original, key)); - child.setId(PREFIX_SLICE + key); + child.setId(key); Slice slice = new Slice(key, child); _slices.add(slice); if (log.isTraceEnabled()) log.trace(_loc.get("slice-configuration", key, child .toProperties(false))); } - setMaster(); + setMaster(original); } /** - * Finds the slices. If slice.Names property is available - * then the slices are ordered in the way they are listed. Otherwise scans - * all available slices by looking for property of the form - * slice.XYZ.abc where XYZ is the slice - * identifier and abc is openjpa property name. The slices - * are then ordered alphabetically. + * Finds the slices. If openjpa.slice.Names property is + * specified then the slices are ordered in the way they are listed. + * Otherwise scans all available slices by looking for property of the form + * openjpa.slice.XYZ.abc where XYZ is the slice + * identifier and abc is any openjpa property name. The slices + * are then ordered alphabetically by their identifier. */ private List findSlices(Map p) { List sliceNames = new ArrayList(); Log log = getConfigurationLog(); - String key = PREFIX_SLICE+namesPlugin.getProperty(); + String key = PREFIX_SLICE + namesPlugin.getProperty(); boolean explicit = p.containsKey(key); if (explicit) { String[] values = p.get(key).toString().split("\\,"); @@ -355,7 +356,7 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl sliceNames.add(name.trim()); } else { if (log.isWarnEnabled()) - log.warn(_loc.get("no-slice-names")); + log.warn(_loc.get("no-slice-names", key)); sliceNames = scanForSliceNames(p); Collections.sort(sliceNames); } @@ -365,6 +366,12 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl return sliceNames; } + /** + * Scan the given map for slice-specific property of the form + * openjpa.slice.XYZ.abc (while ignoring + * openjpa.slice.XYZ as they refer to slice-wide property) + * to determine the names of all available slices. + */ private List scanForSliceNames(Map p) { List sliceNames = new ArrayList(); for (Object o : p.keySet()) { @@ -379,17 +386,17 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl return sliceNames; } - static int getPartCount(String s) { + private static int getPartCount(String s) { return (s == null) ? 0 : s.split(REGEX_DOT).length; } - static String chopHead(String s, String head) { + private static String chopHead(String s, String head) { if (s.startsWith(head)) return s.substring(head.length()); return s; } - static String chopTail(String s, String tail) { + private static String chopTail(String s, String tail) { int i = s.lastIndexOf(tail); if (i == -1) return s; @@ -436,14 +443,15 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl /** * Determine the master slice. */ - private void setMaster() { - String masterSlice = masterPlugin.get(); + private void setMaster(Map original) { + String key = PREFIX_SLICE + masterPlugin.getProperty(); + Object masterSlice = original.get(key); Log log = getConfigurationLog(); List activeSlices = getSlices(null); - if (masterSlice == null || masterSlice.length() == 0) { + if (masterSlice == null) { _master = activeSlices.get(0); if (log.isWarnEnabled()) - log.warn(_loc.get("no-master-slice", _master)); + log.warn(_loc.get("no-master-slice", key, _master)); return; } for (Slice slice:activeSlices) @@ -482,5 +490,5 @@ public class DistributedJDBCConfigurationImpl extends JDBCConfigurationImpl executorServicePlugin.instantiate(ExecutorService.class, this); } return (ExecutorService) executorServicePlugin.get(); - } + } } diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedStoreManager.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedStoreManager.java index 89abb4999..7bdf43bd2 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedStoreManager.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/jdbc/DistributedStoreManager.java @@ -18,6 +18,8 @@ */ package org.apache.openjpa.slice.jdbc; +import java.sql.Connection; +import java.sql.SQLException; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; @@ -82,6 +84,7 @@ class DistributedStoreManager extends JDBCStoreManager { private boolean isXA; private TransactionManager _tm; private final DistributedJDBCConfiguration _conf; + private boolean _active = false; private Log _log; private static final Localizer _loc = Localizer.forPackage(DistributedStoreManager.class); @@ -199,6 +202,9 @@ class DistributedStoreManager extends JDBCStoreManager { } public void begin() { + if (_active) + return; + _active = true; TransactionManager tm = getTransactionManager(); for (SliceStoreManager slice : _slices) { try { @@ -239,16 +245,21 @@ class DistributedStoreManager extends JDBCStoreManager { } public void close() { + _active = false; for (SliceStoreManager slice : _slices) slice.close(); } public void commit() { + if (!_active) + return; TransactionManager tm = getTransactionManager(); try { tm.commit(); } catch (Exception e) { throw new StoreException(e); + } finally { + _active = false; } } @@ -280,6 +291,7 @@ class DistributedStoreManager extends JDBCStoreManager { return false; } + /** * Flush the given StateManagers after binning them to respective physical * slices. @@ -309,7 +321,7 @@ class DistributedStoreManager extends JDBCStoreManager { } return exceptions; } - + /** * Separate the given list of StateManagers in separate lists for each slice * by the associated slice identifier of each StateManager. @@ -421,11 +433,15 @@ class DistributedStoreManager extends JDBCStoreManager { } public void rollback() { + if (!_active) + return; TransactionManager tm = getTransactionManager(); try { tm.rollback(); } catch (Exception e) { throw new StoreException(e); + } finally { + _active = false; } } @@ -476,6 +492,15 @@ class DistributedStoreManager extends JDBCStoreManager { } return _tm; } + + @Override + protected RefCountConnection connectInternal() throws SQLException { + List list = new ArrayList(); + for (SliceStoreManager slice : _slices) + list.add(slice.getConnection()); + DistributedConnection con = new DistributedConnection(list); + return new RefCountConnection(con); + } private static class Flusher implements Callable { final SliceStoreManager store; diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedTransactionManager.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedTransactionManager.java index eb2c66820..1d4d34ef3 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedTransactionManager.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedTransactionManager.java @@ -52,7 +52,7 @@ public class DistributedTransactionManager implements TransactionManager { Localizer.forPackage(DistributedTransactionManager.class); public void begin() throws NotSupportedException, SystemException { - DistributedXATransaction txn = getTransaction(false); + DistributedXATransaction txn = (DistributedXATransaction)getTransaction(); int i = 1; Set resources = txn.getEnlistedResources(); for (XAResource resource : resources) { @@ -70,22 +70,24 @@ public class DistributedTransactionManager implements TransactionManager { public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException { - DistributedXATransaction txn = getTransaction(true); + DistributedXATransaction txn = getTransactionOfCurrentThread(true); Set resources = txn.getEnlistedResources(); int branchId = 1; - boolean nextPhase = true; + Exception failedFirstPhase = null; + Exception failedSecondPhase = null; + for (XAResource resource : resources) { XID branch = txn.getXID().branch(branchId++); try { resource.end(branch, TMSUCCESS); resource.prepare(branch); } catch (XAException e) { - nextPhase = false; + failedFirstPhase = e; } } branchId = 1; // reset - if (!nextPhase) { + if (failedFirstPhase != null) { for (XAResource resource : resources) { try { XID branch = txn.getXID().branch(branchId++); @@ -93,20 +95,24 @@ public class DistributedTransactionManager implements TransactionManager { } catch (XAException e) { // ignore } - throw new SystemException(_loc.get("prepare-failed") - .getMessage()); - } - } - - branchId = 1; // reset - for (XAResource resource : resources) { - XID branch = txn.getXID().branch(branchId++); - try { - resource.commit(branch, false); - } catch (XAException e) { - throw new SystemException(e.getMessage()); + } + } else { + branchId = 1; // reset + for (XAResource resource : resources) { + XID branch = txn.getXID().branch(branchId++); + try { + resource.commit(branch, false); + } catch (XAException e) { + failedSecondPhase = e; + } } } + txn.commit(); + txns.set(null); + if (failedFirstPhase != null) { + throw new SystemException(failedFirstPhase.getMessage()); + } else if (failedSecondPhase != null) + throw new SystemException(failedSecondPhase.getMessage()); } public int getStatus() throws SystemException { @@ -114,7 +120,10 @@ public class DistributedTransactionManager implements TransactionManager { } public Transaction getTransaction() throws SystemException { - return getTransaction(false); + DistributedXATransaction txn = getTransactionOfCurrentThread(false); + if (txn == null) + txn = newTransaction(); + return txn; } public void resume(Transaction arg0) throws IllegalStateException, @@ -124,7 +133,9 @@ public class DistributedTransactionManager implements TransactionManager { public void rollback() throws IllegalStateException, SecurityException, SystemException { - DistributedXATransaction txn = getTransaction(true); + DistributedXATransaction txn = getTransactionOfCurrentThread(true); + if (txn == null) + return; Set slices = txn.getEnlistedResources(); int branchId = 1; for (XAResource slice : slices) { @@ -135,6 +146,8 @@ public class DistributedTransactionManager implements TransactionManager { } catch (XAException e) { } } + txn.rollback(); + txns.set(null); } public void setRollbackOnly() throws IllegalStateException, SystemException { @@ -173,19 +186,49 @@ public class DistributedTransactionManager implements TransactionManager { * transaction, a new transaction is created with a global identifier * and associated with the current thread. */ - DistributedXATransaction getTransaction(boolean mustExist) { - DistributedXATransaction txn = txns.get(); - if (txn == null) { - if (mustExist) - throw new IllegalStateException(_loc.get("no-txn-on-thread", - Thread.currentThread().getName()).getMessage()); - byte[] global = - Long.toHexString(System.currentTimeMillis()).getBytes(); - XID xid = new XID(0, global, new byte[] { 0x1 }); - txn = new DistributedXATransaction(xid, this); - txns.set(txn); - } +// DistributedXATransaction getTransaction(boolean create, boolean mustExist) { +// DistributedXATransaction txn = txns.get(); +// if (txn == null && mustExist) { +// throw new IllegalStateException(_loc.get("no-txn-on-thread", +// Thread.currentThread().getName()).getMessage()); +// } +//// if (txn != null && !mustExist) { +//// throw new IllegalStateException(_loc.get("txn-exists--on-thread", +//// txn.getXID(), Thread.currentThread().getName()).getMessage()); +//// } +// if (create && txn == null) { +// } +// +// +// return txn; +// } + +// DistributedXATransaction getTransactionOfCurrentThread() { +// return txns.get(); +// } + + DistributedXATransaction getTransactionOfCurrentThread(boolean mustExist) { + DistributedXATransaction txn = txns.get(); + if (txn == null && mustExist) + throw new IllegalStateException(_loc.get("no-txn-on-thread", + Thread.currentThread().getName()).getMessage()); return txn; } + + DistributedXATransaction newTransaction() { + DistributedXATransaction txn = getTransactionOfCurrentThread(false); + if (txn != null) + throw new IllegalStateException(_loc.get("txn-exists-on-thread", + txn.getXID(), Thread.currentThread().getName()).getMessage()); + + byte[] global = + Long.toHexString(System.currentTimeMillis()).getBytes(); + XID xid = new XID(0, global, new byte[] { 0x1 }); + txn = new DistributedXATransaction(xid, this); + txns.set(txn); + + return txn; + } + } diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedXATransaction.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedXATransaction.java index 2139245de..64cec78a0 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedXATransaction.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/DistributedXATransaction.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; +import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; @@ -39,13 +40,11 @@ import javax.transaction.xa.XAResource; * */ class DistributedXATransaction implements Transaction { - private static ThreadLocal _trans = new ThreadLocal(); private Set _slices = new HashSet(); private Set _syncs = new HashSet(); - private final TransactionManager _tm; +// private final TransactionManager _tm; private final XID xid; private int _status; - private boolean _rollbackOnly; /** * Construct with @@ -54,7 +53,7 @@ class DistributedXATransaction implements Transaction { */ DistributedXATransaction(XID xid, TransactionManager tm) { this.xid = xid; - this._tm = tm; + _status = Status.STATUS_ACTIVE; } public XID getXID() { @@ -64,7 +63,8 @@ class DistributedXATransaction implements Transaction { public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, SystemException { - _tm.commit(); + _status = Status.STATUS_COMMITTED; + _slices.clear(); } public boolean delistResource(XAResource arg0, int arg1) @@ -87,11 +87,12 @@ class DistributedXATransaction implements Transaction { } public void rollback() throws IllegalStateException, SystemException { - _tm.rollback(); + _status = Status.STATUS_ROLLEDBACK; + _slices.clear(); } public void setRollbackOnly() throws IllegalStateException, SystemException { - _rollbackOnly = true; + _status = Status.STATUS_MARKED_ROLLBACK; } Set getEnlistedResources() { diff --git a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/NaiveTransactionManager.java b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/NaiveTransactionManager.java index a858224b6..3b4e34761 100644 --- a/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/NaiveTransactionManager.java +++ b/openjpa-slice/src/main/java/org/apache/openjpa/slice/transaction/NaiveTransactionManager.java @@ -18,6 +18,7 @@ */ package org.apache.openjpa.slice.transaction; +import java.sql.SQLException; import java.util.Set; import javax.transaction.HeuristicMixedException; @@ -60,7 +61,12 @@ public class NaiveTransactionManager implements TransactionManager { DistributedNaiveTransaction txn = getTransaction(false); Set slices = txn.getEnlistedResources(); for (SliceStoreManager slice : slices) { - slice.commit(); + try { + if (!slice.getConnection().getAutoCommit()) + slice.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } } } @@ -82,7 +88,12 @@ public class NaiveTransactionManager implements TransactionManager { DistributedNaiveTransaction txn = getTransaction(false); Set slices = txn.getEnlistedResources(); for (SliceStoreManager slice : slices) { - slice.commit(); + try { + if (!slice.getConnection().getAutoCommit()) + slice.rollback(); + } catch (SQLException e) { + e.printStackTrace(); + } } } diff --git a/openjpa-slice/src/main/resources/org/apache/openjpa/slice/jdbc/localizer.properties b/openjpa-slice/src/main/resources/org/apache/openjpa/slice/jdbc/localizer.properties index 60c2222a6..e21d0de7e 100644 --- a/openjpa-slice/src/main/resources/org/apache/openjpa/slice/jdbc/localizer.properties +++ b/openjpa-slice/src/main/resources/org/apache/openjpa/slice/jdbc/localizer.properties @@ -50,10 +50,11 @@ slice-xa-disabled: Not all active slices "{0}" is XA-complaint and hence store \ two-phase: "{3}".{0}"(xid=[{4}]] Connection={1} XAConnection={2} factory-init: Starting OpenJPA Slice {0} config-init: Configuring Slice {0} -no-slice-names: Slice identifiers are not listed in [slice.Names] property. \ +no-slice-names: Slice identifiers are not explicitly listed via "{0}" property.\ The configuration will be scanned to determine slice identifiers. +no-slice: No slices are configured or available no-master-slice: No master slice has been configured explicitly in \ - [slice.Master] property. The first slice "{0}" in the list of configured \ + "{0}" property. The first slice "{1}" in the list of configured \ slices will be used as master. resource-xa-tm-not-2pc: All slices is using XA-complaint driver but the \ configured "{0}" transaction manager is not capable of enlisting XA-aware \ diff --git a/openjpa-slice/src/main/resources/org/apache/openjpa/slice/transaction/localizer.properties b/openjpa-slice/src/main/resources/org/apache/openjpa/slice/transaction/localizer.properties index b374bd4c4..0ce630925 100644 --- a/openjpa-slice/src/main/resources/org/apache/openjpa/slice/transaction/localizer.properties +++ b/openjpa-slice/src/main/resources/org/apache/openjpa/slice/transaction/localizer.properties @@ -15,5 +15,5 @@ # specific language governing permissions and limitations # under the License. no-txn-on-thread: No transaction is associated with current thread "{0}" -prepare-failed: one or more XA-complaint resources have failed to prepare for \ - commit during the first phase of a two-phase commit protocol. \ No newline at end of file +prepare-failed: One or more XA-complaint resources have failed to prepare for \ + commit during the first phase of a two-phase commit protocol due to {0} \ No newline at end of file diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/Address.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/Address.java new file mode 100644 index 000000000..4fdbe5010 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/Address.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import javax.persistence.*; + +@Entity +public class Address { + @Id + @GeneratedValue + private long id; + + private String city; + private int zip; + + @OneToOne(mappedBy = "address") + Person owner; + + @Version + private long version; + + public long getVersion() { + return version; + } + + public Address() { + this("?", 0); + } + + public Address(String city, int zip) { + setCity(city); + setZip(zip); + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public int getZip() { + return zip; + } + + public void setZip(int zip) { + this.zip = zip; + } + + public Person getOwner() { + return owner; + } + + public void setOwner(Person owner) { + this.owner = owner; + } + + public long getId() { + return id; + } + + public String toString() { + return city; + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/PObject.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/PObject.java new file mode 100644 index 000000000..93c324dac --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/PObject.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class PObject { + @Id + private long id; + + private int value; + + public PObject() { + this(System.currentTimeMillis()); + } + + public PObject(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public int getValue() { + return value; + } + + public void setValue(int i) { + value = i; + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/PersistenceTestCase.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/PersistenceTestCase.java new file mode 100644 index 000000000..af6c99886 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/PersistenceTestCase.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import junit.framework.TestCase; +import junit.framework.TestResult; +import org.apache.openjpa.kernel.AbstractBrokerFactory; +import org.apache.openjpa.kernel.Broker; +import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; +import org.apache.openjpa.persistence.JPAFacadeHelper; + +/** + * Base test class providing persistence utilities. + */ +public abstract class PersistenceTestCase + extends TestCase { + + /** + * Marker object you an pass to {@link #setUp} to indicate that the + * database tables should be cleared. + */ + protected static final Object CLEAR_TABLES = new Object(); + + /** + * The {@link TestResult} instance for the current test run. + */ + protected TestResult testResult; + + /** + * Create an entity manager factory. Put {@link #CLEAR_TABLES} in + * this list to tell the test framework to delete all table contents + * before running the tests. + * + * @param props list of persistent types used in testing and/or + * configuration values in the form key,value,key,value... + */ + protected OpenJPAEntityManagerFactorySPI createEMF(Object... props) { + return createNamedEMF(getPersistenceUnitName(), props); + } + + /** + * The name of the persistence unit that this test class should use + * by default. This defaults to "test". + */ + protected String getPersistenceUnitName() { + return "test"; + } + + /** + * Create an entity manager factory for persistence unit pu. + * Put {@link #CLEAR_TABLES} in + * this list to tell the test framework to delete all table contents + * before running the tests. + * + * @param props list of persistent types used in testing and/or + * configuration values in the form key,value,key,value... + */ + protected OpenJPAEntityManagerFactorySPI createNamedEMF(String pu, + Object... props) { + Map map = new HashMap(System.getProperties()); + List types = new ArrayList(); + boolean prop = false; + for (int i = 0; i < props.length; i++) { + if (prop) { + map.put(props[i - 1], props[i]); + prop = false; + } else if (props[i] == CLEAR_TABLES) { + map.put("openjpa.jdbc.SynchronizeMappings", + "buildSchema(ForeignKeys=true," + + "SchemaAction='add,deleteTableContents')"); + } else if (props[i] instanceof Class) + types.add((Class) props[i]); + else if (props[i] != null) + prop = true; + } + + if (!types.isEmpty()) { + StringBuffer buf = new StringBuffer(); + for (Class c : types) { + if (buf.length() > 0) + buf.append(";"); + buf.append(c.getName()); + } + map.put("openjpa.MetaDataFactory", + "jpa(Types=" + buf.toString() + ")"); + } + + return (OpenJPAEntityManagerFactorySPI) Persistence. + createEntityManagerFactory(pu, map); + } + + @Override + public void run(TestResult testResult) { + this.testResult = testResult; + super.run(testResult); + } + + @Override + public void tearDown() throws Exception { + try { + super.tearDown(); + } catch (Exception e) { + // if a test failed, swallow any exceptions that happen + // during tear-down, as these just mask the original problem. + if (testResult.wasSuccessful()) + throw e; + } + } + + /** + * Safely close the given factory. + */ + protected boolean closeEMF(EntityManagerFactory emf) { + if (emf == null || !emf.isOpen()) + return false; + + closeAllOpenEMs(emf); + emf.close(); + return !emf.isOpen(); + } + + /** + * Closes all open entity managers after first rolling back any open transactions + */ + protected void closeAllOpenEMs(EntityManagerFactory emf) { + if (emf == null || !emf.isOpen()) + return; + + for (Iterator iter = ((AbstractBrokerFactory) JPAFacadeHelper + .toBrokerFactory(emf)).getOpenBrokers().iterator(); + iter.hasNext(); ) { + Broker b = (Broker) iter.next(); + if (b != null && !b.isClosed()) { + EntityManager em = JPAFacadeHelper.toEntityManager(b); + if (em.getTransaction().isActive()) + em.getTransaction().rollback(); + em.close(); + } + } + } + + /** + * Delete all instances of the given types using bulk delete queries, + * but do not close any open entity managers. + */ + protected void clear(EntityManagerFactory emf, Class... types) { + if (emf == null || types.length == 0) + return; + + List metas = new ArrayList(types.length); + for (Class c : types) { + ClassMetaData meta = JPAFacadeHelper.getMetaData(emf, c); + if (meta != null) + metas.add(meta); + } + clear(emf, false, metas.toArray(new ClassMetaData[metas.size()])); + } + + /** + * Delete all instances of the persistent types registered with the given + * factory using bulk delete queries, after first closing all open entity + * managers (and rolling back any open transactions). + */ + protected void clear(EntityManagerFactory emf) { + if (emf == null) + return; + clear(emf, true, ((OpenJPAEntityManagerFactorySPI) emf).getConfiguration(). + getMetaDataRepositoryInstance().getMetaDatas()); + } + + /** + * Delete all instances of the given types using bulk delete queries. + * @param closeEMs TODO + */ + private void clear(EntityManagerFactory emf, boolean closeEMs, ClassMetaData... types) { + if (emf == null || types.length == 0) + return; + + // prevent deadlock by closing the open entity managers + // and rolling back any open transactions + // before issuing delete statements on a new entity manager. + if (closeEMs) + closeAllOpenEMs(emf); + + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + for (ClassMetaData meta : types) { + if (!meta.isMapped() || meta.isEmbeddedOnly() + || Modifier.isAbstract(meta.getDescribedType().getModifiers())) + continue; + List all = em.createQuery("SELECT o FROM " + meta.getTypeAlias() + " o"). + getResultList(); + for (Object pc:all) + em.remove(pc); + } + em.getTransaction().commit(); + em.close(); + } + + /** + * Return the entity name for the given type. + */ + protected String entityName(EntityManagerFactory emf, Class c) { + ClassMetaData meta = JPAFacadeHelper.getMetaData(emf, c); + return (meta == null) ? null : meta.getTypeAlias(); + } + + public static void assertNotEquals(Object o1, Object o2) { + if (o1 == o2) + fail("expected args to be different; were the same instance."); + else if (o1 == null || o2 == null) + return; + else if (o1.equals(o2)) + fail("expected args to be different; compared equal."); + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/Person.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/Person.java new file mode 100644 index 000000000..36a87d2a3 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/Person.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import javax.persistence.*; + +@Entity +public class Person { + @Id + @GeneratedValue + private long id; + + private String name; + + @Version + private long version; + + @OneToOne(cascade=CascadeType.ALL) + private Address address; + + public Person() { + this("?"); + } + + public Person(String name) { + setName(name); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + address.setOwner(this); + } + + public long getId() { + return id; + } + + public String toString() { + return name; + } + + public long getVersion() { + return version; + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/SingleEMFTestCase.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/SingleEMFTestCase.java new file mode 100644 index 000000000..417edc7de --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/SingleEMFTestCase.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; + +public abstract class SingleEMFTestCase + extends PersistenceTestCase { + + protected OpenJPAEntityManagerFactorySPI emf; + + /** + * Call {@link #setUp(Object...)} with no arguments so that the emf + * set-up happens even if setUp() is not called from the + * subclass. + */ + public void setUp() throws Exception { + setUp(new Object[0]); + } + + /** + * Initialize entity manager factory. Put {@link #CLEAR_TABLES} in + * this list to tell the test framework to delete all table contents + * before running the tests. + * + * @param props list of persistent types used in testing and/or + * configuration values in the form key,value,key,value... + */ + protected void setUp(Object... props) { + emf = createEMF(props); + } + + /** + * Closes the entity manager factory. + */ + public void tearDown() throws Exception { + super.tearDown(); + + if (emf == null) + return; + + try { + clear(emf); + } catch (Exception e) { + // if a test failed, swallow any exceptions that happen + // during tear-down, as these just mask the original problem. + if (testResult.wasSuccessful()) + throw e; + } finally { + closeEMF(emf); + } + } + + protected ClassMapping getMapping(String name) { + return (ClassMapping) emf.getConfiguration() + .getMetaDataRepositoryInstance().getMetaData(name, + getClass().getClassLoader(), true); + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/SliceTestCase.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/SliceTestCase.java new file mode 100644 index 000000000..4697ef8d9 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/SliceTestCase.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import javax.persistence.EntityManager; + +import org.apache.openjpa.slice.jdbc.DistributedJDBCConfiguration; + +public abstract class SliceTestCase extends SingleEMFTestCase { + protected void setUp(Object... props) { + super.setUp(props); + assertTrue(emf.getClass() + " is not a slice configuration. Check" + + " that BrokerFactory for the persistence unit is set to slice", + emf.getConfiguration() instanceof DistributedJDBCConfiguration); + + } + + int count(Class type) { + EntityManager em = emf.createEntityManager(); + String query = "SELECT COUNT(p) FROM " + type.getSimpleName() + " p"; + Number number = (Number) em.createQuery(query).getSingleResult(); + return number.intValue(); + } + +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestBasic.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestBasic.java new file mode 100644 index 000000000..2b6220ae4 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestBasic.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import java.util.List; + +import javax.persistence.EntityManager; + +public class TestBasic extends SliceTestCase { + private static String persistenceUnitName = "slice"; + + public void setUp() throws Exception { + super.setUp(PObject.class, Person.class, Address.class); + } + + PObject persist() { + EntityManager em = emf.createEntityManager(); + int value = (int)(System.currentTimeMillis()%100); + PObject pc = new PObject(); + em.getTransaction().begin(); + em.persist(pc); + pc.setValue(value); + em.getTransaction().commit(); + em.clear(); + return pc; + } + + public void testDelete() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + List all = em.createQuery("SELECT p FROM PObject p").getResultList(); + for (Object pc:all) + em.remove(pc); + em.getTransaction().commit(); + + int count = count(PObject.class); + assertEquals(0, count); + + } + + public void testBulkDelete() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + int c = count(PObject.class); + int d = em.createQuery("DELETE FROM PObject p").executeUpdate(); + assertEquals(c, d); + em.getTransaction().commit(); + + c = count(PObject.class); + assertEquals(0, c); + + } + + /** + * Stores and finds the same object. + */ + public void testFind() { + PObject pc = persist(); + int value = pc.getValue(); + + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + PObject pc2 = em.find(PObject.class, pc.getId()); + assertNotNull(pc2); + assertNotEquals(pc, pc2); + assertEquals(pc.getId(), pc2.getId()); + assertEquals(value, pc2.getValue()); + } + + public void testPersistIndependentObjects() { + int before = count(PObject.class); + EntityManager em = emf.createEntityManager(); + int N = 2; + long start = System.currentTimeMillis(); + em.getTransaction().begin(); + for (int i=0; i persons = em.createQuery("SELECT p FROM Person p WHERE p.name=?1"). + setParameter(1, "A").getResultList(); + List
addresses = em.createQuery("SELECT a FROM Address a").getResultList(); + for (Address pc:addresses) { + assertNotNull(pc.getCity()); + assertNotNull(pc.getOwner().getName()); + } + for (Person pc:persons) { + assertNotNull(pc.getName()); + assertNotNull(pc.getAddress().getCity()); + } + em.getTransaction().rollback(); + } + + /** + * Merge only works if the distribution policy assigns the correct slice + * from which the instance was fetched. + */ + public void testMerge() { + PObject pc = persist(); + int value = pc.getValue(); + pc.setValue(value+1); + assertNotNull(pc); + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + PObject pc2 = em.merge(pc); + em.getTransaction().commit(); + em.clear(); + + assertNotNull(pc2); + assertNotEquals(pc, pc2); + assertEquals(pc.getId(), pc2.getId()); + assertEquals(value+1, pc2.getValue()); + } + + protected String getPersistenceUnitName() { + return persistenceUnitName; + } + +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestConfiguration.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestConfiguration.java new file mode 100644 index 000000000..475a3d049 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestConfiguration.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import java.util.List; + +import org.apache.openjpa.kernel.Broker; +import org.apache.openjpa.kernel.BrokerFactory; +import org.apache.openjpa.persistence.EntityManagerFactoryImpl; +import org.apache.openjpa.slice.jdbc.DistributedJDBCBrokerFactory; +import org.apache.openjpa.slice.jdbc.DistributedJDBCConfiguration; +import org.apache.openjpa.slice.transaction.NaiveTransactionManager; + +/** + * + * @author Pinaki Poddar + * + */ +public class TestConfiguration extends SliceTestCase { + /** + * Tests that user-level configurations are set. + * + */ + public void testConfig() { + assertTrue(emf.getConfiguration() instanceof DistributedConfiguration); + DistributedJDBCConfiguration conf = (DistributedJDBCConfiguration) + emf.getConfiguration(); + List slices = conf.getAvailableSliceNames(); + assertTrue(slices.size()>1); + assertTrue(slices.contains("One")); + assertTrue(slices.contains("Two")); + assertTrue(slices.contains("Three")); + assertEquals("jdbc:mysql://localhost/slice1", conf.getSlice("One").getConfiguration().getConnectionURL()); + assertEquals("jdbc:mysql://localhost/slice2", conf.getSlice("Two").getConfiguration().getConnectionURL()); + assertEquals("jdbc:mysql://localhost/slice3", conf.getSlice("Three").getConfiguration().getConnectionURL()); + assertTrue(conf.getTransactionManagerInstance() instanceof NaiveTransactionManager); + BrokerFactory bf = ((EntityManagerFactoryImpl)emf).getBrokerFactory(); + Broker broker = bf.newBroker(); + assertEquals(DistributedJDBCBrokerFactory.class, bf.getClass()); + assertEquals(DistributedBrokerImpl.class, broker.getClass()); + assertNotNull(conf.getDistributionPolicyInstance()); + + + emf.createEntityManager(); + + slices = conf.getActiveSliceNames(); + assertTrue(slices.size()>1); + assertTrue(slices.contains("One")); + assertTrue(slices.contains("Two")); + assertFalse(slices.contains("Three")); + + conf.getExecutorServiceInstance(); + } + + protected String getPersistenceUnitName() { + return "per-slice"; + } + +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestQuery.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestQuery.java new file mode 100644 index 000000000..db0f4d0a5 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestQuery.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.openjpa.slice.SlicePersistence; + +public class TestQuery extends SliceTestCase { + public void setUp() throws Exception { + super.setUp(PObject.class, Person.class, Address.class); + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + long id = System.currentTimeMillis(); + for (int i=0;i<0;i++) { + PObject pc = new PObject(id++); + pc.setValue(i); + em.persist(pc); + String slice = SlicePersistence.getSlice(pc); + String expected = (i%2 == 0) ? "Even" : "Odd"; + assertEquals(expected, slice); + } + em.getTransaction().commit(); + } + + public void testQueryResultIsOrderedAcrossSlice() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery("SELECT p.value,p FROM PObject p ORDER BY p.value ASC"); + List result = query.getResultList(); + Integer old = Integer.MIN_VALUE; + for (Object row:result) { + Object[] line = (Object[])row; + int value = ((Integer)line[0]).intValue(); + PObject pc = (PObject)line[1]; + assertTrue(value >= old); + old = value; + assertEquals(value, pc.getValue()); + } + em.getTransaction().commit(); + } + + public void testAggregateQuery() { + EntityManager em = emf.createEntityManager(); + List result = em.createQuery("SELECT COUNT(p) FROM PObject p").getResultList(); + for (Object r:result) + System.err.println(r); + } + protected String getPersistenceUnitName() { + return "ordering"; + } +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestXA.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestXA.java new file mode 100644 index 000000000..af236a6e8 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/TestXA.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice; + +import javax.persistence.*; + +public class TestXA extends SliceTestCase { + public void setUp() throws Exception { + super.setUp(PObject.class, Person.class, Address.class); + } + public void testEmptyCommit() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.getTransaction().commit(); + } + public void testEmptyRollback() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.getTransaction().rollback(); + } + + public void testPersistIndependentObjects() { + EntityManager em = emf.createEntityManager(); + int before = count(PObject.class); + int N = 2; + long start = System.currentTimeMillis(); + em.getTransaction().begin(); + for (int i=0; i slices, Object context) { + if (pc instanceof PObject) { + int v = ((PObject)pc).getValue(); + return (v%2 == 0) ? "Even" : "Odd"; + } + return null; + } + +} diff --git a/openjpa-slice/src/test/java/org/apache/openjpa/slice/policy/UserDistributionPolicy.java b/openjpa-slice/src/test/java/org/apache/openjpa/slice/policy/UserDistributionPolicy.java new file mode 100644 index 000000000..d19dd7122 --- /dev/null +++ b/openjpa-slice/src/test/java/org/apache/openjpa/slice/policy/UserDistributionPolicy.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.slice.policy; + +import java.util.List; + +import org.apache.openjpa.slice.DistributionPolicy; + +import org.apache.openjpa.slice.PObject; +import org.apache.openjpa.slice.Person; + + +/** + * Exemplar {@link DistributionPolicy} that maintains closure and distributes + * based on attributes of the given instance. + * + * @author Pinaki Poddar + * + */ +public class UserDistributionPolicy implements DistributionPolicy { + + /** + * Distribute the given instance. + * Assumes that two configured slices are named as One and + * Two.
+ * The policy is only implemented for PObject and Person i.e. two of three + * known classes. No policy is implemented for Address because Address is + * persisted always by cascade and hence Slice should assign automatically + * the same slice as its owner Person. + * + */ + public String distribute(Object pc, List slices, Object context) { + assertValidSlices(slices); + if (pc instanceof PObject) + return distribute((PObject)pc); + if (pc instanceof Person) { + return distribute((Person)pc); + } + throw new RuntimeException("No policy for " + pc.getClass()); + } + + void assertValidSlices(List slices) { + if (slices.contains("One") && slices.contains("Two")) + return; + throw new RuntimeException("This policy assumes two slices named " + + "One and Two. But configured slices are " + slices); + } + + /** + * Distribute PObject based on odd-even value of its id. + */ + String distribute(PObject pc) { + return (pc.getId()%2 == 0) ? "One" : "Two"; + } + + /** + * Distribute Person based on first character of its name. + */ + String distribute(Person pc) { + return (pc.getName().startsWith("A")) ? "One" : "Two"; + } +} diff --git a/openjpa-slice/src/test/resources/META-INF/persistence.xml b/openjpa-slice/src/test/resources/META-INF/persistence.xml new file mode 100644 index 000000000..108bd6007 --- /dev/null +++ b/openjpa-slice/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,118 @@ + + + + + org.apache.openjpa.slice.PObject + org.apache.openjpa.slice.Person + org.apache.openjpa.slice.Address + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.openjpa.slice.PObject + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.openjpa.slice.PObject + org.apache.openjpa.slice.Person + org.apache.openjpa.slice.Address + + + + + + + + + + + + + + + + + + + + + + + org.apache.openjpa.slice.PObject + + + + + + + + + + + + + + + + +