| /* |
| * 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 agEmployee_Last_Name to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR 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.persistence.sequence; |
| |
| import java.io.PrintStream; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| import javax.persistence.EntityManager; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.persistence.test.SingleEMFTestCase; |
| |
| /** |
| * @author Tim McConnell |
| * @since 2.0.0 |
| */ |
| public class TestSequence extends SingleEMFTestCase { |
| |
| private String multiThreadExecuting = null; |
| private static final int NUMBER_ENTITIES = 5000; |
| |
| public void setUp() { |
| setUp(EntityPerson.class, EntityEmployee.class, CLEAR_TABLES, |
| "openjpa.Multithreaded", "true"); |
| } |
| |
| // Override teardown to preserve database contents |
| @Override |
| public void tearDown() throws Exception { |
| } |
| |
| public void testMultiThreadedNativeSequences() throws Exception { |
| boolean supportsNativeSequence = false; |
| |
| try { |
| supportsNativeSequence = ((JDBCConfiguration) emf |
| .getConfiguration()).getDBDictionaryInstance() |
| .nextSequenceQuery != null; |
| } catch (Throwable t) { |
| supportsNativeSequence = false; |
| } |
| |
| if (supportsNativeSequence) { |
| mttest(6, 8); |
| switch ((int) (Math.random() * 7)) { |
| case 0: |
| createAndRemove(); |
| break; |
| case 1: |
| createManyPersonsInSeparateTransactions(); |
| break; |
| case 2: |
| createManyEmployeesInSeparateTransactions(); |
| break; |
| case 3: |
| createManyPersonsAndEmployeesInSeparateTransactions(); |
| break; |
| case 4: |
| createManyPersonsInSingleTransaction(); |
| break; |
| case 5: |
| createManyEmployeesInSingleTransaction(); |
| break; |
| case 6: |
| createManyPersonsAndEmployeesInSingleTransaction(); |
| break; |
| } |
| } |
| } |
| |
| private void createAndRemove() { |
| int person_id; |
| int employee_id; |
| |
| EntityManager em = emf.createEntityManager(); |
| |
| EntityPerson person = new EntityPerson(); |
| person.setFirstName("Person_First_Name"); |
| person.setLastName("Person_Last_Name"); |
| |
| EntityEmployee employee = new EntityEmployee(); |
| employee.setFirstName("Employee_First_Name"); |
| employee.setLastName("Employee_Last_Name"); |
| employee.setSalary(NUMBER_ENTITIES); |
| |
| em.getTransaction().begin(); |
| em.persist(person); |
| em.persist(employee); |
| em.getTransaction().commit(); |
| |
| em.refresh(person); |
| em.refresh(employee); |
| person_id = person.getId(); |
| employee_id = employee.getId(); |
| |
| person = em.find(EntityPerson.class, person_id); |
| assertTrue(person != null); |
| assertTrue(person.getId() == person_id); |
| assertTrue(person.getFirstName().equals("Person_First_Name")); |
| assertTrue(person.getLastName().equals("Person_Last_Name")); |
| |
| employee = em.find(EntityEmployee.class, employee_id); |
| assertTrue(employee != null); |
| assertTrue(employee.getId() == employee_id); |
| assertTrue(employee.getFirstName().equals("Employee_First_Name")); |
| assertTrue(employee.getLastName().equals("Employee_Last_Name")); |
| assertTrue(employee.getSalary() == NUMBER_ENTITIES); |
| |
| em.getTransaction().begin(); |
| em.remove(person); |
| em.remove(employee); |
| em.getTransaction().commit(); |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyPersonsInSeparateTransactions() { |
| EntityManager em = emf.createEntityManager(); |
| |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityPerson person = new EntityPerson(); |
| person.setFirstName("1_First_name_" + ii); |
| person.setLastName("1_Last_name_" + ii); |
| |
| em.getTransaction().begin(); |
| em.persist(person); |
| em.getTransaction().commit(); |
| } |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyEmployeesInSeparateTransactions() { |
| EntityManager em = emf.createEntityManager(); |
| |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityEmployee employee = new EntityEmployee(); |
| employee.setFirstName("2_First_name_" + ii); |
| employee.setLastName("2_Last_name_" + ii); |
| employee.setSalary(ii); |
| |
| em.getTransaction().begin(); |
| em.persist(employee); |
| em.getTransaction().commit(); |
| } |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyPersonsAndEmployeesInSeparateTransactions() { |
| EntityManager em = emf.createEntityManager(); |
| |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityPerson person = new EntityPerson(); |
| person.setFirstName("3_First_name_" + ii); |
| person.setLastName("3_Last_name_" + ii); |
| |
| EntityEmployee employee = new EntityEmployee(); |
| employee.setFirstName("4_First_name_" + ii); |
| employee.setLastName("4_Last_name_" + ii); |
| employee.setSalary(ii); |
| |
| em.getTransaction().begin(); |
| em.persist(person); |
| em.persist(employee); |
| em.getTransaction().commit(); |
| } |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyPersonsInSingleTransaction() { |
| EntityManager em = emf.createEntityManager(); |
| |
| em.getTransaction().begin(); |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityPerson person = new EntityPerson(); |
| person.setFirstName("5_First_name_" + ii); |
| person.setLastName("5_Last_name_" + ii); |
| |
| em.persist(person); |
| } |
| em.getTransaction().commit(); |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyEmployeesInSingleTransaction() { |
| EntityManager em = emf.createEntityManager(); |
| |
| em.getTransaction().begin(); |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityEmployee employee = new EntityEmployee(); |
| employee.setFirstName("6_First_name_" + ii); |
| employee.setLastName("6_Last_name_" + ii); |
| employee.setSalary(ii); |
| |
| em.persist(employee); |
| } |
| em.getTransaction().commit(); |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| private void createManyPersonsAndEmployeesInSingleTransaction() { |
| EntityManager em = emf.createEntityManager(); |
| |
| em.getTransaction().begin(); |
| for (int ii = 0; ii < NUMBER_ENTITIES; ii++) { |
| EntityPerson person = new EntityPerson(); |
| person.setFirstName("7_First_name_" + ii); |
| person.setLastName("7_Last_name_" + ii); |
| |
| EntityEmployee employee = new EntityEmployee(); |
| employee.setFirstName("8_First_name_" + ii); |
| employee.setLastName("8_Last_name_" + ii); |
| employee.setSalary(ii); |
| |
| em.persist(person); |
| em.persist(employee); |
| } |
| em.getTransaction().commit(); |
| |
| em.clear(); |
| em.close(); |
| } |
| |
| /** |
| * Re-execute the invoking method a random number of times in a random |
| * number of Threads. |
| */ |
| public void mttest() throws ThreadingException { |
| // 6 iterations in 8 threads is a good trade-off between |
| // tests taking way too long and having a decent chance of |
| // identifying MT problems. |
| int iterations = 6; |
| int threads = 8; |
| |
| mttest(threads, iterations); |
| } |
| |
| /** |
| * Execute the calling method <code>iterations</code> times in |
| * <code>threads</code> Threads. |
| */ |
| public void mttest(int threads, int iterations) { |
| mttest(0, threads, iterations); |
| } |
| |
| public void mttest(int serialCount, int threads, int iterations) |
| throws ThreadingException { |
| String methodName = callingMethod("mttest"); |
| mttest(serialCount, threads, iterations, methodName, new Object[0]); |
| } |
| |
| /** |
| * Execute a test method in multiple threads. |
| * |
| * @param threads |
| * the number of Threads to run in |
| * @param iterations |
| * the number of times the method should be execute in a single |
| * Thread |
| * @param method |
| * the name of the method to execute |
| * @param args |
| * the arguments to pass to the method |
| * @throws ThreadingException |
| * if an errors occur in any of the Threads. The actual |
| * exceptions will be embedded in the exception. Note that this |
| * means that assert() failures will be treated as errors rather |
| * than warnings. |
| * @author Marc Prud'hommeaux |
| */ |
| public void mttest(int threads, int iterations, final String method, |
| final Object[] args) throws ThreadingException { |
| mttest(0, threads, iterations, method, args); |
| } |
| |
| public void mttest(int serialCount, int threads, int iterations, |
| final String method, final Object[] args) throws ThreadingException { |
| if (multiThreadExecuting != null |
| && multiThreadExecuting.equals(method)) { |
| // we are currently executing in multi-threaded mode: |
| // don't deadlock! |
| return; |
| } |
| |
| multiThreadExecuting = method; |
| |
| try { |
| Class<?>[] paramClasses = new Class[args.length]; |
| for (int i = 0; i < paramClasses.length; i++) |
| paramClasses[i] = args[i].getClass(); |
| |
| final Method meth; |
| |
| try { |
| meth = getClass().getMethod(method, paramClasses); |
| } catch (NoSuchMethodException nsme) { |
| throw new ThreadingException(nsme.toString(), nsme); |
| } |
| |
| final Object thiz = this; |
| |
| mttest("reflection invocation: (" + method + ")", serialCount, |
| threads, iterations, new VolatileRunnable() { |
| public void run() throws Exception { |
| meth.invoke(thiz, args); |
| } |
| }); |
| } finally { |
| multiThreadExecuting = null; |
| } |
| } |
| |
| public void mttest(String title, final int threads, final int iterations, |
| final VolatileRunnable runner) throws ThreadingException { |
| mttest(title, 0, threads, iterations, runner); |
| } |
| |
| /** |
| * Execute a test method in multiple threads. |
| * |
| * @param title |
| * a description of the test, for inclusion in the error message |
| * @param serialCount |
| * the number of times to run the method serially before spawning |
| * threads. |
| * @param threads |
| * the number of Threads to run in |
| * @param iterations |
| * the number of times the method should |
| * @param runner |
| * the VolatileRunnable that will execute the actual test from |
| * within the Thread. |
| * @throws ThreadingException |
| * if an errors occur in any of the Threads. The actual |
| * exceptions will be embedded in the exception. Note that this |
| * means that assert() failures will be treated as errors rather |
| * than warnings. |
| * @author Marc Prud'hommeaux |
| */ |
| public void mttest(String title, final int serialCount, final int threads, |
| final int iterations, final VolatileRunnable runner) |
| throws ThreadingException { |
| final List exceptions = Collections.synchronizedList(new LinkedList()); |
| |
| Thread[] runners = new Thread[threads]; |
| |
| final long startMillis = System.currentTimeMillis() + 1000; |
| |
| for (int i = 1; i <= threads; i++) { |
| final int thisThread = i; |
| |
| runners[i - 1] = new Thread(title + " [" + i + " of " + threads |
| + "]") { |
| public void run() { |
| // do our best to have all threads start at the exact |
| // same time. This is imperfect, but the closer we |
| // get to everyone starting at the same time, the |
| // better chance we have for identifying MT problems. |
| while (System.currentTimeMillis() < startMillis) |
| yield(); |
| |
| int thisIteration = 1; |
| try { |
| for (; thisIteration <= iterations; thisIteration++) { |
| // go go go! |
| runner.run(); |
| } |
| } catch (Throwable error) { |
| synchronized (exceptions) { |
| // embed the exception into something that gives |
| // us some more information about the threading |
| // environment |
| exceptions.add(new ThreadingException("thread=" |
| + this.toString() + ";threadNum=" + thisThread |
| + ";maxThreads=" + threads + ";iteration=" |
| + thisIteration + ";maxIterations=" |
| + iterations, error)); |
| } |
| } |
| } |
| }; |
| } |
| |
| // start the serial tests(does not spawn the threads) |
| for (int i = 0; i < serialCount; i++) { |
| runners[0].run(); |
| } |
| |
| // start the multithreaded |
| for (int i = 0; i < threads; i++) { |
| runners[i].start(); |
| } |
| |
| // wait for them all to complete |
| for (int i = 0; i < threads; i++) { |
| try { |
| runners[i].join(); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| if (exceptions.size() == 0) |
| return; // sweeeeeeeet: no errors |
| |
| // embed all the exceptions that were throws into a |
| // ThreadingException |
| Throwable[] errors = (Throwable[]) exceptions.toArray(new Throwable[0]); |
| throw new ThreadingException("The " + errors.length |
| + " embedded errors " + "occured in the execution of " + iterations |
| + " iterations " + "of " + threads + " threads: [" + title + "]", |
| errors); |
| } |
| |
| /** |
| * Check to see if we are in the top-level execution stack. |
| */ |
| public boolean isRootThread() { |
| return multiThreadExecuting == null; |
| } |
| |
| /** |
| * A Runnable that can throw an Exception: used to test cases. |
| */ |
| public static interface VolatileRunnable { |
| |
| public void run() throws Exception; |
| } |
| |
| /** |
| * Exception for errors caught during threading tests. |
| */ |
| public class ThreadingException extends RuntimeException { |
| |
| private static final long serialVersionUID = -1911769845552507956L; |
| private final Throwable[] _nested; |
| |
| public ThreadingException(String msg, Throwable nested) { |
| super(msg); |
| if (nested == null) |
| _nested = new Throwable[0]; |
| else |
| _nested = new Throwable[] { nested }; |
| } |
| |
| public ThreadingException(String msg, Throwable[] nested) { |
| super(msg); |
| if (nested == null) |
| _nested = new Throwable[0]; |
| else |
| _nested = nested; |
| } |
| |
| public void printStackTrace() { |
| printStackTrace(System.out); |
| } |
| |
| public void printStackTrace(PrintStream out) { |
| printStackTrace(new PrintWriter(out)); |
| } |
| |
| public void printStackTrace(PrintWriter out) { |
| super.printStackTrace(out); |
| for (int i = 0; i < _nested.length; i++) { |
| out.print("Nested Throwable #" + (i + 1) + ": "); |
| _nested[i].printStackTrace(out); |
| } |
| } |
| } |
| |
| /** |
| * Return the last method name that called this one by parsing the current |
| * stack trace. |
| * |
| * @param exclude |
| * a method name to skip |
| * @throws IllegalStateException |
| * If the calling method could not be identified. |
| * @author Marc Prud'hommeaux |
| */ |
| public String callingMethod(String exclude) { |
| // determine the currently executing method by |
| // looking at the stack track. Hackish, but convenient. |
| StringWriter sw = new StringWriter(); |
| new Exception().printStackTrace(new PrintWriter(sw)); |
| for (StringTokenizer stackTrace = new StringTokenizer(sw.toString(), |
| System.getProperty("line.separator")) |
| ; stackTrace.hasMoreTokens() ; ) { |
| String line = stackTrace.nextToken().trim(); |
| |
| // not a stack trace element |
| if (!(line.startsWith("at "))) |
| continue; |
| |
| String fullMethodName = line.substring(0, line.indexOf("(")); |
| |
| String shortMethodName = fullMethodName.substring(fullMethodName |
| .lastIndexOf(".") + 1); |
| |
| // skip our own methods! |
| if (shortMethodName.equals("callingMethod")) |
| continue; |
| if (exclude != null && shortMethodName.equals(exclude)) |
| continue; |
| |
| return shortMethodName; |
| } |
| |
| throw new IllegalStateException("Could not identify calling " |
| + "method in stack trace"); |
| } |
| } |