1 /* 2 Copyright (c) 2010, 2021, Oracle and/or its affiliates. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License, version 2.0, 6 as published by the Free Software Foundation. 7 8 This program is also distributed with certain software (including 9 but not limited to OpenSSL) that is licensed under separate terms, 10 as designated in a particular file or component or in included license 11 documentation. The authors of MySQL hereby grant you an additional 12 permission to link the program and your derivative works with the 13 separately licensed software that they have included with MySQL. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License, version 2.0, for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 */ 24 25 package com.mysql.clusterj.jpatest; 26 27 import java.sql.Connection; 28 import java.sql.PreparedStatement; 29 import java.sql.ResultSet; 30 import java.sql.SQLException; 31 import java.util.ArrayList; 32 import java.util.Calendar; 33 import java.util.List; 34 import java.util.TimeZone; 35 36 import javax.persistence.Query; 37 38 import org.apache.openjpa.persistence.OpenJPAEntityManager; 39 40 import com.mysql.clusterj.jpatest.model.Employee; 41 import com.mysql.clusterj.jpatest.model.IdBase; 42 43 /** 44 * 45 */ 46 public abstract class AbstractJPABaseTest extends SingleEMTestCase { 47 48 /** The local system default time zone, which is reset by resetLocalSystemDefaultTimeZone */ 49 protected static TimeZone localSystemTimeZone = TimeZone.getDefault(); 50 51 /** The connection to the database */ 52 protected Connection connection; 53 54 /** The column descriptors */ 55 private ColumnDescriptor[] columnDescriptors = getColumnDescriptors(); 56 57 /** The instances used in the tests, generated by generateInstances */ 58 List<IdBase> instances = new ArrayList<IdBase>(); 59 60 /** List of expected results, generated by generateInstances */ 61 private List<Object[]> expected = null; 62 63 /** Debug flag */ 64 protected static boolean debug; 65 AbstractJPABaseTest()66 public AbstractJPABaseTest() { 67 debug = getDebug(); 68 } 69 getConnection()70 protected void getConnection() { 71 connection = (Connection) ((OpenJPAEntityManager)em).getConnection(); 72 setAutoCommit(connection, false); 73 } 74 setAutoCommit(Connection connection, boolean b)75 protected void setAutoCommit(Connection connection, boolean b) { 76 try { 77 connection.setAutoCommit(false); 78 } catch (SQLException e) { 79 throw new RuntimeException("setAutoCommit failed", e); 80 } 81 } 82 deleteAll()83 public void deleteAll() { 84 // delete all instances without verifying 85 em = emf.createEntityManager(); 86 begin(); 87 for (int i = 0; i < getNumberOfEmployees(); ++i) { 88 Employee e = em.find(Employee.class, i); 89 if (e != null) { 90 em.remove(e); 91 } 92 } 93 commit(); 94 em.close(); 95 } verifyDeleteAll()96 public void verifyDeleteAll() { 97 em = emf.createEntityManager(); 98 begin(); 99 for (int i = 0; i < getNumberOfEmployees(); ++i) { 100 Employee e = em.find(Employee.class, i); 101 if (e != null) { 102 error("Entity exists after being removed: " + i); 103 } 104 } 105 commit(); 106 failOnError(); 107 em.close(); 108 } 109 createEmployeeInstances()110 public void createEmployeeInstances() { 111 for (int i = 0; i < getNumberOfEmployees(); ++i) { 112 System.out.println("AbstractJPABaseTest creating Employee " + i); 113 Employee e = new Employee(); 114 e.setId(i); 115 e.setAge(i); 116 e.setMagic(i); 117 e.setName("Employee " + i); 118 em.persist(e); 119 } 120 } 121 createAll()122 public void createAll() { 123 em = emf.createEntityManager(); 124 begin(); 125 for (int i = 0; i < getNumberOfEmployees(); ++i) { 126 Employee e = new Employee(); 127 e.setId(i); 128 e.setAge(i); 129 e.setMagic(i); 130 e.setName("Employee " + i); 131 em.persist(e); 132 } 133 commit(); 134 em.close(); 135 } 136 findAll()137 public void findAll() { 138 em = emf.createEntityManager(); 139 begin(); 140 for (int i = 0; i < getNumberOfEmployees(); ++i) { 141 Employee e = em.find(Employee.class, i); 142 verifyEmployee(e, 0); 143 } 144 commit(); 145 em.close(); 146 } 147 updateThenVerifyAll()148 public void updateThenVerifyAll() { 149 em = emf.createEntityManager(); 150 begin(); 151 for (int i = 0; i < getNumberOfEmployees(); ++i) { 152 Employee e = em.find(Employee.class, i); 153 e.setAge(i + 1); 154 verifyEmployee(e, 1); 155 } 156 commit(); 157 begin(); 158 for (int i = 0; i < getNumberOfEmployees(); ++i) { 159 Employee e = em.find(Employee.class, i); 160 verifyEmployee(e, 1); 161 } 162 commit(); 163 em.close(); 164 } 165 deleteThenVerifyAll()166 public void deleteThenVerifyAll() { 167 em = emf.createEntityManager(); 168 begin(); 169 for (int i = 0; i < getNumberOfEmployees(); ++i) { 170 Employee e = em.find(Employee.class, i); 171 verifyEmployee(e, 1); 172 em.remove(e); 173 } 174 commit(); 175 begin(); 176 for (int i = 0; i < getNumberOfEmployees(); ++i) { 177 Employee e = em.find(Employee.class, i); 178 if (e != null) { 179 error("Entity exists after being removed: " + i); 180 } 181 } 182 commit(); 183 em.close(); 184 } 185 verifyEmployee(Employee e, int updateOffset)186 protected void verifyEmployee(Employee e, int updateOffset) { 187 int i = e.getId(); 188 errorIfNotEqual("Error in age", i + updateOffset, e.getAge().intValue()); 189 errorIfNotEqual("Error in magic", i, e.getMagic()); 190 errorIfNotEqual("Error in name", "Employee " + i, e.getName()); 191 } getNumberOfEmployees()192 protected int getNumberOfEmployees() { 193 return 1; 194 } 195 196 /** Convert year, month, day, hour, minute, second into milliseconds after the Epoch, UCT. 197 * @param year the year 198 * @param month the month (0 for January) 199 * @param day the day of the month 200 * @param hour the hour of the day 201 * @param minute the minute 202 * @param second the second 203 * @return 204 */ getMillisFor(int year, int month, int day, int hour, int minute, int second)205 protected static long getMillisFor(int year, int month, int day, int hour, int minute, int second) { 206 Calendar calendar = Calendar.getInstance(); 207 calendar.clear(); 208 calendar.set(Calendar.YEAR, year); 209 calendar.set(Calendar.MONTH, month); 210 calendar.set(Calendar.DATE, day); 211 calendar.set(Calendar.HOUR, hour); 212 calendar.set(Calendar.MINUTE, minute); 213 calendar.set(Calendar.SECOND, second); 214 calendar.set(Calendar.MILLISECOND, 0); 215 long result = calendar.getTimeInMillis(); 216 return result; 217 } 218 219 /** Convert year, month, day into milliseconds after the Epoch, UCT. 220 * Set hours, minutes, seconds, and milliseconds to zero. 221 * @param year the year 222 * @param month the month (0 for January) 223 * @param day the day of the month 224 * @return 225 */ getMillisFor(int year, int month, int day)226 protected static long getMillisFor(int year, int month, int day) { 227 Calendar calendar = Calendar.getInstance(); 228 calendar.clear(); 229 calendar.set(Calendar.YEAR, year); 230 calendar.set(Calendar.MONTH, month); 231 calendar.set(Calendar.DATE, day); 232 calendar.set(Calendar.HOUR, 0); 233 calendar.set(Calendar.MINUTE, 0); 234 calendar.set(Calendar.SECOND, 0); 235 calendar.set(Calendar.MILLISECOND, 0); 236 long result = calendar.getTimeInMillis(); 237 return result; 238 } 239 240 /** Convert days, hours, minutes, and seconds into milliseconds after the Epoch, UCT. 241 * Date is index origin 1 so add one to the number of days. Default year and month, 242 * as these are assumed by Calendar to be the Epoch. 243 * @param day the number of days 244 * @param hour the hour (or number of hours) 245 * @param minute the minute (or number of minutes) 246 * @param second the second (or number of seconds) 247 * @return millis past the Epoch UCT 248 */ getMillisFor(int days, int hour, int minute, int second)249 protected static long getMillisFor(int days, int hour, int minute, int second) { 250 Calendar calendar = Calendar.getInstance(); 251 calendar.clear(); 252 calendar.set(Calendar.DATE, days + 1); 253 calendar.set(Calendar.HOUR, hour); 254 calendar.set(Calendar.MINUTE, minute); 255 calendar.set(Calendar.SECOND, second); 256 calendar.set(Calendar.MILLISECOND, 0); 257 long result = calendar.getTimeInMillis(); 258 return result; 259 } 260 261 /** Reset the local system default time zone to the time zone used 262 * by the MySQL server. This guarantees that there is no time zone 263 * offset between the time zone in the client and the time zone 264 * in the server. 265 * @param connection2 266 */ resetLocalSystemDefaultTimeZone(Connection connection)267 protected static void resetLocalSystemDefaultTimeZone(Connection connection) { 268 try { 269 PreparedStatement statement = connection.prepareStatement("select @@global.time_zone, @@global.system_time_zone, @@session.time_zone"); 270 ResultSet rs = statement.executeQuery(); 271 // there are two columns in the result 272 rs.next(); 273 String globalTimeZone = rs.getString(1); 274 String globalSystemTimeZone = rs.getString(2); 275 String sessionTimeZone = rs.getString(3); 276 if (debug) System.out.println("Global time zone: " + globalTimeZone + 277 " Global system time zone: " + globalSystemTimeZone +" Session time zone: " + sessionTimeZone); 278 connection.commit(); 279 if ("SYSTEM".equalsIgnoreCase(globalTimeZone)) { 280 globalTimeZone = globalSystemTimeZone; 281 } else { 282 globalTimeZone = "GMT" + globalTimeZone; 283 } 284 localSystemTimeZone = TimeZone.getTimeZone(globalTimeZone); 285 if (debug) System.out.println("Local system time zone set to: " + globalTimeZone + "(" + localSystemTimeZone + ")"); 286 TimeZone.setDefault(localSystemTimeZone); 287 // get a new connection after setting local default time zone 288 // because a connection contains a session calendar used to create Timestamp instances 289 connection.close(); 290 } catch (SQLException e) { 291 throw new RuntimeException("setServerTimeZone failed", e); 292 } 293 } 294 295 /** This class describes columns and fields for a table and model class. 296 * A subclass will instantiate instances of this class and provide handlers to 297 * read and write fields and columns via methods defined in the instance handler. 298 */ 299 protected static class ColumnDescriptor { 300 301 private String columnName; 302 303 protected InstanceHandler instanceHandler; 304 getColumnName()305 public String getColumnName() { 306 return columnName; 307 } 308 getResultSetValue(ResultSet rs, int j)309 public Object getResultSetValue(ResultSet rs, int j) throws SQLException { 310 return instanceHandler.getResultSetValue(rs, j); 311 } 312 getFieldValue(IdBase instance)313 public Object getFieldValue(IdBase instance) { 314 return instanceHandler.getFieldValue(instance); 315 } 316 setFieldValue(IdBase instance, Object value)317 public void setFieldValue(IdBase instance, Object value) { 318 this.instanceHandler.setFieldValue(instance, value); 319 } 320 setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)321 public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) 322 throws SQLException { 323 instanceHandler.setPreparedStatementValue(preparedStatement, j, value); 324 } 325 ColumnDescriptor(String name, InstanceHandler instanceHandler)326 protected ColumnDescriptor(String name, InstanceHandler instanceHandler) { 327 this.columnName = name; 328 this.instanceHandler = instanceHandler; 329 } 330 } 331 332 protected interface InstanceHandler { setFieldValue(IdBase instance, Object value)333 void setFieldValue(IdBase instance, Object value); getResultSetValue(ResultSet rs, int j)334 Object getResultSetValue(ResultSet rs, int j) 335 throws SQLException; getFieldValue(IdBase instance)336 Object getFieldValue(IdBase instance); setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)337 public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) 338 throws SQLException; 339 } 340 341 /** Subclasses can override this method to get debugging info printed to System.out */ getDebug()342 protected boolean getDebug() { 343 return false; 344 } 345 346 /** Subclasses usually should not override this method to provide the list of expected results */ getExpected()347 protected List<Object[]> getExpected() { 348 return expected; 349 } 350 351 /** Subclasses must override this method to provide the name of the table for the test */ getTableName()352 protected String getTableName() { 353 return null; 354 } 355 356 /** Subclasses must override this method to provide the number of instances to create */ getNumberOfInstances()357 protected int getNumberOfInstances() { 358 return 0; 359 } 360 361 /** Subclasses must override this method to provide the column descriptors for the test */ getColumnDescriptors()362 protected ColumnDescriptor[] getColumnDescriptors() { 363 return null; 364 } 365 366 /** Subclasses must override this method to implement the model factory for the test */ getNewInstance(Class<? extends IdBase> modelClass)367 protected IdBase getNewInstance(Class<? extends IdBase> modelClass) { 368 return null; 369 } 370 371 /** Subclasses must override this method to provide the model class for the test */ getModelClass()372 protected Class<? extends IdBase> getModelClass() { 373 return null; 374 } 375 376 /** Subclasses must override this method to provide values for rows (i) and columns (j) */ getColumnValue(int i, int j)377 protected Object getColumnValue(int i, int j) { 378 return null; 379 } 380 381 /** Generated instances to persist. When using JDBC, the data is obtained from the instance 382 * via the column descriptors. As a side effect (!) create the list of expected results from read. 383 * @param columnDescriptors the column descriptors 384 * @return the generated instances 385 */ generateInstances(ColumnDescriptor[] columnDescriptors)386 protected void generateInstances(ColumnDescriptor[] columnDescriptors) { 387 Class<? extends IdBase> modelClass = getModelClass(); 388 expected = new ArrayList<Object[]>(); 389 instances = new ArrayList<IdBase>(); 390 IdBase instance = null; 391 int numberOfInstances = getNumberOfInstances(); 392 for (int i = 0; i < numberOfInstances; ++i) { 393 // create the instance 394 instance = getNewInstance(modelClass); 395 instance.setId(i); 396 // create the expected result row 397 int j = 0; 398 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 399 Object value = getColumnValue(i, j); 400 // set the column value in the instance 401 columnDescriptor.setFieldValue(instance, value); 402 // set the column value in the expected result 403 if (debug) System.out.println("generateInstances set field " + columnDescriptor.getColumnName() + " to value " + value); 404 ++j; 405 } 406 instances.add(instance); 407 Object[] expectedRow = createRow(columnDescriptors, instance); 408 expected.add(expectedRow); 409 } 410 if (debug) System.out.println("Created " + instances.size() + " instances."); 411 } 412 413 /** Verify that the actual results match the expected results. If not, use the multiple error 414 * reporting method errorIfNotEqual defined in the superclass. 415 * @param where the location of the verification of results, normally the name of the test method 416 * @param expecteds the expected results 417 * @param actuals the actual results 418 */ verify(String where, List<Object[]> expecteds, List<Object[]> actuals)419 protected void verify(String where, List<Object[]> expecteds, List<Object[]> actuals) { 420 for (int i = 0; i < expecteds.size(); ++i) { 421 Object[] expected = expecteds.get(i); 422 Object[] actual = actuals.get(i); 423 errorIfNotEqual(where + " got failure on id for row " + i, i, actual[0]); 424 for (int j = 1; j < expected.length; ++j) { 425 errorIfNotEqual(where + " got failure to match column data for row " 426 + i + " column " + j, 427 expected[j], actual[j]); 428 } 429 } 430 } 431 removeAll(Class<?> modelClass)432 protected void removeAll(Class<?> modelClass) { 433 Query query = em.createQuery("DELETE FROM " + modelClass.getSimpleName()); 434 em.getTransaction().begin(); 435 query.executeUpdate(); 436 em.getTransaction().commit(); 437 } 438 removeAll(String tableName)439 private void removeAll(String tableName) { 440 getConnection(); 441 try { 442 PreparedStatement statement = connection.prepareStatement("DELETE FROM " + tableName); 443 statement.execute(); 444 connection.commit(); 445 } catch (SQLException e) { 446 // TODO Auto-generated catch block 447 e.printStackTrace(); 448 } 449 } 450 451 /** Write data via JDBC and read back the data via JPA */ writeJDBCreadJPA()452 protected void writeJDBCreadJPA() { 453 generateInstances(columnDescriptors); 454 removeAll(getTableName()); 455 List<Object[]> result = null; 456 writeToJDBC(columnDescriptors, instances); 457 result = readFromJPA(columnDescriptors); 458 verify("writeJDBCreadJPA", getExpected(), result); 459 } 460 461 /** Write data via JDBC and read back the data via JDBC */ writeJDBCreadJDBC()462 protected void writeJDBCreadJDBC() { 463 generateInstances(columnDescriptors); 464 removeAll(getTableName()); 465 List<Object[]> result = null; 466 writeToJDBC(columnDescriptors, instances); 467 result = readFromJDBC(columnDescriptors); 468 verify("writeJDBCreadJDBC", getExpected(), result); 469 } 470 471 /** Write data via JPA and read back the data via JPA */ writeJPAreadJPA()472 protected void writeJPAreadJPA() { 473 generateInstances(columnDescriptors); 474 removeAll(getModelClass()); 475 List<Object[]> result = null; 476 writeToJPA(columnDescriptors, instances); 477 result = readFromJPA(columnDescriptors); 478 verify("writeJPAreadJPA", getExpected(), result); 479 } 480 481 /** Write data via JPA and read back the data via JDBC */ writeJPAreadJDBC()482 protected void writeJPAreadJDBC() { 483 generateInstances(columnDescriptors); 484 removeAll(getTableName()); 485 List<Object[]> result = null; 486 writeToJPA(columnDescriptors, instances); 487 result = readFromJDBC(columnDescriptors); 488 verify("writeJPAreadJDBC", getExpected(), result); 489 } 490 491 /** Write data via JPA */ writeToJPA(ColumnDescriptor[] columnDescriptors, List<IdBase> instances)492 protected void writeToJPA(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) { 493 em.getTransaction().begin(); 494 for (IdBase instance: instances) { 495 em.persist(instance); 496 } 497 em.getTransaction().commit(); 498 } 499 500 /** Write data to JDBC. */ writeToJDBC(ColumnDescriptor[] columnDescriptors, List<IdBase> instances)501 protected void writeToJDBC(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) { 502 String tableName = getTableName(); 503 StringBuffer buffer = new StringBuffer("INSERT INTO "); 504 buffer.append(tableName); 505 buffer.append(" (id"); 506 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 507 buffer.append(", "); 508 buffer.append(columnDescriptor.getColumnName()); 509 } 510 buffer.append(") VALUES (?"); 511 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 512 buffer.append(", ?"); 513 } 514 buffer.append(")"); 515 String statement = buffer.toString(); 516 if (debug) System.out.println(statement); 517 518 PreparedStatement preparedStatement = null; 519 int i = 0; 520 try { 521 preparedStatement = connection.prepareStatement(statement); 522 if (debug) System.out.println(preparedStatement.toString()); 523 for (i = 0; i < instances.size(); ++i) { 524 IdBase instance = instances.get(i); 525 preparedStatement.setInt(1, instance.getId()); 526 int j = 2; 527 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 528 Object value = columnDescriptor.getFieldValue(instance); 529 columnDescriptor.setPreparedStatementValue(preparedStatement, j++, value); 530 if (debug) System.out.println("writeToJDBC set column: " + columnDescriptor.getColumnName() + " to value: " + value); 531 } 532 preparedStatement.execute(); 533 } 534 connection.commit(); 535 } catch (SQLException e) { 536 throw new RuntimeException("Failed to insert " + tableName + " at instance " + i, e); 537 } 538 } 539 540 /** Read data via JPA */ readFromJPA(ColumnDescriptor[] columnDescriptors)541 protected List<Object[]> readFromJPA(ColumnDescriptor[] columnDescriptors) { 542 Class<? extends IdBase> modelClass = getModelClass(); 543 List<Object[]> result = new ArrayList<Object[]>(); 544 em.getTransaction().begin(); 545 for (int i = 0; i < getNumberOfInstances() ; ++i) { 546 IdBase instance = em.find(modelClass, i); 547 if (instance != null) { 548 Object[] row = createRow(columnDescriptors, instance); 549 result.add(row); 550 } 551 } 552 em.getTransaction().commit(); 553 if (debug) System.out.println("readFromJPA: " + dump(result)); 554 return result; 555 } 556 557 /** Read data via JDBC */ readFromJDBC(ColumnDescriptor[] columnDescriptors)558 protected List<Object[]> readFromJDBC(ColumnDescriptor[] columnDescriptors) { 559 String tableName = getTableName(); 560 List<Object[]> result = new ArrayList<Object[]>(); 561 StringBuffer buffer = new StringBuffer("SELECT id"); 562 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 563 buffer.append(", "); 564 buffer.append(columnDescriptor.getColumnName()); 565 } 566 buffer.append(" FROM "); 567 buffer.append(tableName); 568 buffer.append(" ORDER BY ID"); 569 String statement = buffer.toString(); 570 if (debug) System.out.println(statement); 571 PreparedStatement preparedStatement = null; 572 int i = 0; 573 try { 574 preparedStatement = connection.prepareStatement(statement); 575 ResultSet rs = preparedStatement.executeQuery(); 576 while (rs.next()) { 577 Object[] row = new Object[columnDescriptors.length + 1]; 578 int j = 1; 579 row[0] = rs.getInt(1); 580 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 581 row[j] = columnDescriptor.getResultSetValue(rs, j + 1); 582 ++j; 583 } 584 ++i; 585 result.add(row); 586 } 587 connection.commit(); 588 } catch (SQLException e) { 589 throw new RuntimeException("Failed to read " + tableName + " at instance " + i, e); 590 } 591 if (debug) System.out.println("readFromJDBC: " + dump(result)); 592 return result; 593 } 594 595 /** Create row data from an instance. 596 * @param columnDescriptors the column descriptors describing the data 597 * @param instance the instance to extract data from 598 * @return the row data representing the instance 599 */ createRow(ColumnDescriptor[] columnDescriptors, IdBase instance)600 private Object[] createRow(ColumnDescriptor[] columnDescriptors, 601 IdBase instance) { 602 Object[] row = new Object[columnDescriptors.length + 1]; 603 row[0] = instance.getId(); 604 int j = 1; 605 for (ColumnDescriptor columnDescriptor: columnDescriptors) { 606 row[j++] = columnDescriptor.getFieldValue(instance); 607 } 608 return row; 609 } 610 611 /** Dump the contents of the expected or actual results of the operation */ dump(List<Object[]> results)612 private String dump(List<Object[]> results) { 613 StringBuffer result = new StringBuffer(results.size() + " rows\n"); 614 for (Object[] row: results) { 615 result.append("Id: "); 616 for (Object column: row) { 617 result.append(column); 618 result.append(' '); 619 } 620 result.append('\n'); 621 } 622 return result.toString(); 623 } 624 625 } 626