2 Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 package com.mysql.clusterj.jpatest;
20 import java.sql.Connection;
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.util.ArrayList;
25 import java.util.Calendar;
26 import java.util.List;
27 import java.util.TimeZone;
29 import javax.persistence.Query;
31 import org.apache.openjpa.persistence.OpenJPAEntityManager;
33 import com.mysql.clusterj.jpatest.model.Employee;
34 import com.mysql.clusterj.jpatest.model.IdBase;
39 public abstract class AbstractJPABaseTest extends SingleEMTestCase {
41 /** The local system default time zone, which is reset by resetLocalSystemDefaultTimeZone */
42 protected static TimeZone localSystemTimeZone = TimeZone.getDefault();
44 /** The connection to the database */
45 protected Connection connection;
47 /** The column descriptors */
48 private ColumnDescriptor[] columnDescriptors = getColumnDescriptors();
50 /** The instances used in the tests, generated by generateInstances */
51 List<IdBase> instances = new ArrayList<IdBase>();
53 /** List of expected results, generated by generateInstances */
54 private List<Object[]> expected = null;
57 protected static boolean debug;
59 public AbstractJPABaseTest() {
63 protected void getConnection() {
64 connection = (Connection) ((OpenJPAEntityManager)em).getConnection();
65 setAutoCommit(connection, false);
68 protected void setAutoCommit(Connection connection, boolean b) {
70 connection.setAutoCommit(false);
71 } catch (SQLException e) {
72 throw new RuntimeException("setAutoCommit failed", e);
76 public void deleteAll() {
77 // delete all instances without verifying
78 em = emf.createEntityManager();
80 for (int i = 0; i < getNumberOfEmployees(); ++i) {
81 Employee e = em.find(Employee.class, i);
89 public void verifyDeleteAll() {
90 em = emf.createEntityManager();
92 for (int i = 0; i < getNumberOfEmployees(); ++i) {
93 Employee e = em.find(Employee.class, i);
95 error("Entity exists after being removed: " + i);
102 public void createAll() {
103 em = emf.createEntityManager();
105 for (int i = 0; i < getNumberOfEmployees(); ++i) {
106 Employee e = new Employee();
110 e.setName("Employee " + i);
117 public void findAll() {
118 em = emf.createEntityManager();
120 for (int i = 0; i < getNumberOfEmployees(); ++i) {
121 Employee e = em.find(Employee.class, i);
122 verifyEmployee(e, 0);
128 public void updateThenVerifyAll() {
129 em = emf.createEntityManager();
131 for (int i = 0; i < getNumberOfEmployees(); ++i) {
132 Employee e = em.find(Employee.class, i);
134 verifyEmployee(e, 1);
138 for (int i = 0; i < getNumberOfEmployees(); ++i) {
139 Employee e = em.find(Employee.class, i);
140 verifyEmployee(e, 1);
146 public void deleteThenVerifyAll() {
147 em = emf.createEntityManager();
149 for (int i = 0; i < getNumberOfEmployees(); ++i) {
150 Employee e = em.find(Employee.class, i);
151 verifyEmployee(e, 1);
156 for (int i = 0; i < getNumberOfEmployees(); ++i) {
157 Employee e = em.find(Employee.class, i);
159 error("Entity exists after being removed: " + i);
166 protected void verifyEmployee(Employee e, int updateOffset) {
168 errorIfNotEqual("Error in age", i + updateOffset, e.getAge().intValue());
169 errorIfNotEqual("Error in magic", i, e.getMagic());
170 errorIfNotEqual("Error in name", "Employee " + i, e.getName());
172 protected int getNumberOfEmployees() {
176 /** Convert year, month, day, hour, minute, second into milliseconds after the Epoch, UCT.
177 * @param year the year
178 * @param month the month (0 for January)
179 * @param day the day of the month
180 * @param hour the hour of the day
181 * @param minute the minute
182 * @param second the second
185 protected static long getMillisFor(int year, int month, int day, int hour, int minute, int second) {
186 Calendar calendar = Calendar.getInstance();
188 calendar.set(Calendar.YEAR, year);
189 calendar.set(Calendar.MONTH, month);
190 calendar.set(Calendar.DATE, day);
191 calendar.set(Calendar.HOUR, hour);
192 calendar.set(Calendar.MINUTE, minute);
193 calendar.set(Calendar.SECOND, second);
194 calendar.set(Calendar.MILLISECOND, 0);
195 long result = calendar.getTimeInMillis();
199 /** Convert year, month, day into milliseconds after the Epoch, UCT.
200 * Set hours, minutes, seconds, and milliseconds to zero.
201 * @param year the year
202 * @param month the month (0 for January)
203 * @param day the day of the month
206 protected static long getMillisFor(int year, int month, int day) {
207 Calendar calendar = Calendar.getInstance();
209 calendar.set(Calendar.YEAR, year);
210 calendar.set(Calendar.MONTH, month);
211 calendar.set(Calendar.DATE, day);
212 calendar.set(Calendar.HOUR, 0);
213 calendar.set(Calendar.MINUTE, 0);
214 calendar.set(Calendar.SECOND, 0);
215 calendar.set(Calendar.MILLISECOND, 0);
216 long result = calendar.getTimeInMillis();
220 /** Convert days, hours, minutes, and seconds into milliseconds after the Epoch, UCT.
221 * Date is index origin 1 so add one to the number of days. Default year and month,
222 * as these are assumed by Calendar to be the Epoch.
223 * @param day the number of days
224 * @param hour the hour (or number of hours)
225 * @param minute the minute (or number of minutes)
226 * @param second the second (or number of seconds)
227 * @return millis past the Epoch UCT
229 protected static long getMillisFor(int days, int hour, int minute, int second) {
230 Calendar calendar = Calendar.getInstance();
232 calendar.set(Calendar.DATE, days + 1);
233 calendar.set(Calendar.HOUR, hour);
234 calendar.set(Calendar.MINUTE, minute);
235 calendar.set(Calendar.SECOND, second);
236 calendar.set(Calendar.MILLISECOND, 0);
237 long result = calendar.getTimeInMillis();
241 /** Reset the local system default time zone to the time zone used
242 * by the MySQL server. This guarantees that there is no time zone
243 * offset between the time zone in the client and the time zone
247 protected static void resetLocalSystemDefaultTimeZone(Connection connection) {
249 PreparedStatement statement = connection.prepareStatement("select @@global.time_zone, @@global.system_time_zone, @@session.time_zone");
250 ResultSet rs = statement.executeQuery();
251 // there are two columns in the result
253 String globalTimeZone = rs.getString(1);
254 String globalSystemTimeZone = rs.getString(2);
255 String sessionTimeZone = rs.getString(3);
256 if (debug) System.out.println("Global time zone: " + globalTimeZone +
257 " Global system time zone: " + globalSystemTimeZone +" Session time zone: " + sessionTimeZone);
259 if ("SYSTEM".equalsIgnoreCase(globalTimeZone)) {
260 globalTimeZone = globalSystemTimeZone;
262 globalTimeZone = "GMT" + globalTimeZone;
264 localSystemTimeZone = TimeZone.getTimeZone(globalTimeZone);
265 if (debug) System.out.println("Local system time zone set to: " + globalTimeZone + "(" + localSystemTimeZone + ")");
266 TimeZone.setDefault(localSystemTimeZone);
267 // get a new connection after setting local default time zone
268 // because a connection contains a session calendar used to create Timestamp instances
270 } catch (SQLException e) {
271 throw new RuntimeException("setServerTimeZone failed", e);
275 /** This class describes columns and fields for a table and model class.
276 * A subclass will instantiate instances of this class and provide handlers to
277 * read and write fields and columns via methods defined in the instance handler.
279 protected static class ColumnDescriptor {
281 private String columnName;
283 protected InstanceHandler instanceHandler;
285 public String getColumnName() {
289 public Object getResultSetValue(ResultSet rs, int j) throws SQLException {
290 return instanceHandler.getResultSetValue(rs, j);
293 public Object getFieldValue(IdBase instance) {
294 return instanceHandler.getFieldValue(instance);
297 public void setFieldValue(IdBase instance, Object value) {
298 this.instanceHandler.setFieldValue(instance, value);
301 public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)
302 throws SQLException {
303 instanceHandler.setPreparedStatementValue(preparedStatement, j, value);
306 protected ColumnDescriptor(String name, InstanceHandler instanceHandler) {
307 this.columnName = name;
308 this.instanceHandler = instanceHandler;
312 protected interface InstanceHandler {
313 void setFieldValue(IdBase instance, Object value);
314 Object getResultSetValue(ResultSet rs, int j)
316 Object getFieldValue(IdBase instance);
317 public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)
321 /** Subclasses can override this method to get debugging info printed to System.out */
322 protected boolean getDebug() {
326 /** Subclasses usually should not override this method to provide the list of expected results */
327 protected List<Object[]> getExpected() {
331 /** Subclasses must override this method to provide the name of the table for the test */
332 protected String getTableName() {
336 /** Subclasses must override this method to provide the number of instances to create */
337 protected int getNumberOfInstances() {
341 /** Subclasses must override this method to provide the column descriptors for the test */
342 protected ColumnDescriptor[] getColumnDescriptors() {
346 /** Subclasses must override this method to implement the model factory for the test */
347 protected IdBase getNewInstance(Class<? extends IdBase> modelClass) {
351 /** Subclasses must override this method to provide the model class for the test */
352 protected Class<? extends IdBase> getModelClass() {
356 /** Subclasses must override this method to provide values for rows (i) and columns (j) */
357 protected Object getColumnValue(int i, int j) {
361 /** Generated instances to persist. When using JDBC, the data is obtained from the instance
362 * via the column descriptors. As a side effect (!) create the list of expected results from read.
363 * @param columnDescriptors the column descriptors
364 * @return the generated instances
366 protected void generateInstances(ColumnDescriptor[] columnDescriptors) {
367 Class<? extends IdBase> modelClass = getModelClass();
368 expected = new ArrayList<Object[]>();
369 instances = new ArrayList<IdBase>();
370 IdBase instance = null;
371 int numberOfInstances = getNumberOfInstances();
372 for (int i = 0; i < numberOfInstances; ++i) {
373 // create the instance
374 instance = getNewInstance(modelClass);
376 // create the expected result row
378 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
379 Object value = getColumnValue(i, j);
380 // set the column value in the instance
381 columnDescriptor.setFieldValue(instance, value);
382 // set the column value in the expected result
383 if (debug) System.out.println("generateInstances set field " + columnDescriptor.getColumnName() + " to value " + value);
386 instances.add(instance);
387 Object[] expectedRow = createRow(columnDescriptors, instance);
388 expected.add(expectedRow);
390 if (debug) System.out.println("Created " + instances.size() + " instances.");
393 /** Verify that the actual results match the expected results. If not, use the multiple error
394 * reporting method errorIfNotEqual defined in the superclass.
395 * @param where the location of the verification of results, normally the name of the test method
396 * @param expecteds the expected results
397 * @param actuals the actual results
399 protected void verify(String where, List<Object[]> expecteds, List<Object[]> actuals) {
400 for (int i = 0; i < expecteds.size(); ++i) {
401 Object[] expected = expecteds.get(i);
402 Object[] actual = actuals.get(i);
403 errorIfNotEqual(where + " got failure on id for row " + i, i, actual[0]);
404 for (int j = 1; j < expected.length; ++j) {
405 errorIfNotEqual(where + " got failure to match column data for row "
406 + i + " column " + j,
407 expected[j], actual[j]);
412 protected void removeAll(Class<? extends IdBase> modelClass) {
413 Query query = em.createQuery("DELETE FROM " + modelClass.getSimpleName());
414 em.getTransaction().begin();
415 query.executeUpdate();
416 em.getTransaction().commit();
419 private void removeAll(String tableName) {
422 PreparedStatement statement = connection.prepareStatement("DELETE FROM " + tableName);
425 } catch (SQLException e) {
426 // TODO Auto-generated catch block
431 /** Write data via JDBC and read back the data via JPA */
432 protected void writeJDBCreadJPA() {
433 generateInstances(columnDescriptors);
434 removeAll(getTableName());
435 List<Object[]> result = null;
436 writeToJDBC(columnDescriptors, instances);
437 result = readFromJPA(columnDescriptors);
438 verify("writeJDBCreadJPA", getExpected(), result);
441 /** Write data via JDBC and read back the data via JDBC */
442 protected void writeJDBCreadJDBC() {
443 generateInstances(columnDescriptors);
444 removeAll(getTableName());
445 List<Object[]> result = null;
446 writeToJDBC(columnDescriptors, instances);
447 result = readFromJDBC(columnDescriptors);
448 verify("writeJDBCreadJDBC", getExpected(), result);
451 /** Write data via JPA and read back the data via JPA */
452 protected void writeJPAreadJPA() {
453 generateInstances(columnDescriptors);
454 removeAll(getModelClass());
455 List<Object[]> result = null;
456 writeToJPA(columnDescriptors, instances);
457 result = readFromJPA(columnDescriptors);
458 verify("writeJPAreadJPA", getExpected(), result);
461 /** Write data via JPA and read back the data via JDBC */
462 protected void writeJPAreadJDBC() {
463 generateInstances(columnDescriptors);
464 removeAll(getTableName());
465 List<Object[]> result = null;
466 writeToJPA(columnDescriptors, instances);
467 result = readFromJDBC(columnDescriptors);
468 verify("writeJPAreadJDBC", getExpected(), result);
471 /** Write data via JPA */
472 protected void writeToJPA(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) {
473 em.getTransaction().begin();
474 for (IdBase instance: instances) {
475 em.persist(instance);
477 em.getTransaction().commit();
480 /** Write data to JDBC. */
481 protected void writeToJDBC(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) {
482 String tableName = getTableName();
483 StringBuffer buffer = new StringBuffer("INSERT INTO ");
484 buffer.append(tableName);
485 buffer.append(" (id");
486 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
488 buffer.append(columnDescriptor.getColumnName());
490 buffer.append(") VALUES (?");
491 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
492 buffer.append(", ?");
495 String statement = buffer.toString();
496 if (debug) System.out.println(statement);
498 PreparedStatement preparedStatement = null;
501 preparedStatement = connection.prepareStatement(statement);
502 if (debug) System.out.println(preparedStatement.toString());
503 for (i = 0; i < instances.size(); ++i) {
504 IdBase instance = instances.get(i);
505 preparedStatement.setInt(1, instance.getId());
507 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
508 Object value = columnDescriptor.getFieldValue(instance);
509 columnDescriptor.setPreparedStatementValue(preparedStatement, j++, value);
510 if (debug) System.out.println("writeToJDBC set column: " + columnDescriptor.getColumnName() + " to value: " + value);
512 preparedStatement.execute();
515 } catch (SQLException e) {
516 throw new RuntimeException("Failed to insert " + tableName + " at instance " + i, e);
520 /** Read data via JPA */
521 protected List<Object[]> readFromJPA(ColumnDescriptor[] columnDescriptors) {
522 Class<? extends IdBase> modelClass = getModelClass();
523 List<Object[]> result = new ArrayList<Object[]>();
524 em.getTransaction().begin();
525 for (int i = 0; i < getNumberOfInstances() ; ++i) {
526 IdBase instance = em.find(modelClass, i);
527 if (instance != null) {
528 Object[] row = createRow(columnDescriptors, instance);
532 em.getTransaction().commit();
533 if (debug) System.out.println("readFromJPA: " + dump(result));
537 /** Read data via JDBC */
538 protected List<Object[]> readFromJDBC(ColumnDescriptor[] columnDescriptors) {
539 String tableName = getTableName();
540 List<Object[]> result = new ArrayList<Object[]>();
541 StringBuffer buffer = new StringBuffer("SELECT id");
542 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
544 buffer.append(columnDescriptor.getColumnName());
546 buffer.append(" FROM ");
547 buffer.append(tableName);
548 buffer.append(" ORDER BY ID");
549 String statement = buffer.toString();
550 if (debug) System.out.println(statement);
551 PreparedStatement preparedStatement = null;
554 preparedStatement = connection.prepareStatement(statement);
555 ResultSet rs = preparedStatement.executeQuery();
557 Object[] row = new Object[columnDescriptors.length + 1];
559 row[0] = rs.getInt(1);
560 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
561 row[j] = columnDescriptor.getResultSetValue(rs, j + 1);
568 } catch (SQLException e) {
569 throw new RuntimeException("Failed to read " + tableName + " at instance " + i, e);
571 if (debug) System.out.println("readFromJDBC: " + dump(result));
575 /** Create row data from an instance.
576 * @param columnDescriptors the column descriptors describing the data
577 * @param instance the instance to extract data from
578 * @return the row data representing the instance
580 private Object[] createRow(ColumnDescriptor[] columnDescriptors,
582 Object[] row = new Object[columnDescriptors.length + 1];
583 row[0] = instance.getId();
585 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
586 row[j++] = columnDescriptor.getFieldValue(instance);
591 /** Dump the contents of the expected or actual results of the operation */
592 private String dump(List<Object[]> results) {
593 StringBuffer result = new StringBuffer(results.size() + " rows\n");
594 for (Object[] row: results) {
595 result.append("Id: ");
596 for (Object column: row) {
597 result.append(column);
602 return result.toString();