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