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 testsuite.clusterj;
26 
27 import java.lang.reflect.InvocationHandler;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Proxy;
30 import java.sql.Connection;
31 import java.sql.PreparedStatement;
32 import java.sql.ResultSet;
33 import java.sql.SQLException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Calendar;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.TimeZone;
43 import java.util.TreeSet;
44 
45 import com.mysql.clusterj.Session;
46 
47 import testsuite.clusterj.model.AllPrimitives;
48 import testsuite.clusterj.model.Dn2id;
49 import testsuite.clusterj.model.Employee;
50 import testsuite.clusterj.model.IdBase;
51 
52 public abstract class AbstractClusterJModelTest extends AbstractClusterJTest {
53 
54     /** The local system default time zone, which is reset by resetLocalSystemDefaultTimeZone */
55     protected static TimeZone localSystemTimeZone = TimeZone.getDefault();
56 
57     /** ONE_SECOND is the number of milliseconds in one second. */
58     protected static final long ONE_SECOND = 1000L;
59 
60     /** ONE_MINUTE is the number of milliseconds in one minute. */
61     protected static final long ONE_MINUTE = 1000L * 60L;
62 
63     /** ONE_HOUR is the number of milliseconds in one hour. */
64     protected static final long ONE_HOUR = 1000L * 60L * 60L;
65 
66     /** TEN_HOURS is the number of milliseconds in ten hours. */
67     protected static final long TEN_HOURS = 1000L * 60L * 60L * 10L;
68 
69     /** ONE_DAY is the number of milliseconds in one day. */
70     protected static final long ONE_DAY = 1000L * 60L * 60L * 24L;
71 
72     /** Convert year, month, day, hour, minute, second into milliseconds after the Epoch, UCT.
73      * @param year the year
74      * @param month the month (0 for January)
75      * @param day the day of the month
76      * @param hour the hour of the day
77      * @param minute the minute
78      * @param second the second
79      * @return
80      */
getMillisFor(int year, int month, int day, int hour, int minute, int second)81     protected static long getMillisFor(int year, int month, int day, int hour, int minute, int second) {
82         Calendar calendar = Calendar.getInstance();
83         calendar.clear();
84         calendar.set(Calendar.YEAR, year);
85         calendar.set(Calendar.MONTH, month);
86         calendar.set(Calendar.DATE, day);
87         calendar.set(Calendar.HOUR, hour);
88         calendar.set(Calendar.MINUTE, minute);
89         calendar.set(Calendar.SECOND, second);
90         calendar.set(Calendar.MILLISECOND, 0);
91         long result = calendar.getTimeInMillis();
92         return result;
93     }
94 
95     /** Convert year, month, day into milliseconds after the Epoch, UCT.
96      * Set hours, minutes, seconds, and milliseconds to zero.
97      * @param year the year
98      * @param month the month (0 for January)
99      * @param day the day of the month
100      * @return
101      */
getMillisFor(int year, int month, int day)102     protected static long getMillisFor(int year, int month, int day) {
103         Calendar calendar = Calendar.getInstance();
104         calendar.clear();
105         calendar.set(Calendar.YEAR, year);
106         calendar.set(Calendar.MONTH, month);
107         calendar.set(Calendar.DATE, day);
108         calendar.set(Calendar.HOUR, 0);
109         calendar.set(Calendar.MINUTE, 0);
110         calendar.set(Calendar.SECOND, 0);
111         calendar.set(Calendar.MILLISECOND, 0);
112         long result = calendar.getTimeInMillis();
113         return result;
114     }
115 
116     /** Convert hours, minutes, and seconds into milliseconds after the Epoch, UCT.
117      * Default year, month, and day,
118      * as these are assumed by Calendar to be the Epoch.
119      * @param day the number of days: ignored
120      * @param hour the hour (or number of hours)
121      * @param minute the minute (or number of minutes)
122      * @param second the second (or number of seconds)
123      * @return millis past the Epoch UCT
124      */
getMillisFor(int days, int hour, int minute, int second)125     protected static long getMillisFor(int days, int hour, int minute, int second) {
126         Calendar calendar = Calendar.getInstance();
127         calendar.clear();
128         calendar.set(Calendar.HOUR, hour);
129         calendar.set(Calendar.MINUTE, minute);
130         calendar.set(Calendar.SECOND, second);
131         calendar.set(Calendar.MILLISECOND, 0);
132         long result = calendar.getTimeInMillis();
133         return result;
134     }
135 
136     /** A1 values. */
137     String[] a1values = new String[]{"dc=abc", "dc=prs", "dc=xyz"};
138 
139     protected List<Employee> employees;
140 
141     protected List<Dn2id> dn2ids;
142 
143     protected static Object[] dn2idPK = setupDn2idPK();
144 
145     /** The instances used in the tests, generated by generateInstances */
146     protected List<IdBase> instances = new ArrayList<IdBase>();
147 
148     /** List of expected results, generated by generateInstances */
149     private List<Object[]> expected = null;
150 
151     /** The column descriptors as provided by subclasses */
152     ColumnDescriptor[] columnDescriptors = null;
153 
154     /** The class loader for the domain object type */
155     protected ClassLoader loader;
156 
AbstractClusterJModelTest()157     public AbstractClusterJModelTest() {
158         columnDescriptors = getColumnDescriptors();
159     }
160 
getCleanupAfterTest()161     protected boolean getCleanupAfterTest() {
162         return true;
163     }
164 
165     /** Set this to false to avoid running test (e.g. if no model class) */
166     protected boolean runTest = true;
167 
168     @Override
localSetUp()169     public void localSetUp() {
170         createSessionFactory();
171         session = sessionFactory.getSession();
172         setAutoCommit(connection, false);
173         try {
174             session.newInstance(getModelClass());
175             runTest = true;
176         } catch (Exception e) {
177             System.out.println("Ignoring test; no model class " + getModelClass().getName());
178             runTest = false;
179         }
180 
181         if (getModelClass() != null && getCleanupAfterTest()) {
182             addTearDownClasses(getModelClass());
183         }
184     }
185 
186     /** Reset the local system default time zone to the time zone used
187      * by the MySQL server. This guarantees that there is no time zone
188      * offset between the time zone in the client and the time zone
189      * in the server.
190      * @param connection
191      */
resetLocalSystemDefaultTimeZone(Connection connection)192     protected static void resetLocalSystemDefaultTimeZone(Connection connection) {
193         try {
194             PreparedStatement statement = connection.prepareStatement("select @@global.time_zone, @@global.system_time_zone, @@session.time_zone");
195             ResultSet rs = statement.executeQuery();
196             // there are two columns in the result
197             rs.next();
198             String globalTimeZone = rs.getString(1);
199             String globalSystemTimeZone = rs.getString(2);
200             String sessionTimeZone = rs.getString(3);
201 //            if (debug) System.out.println("Global time zone: " + globalTimeZone +
202 //                    " Global system time zone: " + globalSystemTimeZone +" Session time zone: " + sessionTimeZone);
203             connection.commit();
204             if ("SYSTEM".equalsIgnoreCase(globalTimeZone)) {
205                 globalTimeZone = globalSystemTimeZone;
206             } else {
207                 globalTimeZone = "GMT" + globalTimeZone;
208             }
209             localSystemTimeZone = TimeZone.getTimeZone(globalTimeZone);
210 //            if (debug) System.out.println("Local system time zone set to: " + globalTimeZone + "(" + localSystemTimeZone + ")");
211 //            TimeZone.setDefault(localSystemTimeZone);
212             // get a new connection after setting local default time zone
213             // because a connection contains a session calendar used to create Timestamp instances
214             connection.close();
215         } catch (SQLException e) {
216             throw new RuntimeException("setServerTimeZone failed", e);
217         }
218     }
219 
setAutoCommit(Connection connection, boolean b)220     protected void setAutoCommit(Connection connection, boolean b) {
221         try {
222             connection.setAutoCommit(false);
223         } catch (SQLException e) {
224             throw new RuntimeException("setAutoCommit failed", e);
225         }
226     }
227 
createEmployeeInstances(int count)228     protected void createEmployeeInstances(int count) {
229         employees = new ArrayList<Employee>(count);
230         for (int i = 0; i < count; ++i) {
231             Employee emp = session.newInstance(Employee.class);
232             emp.setId(i);
233             emp.setName("Employee number " + i);
234             emp.setAge(i);
235             emp.setMagic(i);
236             employees.add(emp);
237         }
238     }
239 
consistencyCheck(Employee emp)240     protected void consistencyCheck(Employee emp) {
241         int id = emp.getId();
242         String expectedName = "Employee number " + id;
243         String actualName = emp.getName();
244         if (!expectedName.equals(actualName)) {
245 //            System.out.println("expected " + dump(expectedName));
246 //            System.out.println("actual " + dump(actualName));
247             error("Employee " + id
248                     + " name mismatch; expected length: " + expectedName.length() + "'" + expectedName
249                     + "'; actual length: " + actualName.length() + "'" + actualName + "'");
250         }
251         int actualAge = emp.getAge();
252         if (!(actualAge == id)) {
253             error("Employee " + id
254                     + " age mismatch; expected " + id
255                     + "'; actual '" + actualAge);
256         }
257         int actualMagic = emp.getMagic();
258         if (!(actualMagic == id)) {
259             error("Employee " + id
260                     + " magic mismatch; expected " + id
261                     + "'; actual '" + actualMagic);
262         }
263     }
264 
consistencyCheck(Iterable<T> instances)265     protected <T> void consistencyCheck(Iterable<T> instances) {
266         for (T instance: instances) {
267             if (instance instanceof Employee) {
268                 consistencyCheck((Employee)instance);
269             } else if (instance instanceof Dn2id) {
270                 consistencyCheck((Dn2id)instance);
271             }
272         }
273     }
274 
createDn2idInstances(int number)275     protected void createDn2idInstances(int number) {
276         dn2ids = new ArrayList<Dn2id>();
277         for (int i = 0; i < number; ++i) {
278             Dn2id d = session.newInstance(Dn2id.class);
279             d.setEid(i);
280             d.setObjectClasses("testObject");
281             // XObjectClasses has a NullValue=DEFAULT so don't need to set it
282             d.setA0("dc=com");
283             // a1 should pick all of the a1values equally
284             d.setA1(getA1for(number, i));
285             d.setA2("ou=people");
286             d.setA3(getA3for(i));
287             d.setA4("");
288             d.setA5("");
289             d.setA6("");
290             d.setA7("");
291             d.setA8("");
292             d.setA9("");
293             d.setA10("");
294             d.setA11("");
295             d.setA12("");
296             d.setA13("");
297             d.setA14("");
298             d.setA15("");
299             dn2ids.add(d);
300         }
301     }
302 
consistencyCheck(Dn2id dn2id)303     protected void consistencyCheck(Dn2id dn2id) {
304         long eid = dn2id.getEid();
305         String expected = getA3for(eid);
306         String actual = dn2id.getA3();
307         if (!expected.equals(actual)) {
308             error("Dn2id " + eid
309                     + " a3 mismatch; expected '" + expected
310                     + "'; actual '" + actual + "'");
311         }
312     }
313 
314     /** Subclasses usually should not override this method to provide the list of expected results */
getExpected()315     protected List<Object[]> getExpected() {
316         return expected;
317     }
318 
319     /** Subclasses must override this method to provide the name of the table for the test */
getTableName()320     protected String getTableName() {
321         return null;
322     }
323 
324     /** Subclasses must override this method to provide the number of instances to create */
getNumberOfInstances()325     protected int getNumberOfInstances() {
326         return 0;
327     }
328 
329     /** Subclasses must override this method to provide the column descriptors for the test */
getColumnDescriptors()330     protected ColumnDescriptor[] getColumnDescriptors() {
331         return null;
332     }
333 
334     /** Subclasses must override this method to provide the model class for the test */
getModelClass()335     Class<? extends IdBase> getModelClass() {
336         return null;
337     }
338 
339     /** Subclasses must override this method to provide values for rows (i) and columns (j) */
getColumnValue(int i, int j)340     protected Object getColumnValue(int i, int j) {
341         return null;
342     }
343 
344     /** Subclasses may override this method to convert an int into a key value */
convertToKey(int i)345     protected Object convertToKey(int i) {
346         return Integer.valueOf(i);
347     }
348 
349     /** Write data via JDBC and read back the data via NDB */
writeJDBCreadNDB()350     protected void writeJDBCreadNDB() {
351         if (!runTest) return;
352         generateInstances(getColumnDescriptors());
353         removeAll(getModelClass());
354         List<Object[]> result = null;
355         writeToJDBC(columnDescriptors, instances);
356         result = readFromNDB(columnDescriptors);
357         verify("writeJDBCreadNDB", getExpected(), result);
358     }
359 
360     /** Write data via JDBC and read back the data via JDBC */
writeJDBCreadJDBC()361     protected void writeJDBCreadJDBC() {
362         if (!runTest) return;
363         generateInstances(getColumnDescriptors());
364         removeAll(getModelClass());
365         List<Object[]> result = null;
366         writeToJDBC(columnDescriptors, instances);
367         result = readFromJDBC(columnDescriptors);
368         verify("writeJDBCreadJDBC", getExpected(), result);
369     }
370 
371     /** Write data via NDB and read back the data via NDB */
writeNDBreadNDB()372     protected void writeNDBreadNDB() {
373         if (!runTest) return;
374         generateInstances(getColumnDescriptors());
375         removeAll(getModelClass());
376         List<Object[]> result = null;
377         writeToNDB(columnDescriptors, instances);
378         result = readFromNDB(columnDescriptors);
379         verify("writeNDBreadNDB", getExpected(), result);
380     }
381 
382     /** Write data via NDB and read back the data via JDBC */
writeNDBreadJDBC()383     protected void writeNDBreadJDBC() {
384         if (!runTest) return;
385         generateInstances(getColumnDescriptors());
386         removeAll(getModelClass());
387         List<Object[]> result = null;
388         writeToNDB(columnDescriptors, instances);
389         result = readFromJDBC(columnDescriptors);
390         verify("writeNDBreadJDBC", getExpected(), result);
391     }
392 
393     /** Dump the contents of the expected or actual results of the operation */
dumpListOfObjectArray(List<Object[]> results)394     private String dumpListOfObjectArray(List<Object[]> results) {
395         StringBuffer result = new StringBuffer(results.size() + " rows\n");
396         for (Object[] row: results) {
397             result.append("Id: ");
398             for (Object column: row) {
399                 result.append(column);
400                 result.append(' ');
401             }
402             result.append('\n');
403         }
404         return result.toString();
405     }
406 
queryAndVerifyResults(String where, ColumnDescriptor[] columnDescriptors, String conditions, Object[] parameters, int... objectIds)407     protected void queryAndVerifyResults(String where, ColumnDescriptor[] columnDescriptors,
408             String conditions, Object[] parameters, int... objectIds) {
409         List<Object[]> results = queryJDBC(columnDescriptors, conditions, parameters);
410         verifyQueryResults(where, results, objectIds);
411     }
412 
413     /** Read data via JDBC */
queryJDBC(ColumnDescriptor[] columnDescriptors, String conditions, Object[] parameters)414     protected List<Object[]> queryJDBC(ColumnDescriptor[] columnDescriptors,
415             String conditions, Object[] parameters) {
416         getConnection();
417         String tableName = getTableName();
418         List<Object[]> result = new ArrayList<Object[]>();
419         StringBuffer buffer = new StringBuffer("SELECT id");
420         for (ColumnDescriptor columnDescriptor: columnDescriptors) {
421             buffer.append(", ");
422             buffer.append(columnDescriptor.getColumnName());
423         }
424         buffer.append(" FROM ");
425         buffer.append(tableName);
426         buffer.append(" WHERE ");
427         buffer.append(conditions);
428         String statement = buffer.toString();
429         if (debug) System.out.println(statement);
430         PreparedStatement preparedStatement = null;
431         try {
432             int p = 1;
433             preparedStatement = connection.prepareStatement(statement);
434             for (Object parameter: parameters) {
435                 preparedStatement.setObject(p++, parameter);
436             }
437             ResultSet rs = preparedStatement.executeQuery();
438             while (rs.next()) {
439                 Object[] row = new Object[columnDescriptors.length + 1];
440                 int j = 1;
441                 row[0] = rs.getInt(1);
442                 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
443                     row[j] = columnDescriptor.getResultSetValue(rs, j + 1);
444                     ++j;
445                 }
446                 result.add(row);
447             }
448             if (!connection.getAutoCommit()) {
449                 connection.commit();
450             }
451         } catch (SQLException e) {
452             throw new RuntimeException("Failed to read " + tableName, e);
453         }
454         if (debug) System.out.println("readFromJDBC: " + dumpObjectArray(result));
455         return result;
456     }
457 
458     /** Dump the contents of the expected or actual results of the operation */
dumpObjectArray(List<Object[]> results)459     private String dumpObjectArray(List<Object[]> results) {
460         StringBuffer result = new StringBuffer(results.size() + " rows\n");
461         for (Object[] row: results) {
462             result.append("Id: ");
463             for (Object column: row) {
464                 result.append(column);
465                 result.append(' ');
466             }
467             result.append('\n');
468         }
469         return result.toString();
470     }
471 
verifyQueryResults(String where, List<Object[]> results, int... objectIds)472     protected void verifyQueryResults(String where, List<Object[]> results, int... objectIds) {
473         errorIfNotEqual(where + " mismatch in number of results.", objectIds.length, results.size());
474         for (Object[] result: results) {
475             int id = (Integer)result[0];
476             if (Arrays.binarySearch(objectIds, id) < 0) {
477                 // couldn't find it
478                 error(where + " result " + id + " not expected.");
479             }
480         }
481     }
482 
483     /** Verify that the actual results match the expected results. If not, use the multiple error
484      * reporting method errorIfNotEqual defined in the superclass.
485      * @param where the location of the verification of results, normally the name of the test method
486      * @param expecteds the expected results
487      * @param actuals the actual results
488      */
verify(String where, List<Object[]> expecteds, List<Object[]> actuals)489     protected void verify(String where, List<Object[]> expecteds, List<Object[]> actuals) {
490         if (expecteds.size() != actuals.size()) {
491             error(where + " failure on size of results: expected: " + expecteds.size() + " actual: " + actuals.size());
492             return;
493         }
494         for (int i = 0; i < expecteds.size(); ++i) {
495             Object[] expected = expecteds.get(i);
496             Object[] actual = actuals.get(i);
497             errorIfNotEqual(where + " got failure on id for row " + i, i, actual[0]);
498             for (int j = 1; j < expected.length; ++j) {
499                 errorIfNotEqual(where + " got failure to match column data for row "
500                         + i + " column " + j,
501                         expected[j], actual[j]);
502             }
503         }
504     }
505 
506     /** Generated instances to persist. When using JDBC, the data is obtained from the instance
507      * via the column descriptors. As a side effect (!) create the list of expected results from read.
508      * @param columnDescriptors the column descriptors
509      * @return the generated instances
510      */
generateInstances(ColumnDescriptor[] columnDescriptors)511     protected void generateInstances(ColumnDescriptor[] columnDescriptors) {
512         Class<? extends IdBase> modelClass = getModelClass();
513         expected = new ArrayList<Object[]>();
514         instances = new ArrayList<IdBase>();
515         IdBase instance = null;
516         int numberOfInstances = getNumberOfInstances();
517         for (int i = 0; i < numberOfInstances; ++i) {
518             // create the instance
519             instance = getNewInstance(modelClass);
520             instance.setId(i);
521             // create the expected result row
522             int j = 0;
523             for (ColumnDescriptor columnDescriptor: columnDescriptors) {
524                 Object value = getColumnValue(i, j);
525                 if (debug) System.out.println("generateInstances set field " + columnDescriptor.getColumnName()
526                         + " to value "  + value);
527                 // set the column value in the instance
528                 columnDescriptor.setFieldValue(instance, value);
529                 // check that the value was set correctly
530                 Object actual = columnDescriptor.getFieldValue(instance);
531                 errorIfNotEqual("generateInstances value mismatch for " + columnDescriptor.getColumnName(),
532                         dump(value), dump(actual));
533                 ++j;
534             }
535             instances.add(instance);
536             // set the column values in the expected result
537             Object[] expectedRow = createRow(columnDescriptors, instance);
538             expected.add(expectedRow);
539         }
540         if (debug) System.out.println("Created " + instances.size() + " instances of " + modelClass.getName());
541     }
542 
543     /** Create a new instance of the parameter interface
544      * @param modelClass the interface to instantiate
545      * @return an instance of the class
546      */
getNewInstance(Class<? extends IdBase> modelClass)547     protected IdBase getNewInstance(Class<? extends IdBase> modelClass) {
548         IdBase instance;
549         instance = session.newInstance(modelClass);
550         return instance;
551     }
552 
553     /** Write data to JDBC. */
writeToJDBC(ColumnDescriptor[] columnDescriptors, List<IdBase> instances)554     protected void writeToJDBC(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) {
555         String tableName = getTableName();
556         StringBuffer buffer = new StringBuffer("INSERT INTO ");
557         buffer.append(tableName);
558         buffer.append(" (id");
559         for (ColumnDescriptor columnDescriptor: columnDescriptors) {
560             buffer.append(", ");
561             buffer.append(columnDescriptor.getColumnName());
562         }
563         buffer.append(") VALUES (?");
564         for (ColumnDescriptor columnDescriptor: columnDescriptors) {
565             buffer.append(", ?");
566         }
567         buffer.append(")");
568         String statement = buffer.toString();
569         if (debug) System.out.println(statement);
570 
571         PreparedStatement preparedStatement = null;
572         int i = 0;
573         try {
574             preparedStatement = connection.prepareStatement(statement);
575             if (debug) System.out.println(preparedStatement.toString());
576             for (i = 0; i < instances.size(); ++i) {
577                 IdBase instance = instances.get(i);
578                 preparedStatement.setInt(1, instance.getId());
579                 int j = 2;
580                 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
581                     Object value = columnDescriptor.getFieldValue(instance);
582                     columnDescriptor.setPreparedStatementValue(preparedStatement, j++, value);
583                     if (debug) System.out.println("writeToJDBC set column: " + columnDescriptor.getColumnName() + " to value: " + value);
584                 }
585                 preparedStatement.execute();
586             }
587             if (!connection.getAutoCommit()) {
588                 connection.commit();
589             }
590         } catch (SQLException e) {
591             throw new RuntimeException("Failed to insert " + tableName + " at instance " + i, e);
592         }
593     }
594 
595     /** Write data via NDB */
writeToNDB(ColumnDescriptor[] columnDescriptors, List<IdBase> instances)596     protected void writeToNDB(ColumnDescriptor[] columnDescriptors, List<IdBase> instances) {
597         session.currentTransaction().begin();
598         session.makePersistentAll(instances);
599         session.currentTransaction().commit();
600     }
601 
602     /** Read data via NDB */
readFromNDB(ColumnDescriptor[] columnDescriptors)603     protected List<Object[]> readFromNDB(ColumnDescriptor[] columnDescriptors) {
604         Class<? extends IdBase> modelClass = getModelClass();
605         List<Object[]> result = new ArrayList<Object[]>();
606         session.currentTransaction().begin();
607         for (int i = 0; i < getNumberOfInstances() ; ++i) {
608             IdBase instance = session.find(modelClass, convertToKey(i));
609             if (instance != null) {
610                 Object[] row = createRow(columnDescriptors, instance);
611                 result.add(row);
612             }
613         }
614         session.currentTransaction().commit();
615         if (debug) System.out.println("readFromNDB: " + dumpListOfObjectArray(result));
616         return result;
617     }
618 
619     /** Create row data from an instance.
620      * @param columnDescriptors the column descriptors describing the data
621      * @param instance the instance to extract data from
622      * @return the row data representing the instance
623      */
createRow(ColumnDescriptor[] columnDescriptors, IdBase instance)624     private Object[] createRow(ColumnDescriptor[] columnDescriptors,
625             IdBase instance) {
626         Object[] row = new Object[columnDescriptors.length + 1];
627         row[0] = instance.getId();
628         int j = 1;
629         for (ColumnDescriptor columnDescriptor: columnDescriptors) {
630             row[j++] = columnDescriptor.getFieldValue(instance);
631         }
632         return row;
633     }
634 
635     /** Read data via JDBC ordered by id */
readFromJDBC(ColumnDescriptor[] columnDescriptors)636     protected List<Object[]> readFromJDBC(ColumnDescriptor[] columnDescriptors) {
637         String tableName = getTableName();
638         List<Object[]> result = new ArrayList<Object[]>();
639         Set<Object[]> rows = new TreeSet<Object[]>(new Comparator<Object[]>(){
640             public int compare(Object[] me, Object[] other) {
641                 return ((Integer)me[0]) - ((Integer)other[0]);
642             }
643         });
644         StringBuffer buffer = new StringBuffer("SELECT id");
645         for (ColumnDescriptor columnDescriptor: columnDescriptors) {
646             buffer.append(", ");
647             buffer.append(columnDescriptor.getColumnName());
648         }
649         buffer.append(" FROM ");
650         buffer.append(tableName);
651         String statement = buffer.toString();
652         if (debug) System.out.println(statement);
653         PreparedStatement preparedStatement = null;
654         int i = 0;
655         try {
656             preparedStatement = connection.prepareStatement(statement);
657             ResultSet rs = preparedStatement.executeQuery();
658             while (rs.next()) {
659                 Object[] row = new Object[columnDescriptors.length + 1];
660                 int j = 1;
661                 row[0] = rs.getInt(1);
662                 for (ColumnDescriptor columnDescriptor: columnDescriptors) {
663                     row[j] = columnDescriptor.getResultSetValue(rs, j + 1);
664                     ++j;
665                 }
666                 ++i;
667                 rows.add(row);
668             }
669             if (!connection.getAutoCommit()) {
670                 connection.commit();
671             }
672         } catch (SQLException e) {
673             throw new RuntimeException("Failed to read " + tableName + " at instance " + i, e);
674         }
675         result = new ArrayList<Object[]>(rows);
676         if (debug) System.out.println("readFromJDBC: " + dumpListOfObjectArray(result));
677         return result;
678     }
679 
680     @SuppressWarnings("unchecked") // cast proxy to T
proxyFor(final Class<T> cls)681     protected <T> T proxyFor (final Class<T> cls) {
682         InvocationHandler handler = new InvocationHandler() {
683             private Map<String, Object> values = new HashMap<String, Object>();
684             public Object invoke(Object instance, Method method, Object[] args)
685                     throws Throwable {
686                 String methodName = method.getName();
687                 String propertyName = methodName.substring(3);
688                 String methodPrefix = methodName.substring(0, 3);
689                 if ("get".equals(methodPrefix)) {
690                     return values.get(propertyName);
691                 } else if ("set".equals(methodPrefix)) {
692                     values.put(propertyName, args[0]);
693                     return null;
694                 }
695                 // error
696                 throw new RuntimeException("Not a get/set method: " + methodName);
697             }
698 
699         };
700         Object proxy = Proxy.newProxyInstance(loader, new Class[] {cls}, handler);
701         return (T)proxy;
702     }
703 
704     /** This class describes columns and fields for a table and model class.
705      * A subclass will instantiate instances of this class and provide handlers to
706      * read and write fields and columns via methods defined in the instance handler.
707      */
708     protected static class ColumnDescriptor {
709 
710         private String columnName;
711 
712         protected InstanceHandler instanceHandler;
713 
getColumnName()714         public String getColumnName() {
715             return columnName;
716         }
717 
getResultSetValue(ResultSet rs, int j)718         public Object getResultSetValue(ResultSet rs, int j) throws SQLException {
719             return instanceHandler.getResultSetValue(rs, j);
720         }
721 
getFieldValue(IdBase instance)722         public Object getFieldValue(IdBase instance) {
723             return instanceHandler.getFieldValue(instance);
724         }
725 
setFieldValue(IdBase instance, Object value)726         public void setFieldValue(IdBase instance, Object value) {
727             this.instanceHandler.setFieldValue(instance, value);
728         }
729 
setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)730         public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)
731                 throws SQLException {
732             instanceHandler.setPreparedStatementValue(preparedStatement, j, value);
733         }
734 
ColumnDescriptor(String name, InstanceHandler instanceHandler)735         public ColumnDescriptor(String name, InstanceHandler instanceHandler) {
736             this.columnName = name;
737             this.instanceHandler = instanceHandler;
738         }
739     }
740 
741     protected interface InstanceHandler {
setFieldValue(IdBase instance, Object value)742         void setFieldValue(IdBase instance, Object value);
getResultSetValue(ResultSet rs, int j)743         Object getResultSetValue(ResultSet rs, int j)
744                 throws SQLException;
getFieldValue(IdBase instance)745         Object getFieldValue(IdBase instance);
setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)746         public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value)
747                 throws SQLException;
748     }
749 
getA1for(int number, int index)750     protected String getA1for(int number, int index) {
751         int a1factor = 1 + number/a1values.length;
752         return a1values[index/a1factor];
753     }
754 
getA3for(long i)755     protected String getA3for(long i) {
756         return "employeenumber=100000" + i;
757     }
758 
createAllPrimitivesInstances(int number)759     protected void createAllPrimitivesInstances(int number) {
760         createAllPrimitivesInstances(session, number);
761     }
762 
createAllPrimitivesInstances(Session session, int number)763     protected void createAllPrimitivesInstances(Session session, int number) {
764         for (int i = 0; i < number; ++i) {
765             AllPrimitives instance = createAllPrimitiveInstance(session, i);
766             instances.add(instance);
767         }
768     }
769 
createAllPrimitiveInstance(Session session, int i)770     protected AllPrimitives createAllPrimitiveInstance(Session session, int i) {
771         AllPrimitives instance = session.newInstance(AllPrimitives.class, i);
772         initialize(instance, i);
773         return instance;
774     }
775 
initialize(AllPrimitives instance, int i)776     protected void initialize(AllPrimitives instance, int i) {
777         instance.setInt_not_null_hash(i);
778         instance.setInt_not_null_btree(i);
779         instance.setInt_not_null_both(i);
780         instance.setInt_not_null_none(i);
781         instance.setInt_null_hash(i);
782         instance.setInt_null_btree(i);
783         instance.setInt_null_both(i);
784         instance.setInt_null_none(i);
785 
786         instance.setLong_not_null_hash((long)i);
787         instance.setLong_not_null_btree((long)i);
788         instance.setLong_not_null_both((long)i);
789         instance.setLong_not_null_none((long)i);
790         instance.setLong_null_hash((long)i);
791         instance.setLong_null_btree((long)i);
792         instance.setLong_null_both((long)i);
793         instance.setLong_null_none((long)i);
794 
795         instance.setByte_not_null_hash((byte)i);
796         instance.setByte_not_null_btree((byte)i);
797         instance.setByte_not_null_both((byte)i);
798         instance.setByte_not_null_none((byte)i);
799         instance.setByte_null_hash((byte)i);
800         instance.setByte_null_btree((byte)i);
801         instance.setByte_null_both((byte)i);
802         instance.setByte_null_none((byte)i);
803 
804         instance.setShort_not_null_hash((short)i);
805         instance.setShort_not_null_btree((short)i);
806         instance.setShort_not_null_both((short)i);
807         instance.setShort_not_null_none((short)i);
808         instance.setShort_null_hash((short)i);
809         instance.setShort_null_btree((short)i);
810         instance.setShort_null_both((short)i);
811         instance.setShort_null_none((short)i);
812     }
813 
setupDn2idPK()814     protected static Object[] setupDn2idPK() {
815         Object[] result = new Object[16];
816         result[0] = "dc=com";
817         // pk[1] changes and is set inside loop
818         result[1] = "dc=example";
819         result[2] = "ou=people";
820         // pk[3] changes and is set inside loop
821         result[4] = "";
822         result[5] = "";
823         result[6] = "";
824         result[7] = "";
825         result[8] = "";
826         result[9] = "";
827         result[10] = "";
828         result[11] = "";
829         result[12] = "";
830         result[13] = "";
831         result[14] = "";
832         result[15] = "";
833         return result;
834     }
835 
836 }
837