Author: [log in to unmask] Date: Fri Apr 17 17:20:40 2015 New Revision: 2744 Log: Add refactored API classes to conditions branch. Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObject.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObjectCollection.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObject.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectCollection.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValues.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValuesMap.java java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableMetaData.java java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/ java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectCollectionTest.java java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectTest.java Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObject.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObject.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObject.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,356 @@ +package org.hps.conditions.apinew; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.hps.conditions.api.ConditionsObjectException; + +/** + * This is a basic ORM class for performing CRUD (create, read, update, delete) operations on objects in the conditions + * system. Each object is mapped to a single row in a database table. + * + * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a> + */ +public class BaseConditionsObject implements ConditionsObject { + + /** + * Field name for collection ID. + */ + static final String COLLECTION_ID_FIELD = "collection_id"; + + /** + * Date formatting for insert statement. + */ + static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss"); + + static final int UNSET_COLLECTION_ID = -1; + + static final int UNSET_ID = -1; + + protected static String defaultToString(final BaseConditionsObject object) { + final StringBuffer sb = new StringBuffer(); + sb.append("id: " + object.getId() + ", "); + for (final String field : object.getFieldValues().getFieldNames()) { + sb.append(field + "=" + object.getValue(Object.class, field) + ", "); + } + sb.setLength(sb.length() - 2); + sb.append('\n'); + return sb.toString(); + } + + /** + * The JDBC database connection. + */ + private Connection connection; + + /** + * The field values. + */ + private final FieldValues fields; + + /** + * The row ID of the object in its table. This will be -1 for new objects that are not in the database. + */ + private int id = UNSET_ID; + + /** + * Flag to indicate object is locally changed and database update has not been executed. + */ + private boolean isDirty; + + /** + * The information about the associated table such as the table and column names. + */ + private TableMetaData tableMetaData; + + protected BaseConditionsObject() { + this.fields = new FieldValuesMap(); + } + + /** + * Class constructor. + * <p> + * This should be used when creating new objects without a list of field values. A new <code>FieldValues</code> + * object will be automatically created from the table information. + * + * @param connection + * @param tableMetaData + */ + public BaseConditionsObject(final Connection connection, final TableMetaData tableMetaData) { + this.connection = connection; + this.tableMetaData = tableMetaData; + this.fields = new FieldValuesMap(tableMetaData); + } + + /** + * Class constructor. + * <p> + * This should be used when creating new objects with a list of field values. + * + * @param connection + * @param tableMetaData + * @param fields + */ + public BaseConditionsObject(final Connection connection, final TableMetaData tableMetaData, final FieldValues fields) { + this.connection = connection; + this.tableMetaData = tableMetaData; + this.fields = fields; + } + + /** + * Class constructor. + * <p> + * This should be used when the object is already in the database and the row ID is known. A SQL SELECT operation + * will be performed to get data into this object from the database. + * + * @param connection + * @param rowId + * @param tableMetaData + * @throw ConditionsObjectException if getting data into this object fails + * @throw SQLException if there is an SQL error when executing the SELECT operation + */ + public BaseConditionsObject(final Connection connection, final TableMetaData tableMetaData, final int rowId) + throws ConditionsObjectException, SQLException { + this.connection = connection; + this.tableMetaData = tableMetaData; + this.fields = new FieldValuesMap(tableMetaData); + final boolean selected = select(rowId); + if (!selected) { + throw new ConditionsObjectException("Failed to select data into object using row ID " + rowId); + } + } + + @Override + public final boolean delete() throws ConditionsObjectException, SQLException { + if (isNew()) { + throw new ConditionsObjectException("Object is not in database and so cannot be deleted."); + } + final String sql = "DELETE FROM " + this.tableMetaData.getTableName() + " WHERE id=" + this.getId(); + // if (this.verbose) { + // System.out.println(sql); + // } + Statement statement = null; + int rowsUpdated; + try { + statement = this.connection.createStatement(); + rowsUpdated = statement.executeUpdate(sql); + } finally { + if (statement != null) { + statement.close(); + } + } + return rowsUpdated != 0; + } + + @Override + public final int getCollectionId() { + if (this.fields.isNonNull(COLLECTION_ID_FIELD)) { + return getValue(Integer.class, COLLECTION_ID_FIELD); + } else { + return UNSET_COLLECTION_ID; + } + } + + @Override + public FieldValues getFieldValues() { + return this.fields; + } + + @Override + public final int getId() { + return this.id; + } + + @Override + public final TableMetaData getTableMetaData() { + return this.tableMetaData; + } + + @Override + public final <T> T getValue(final Class<T> type, final String name) { + return type.cast(this.fields.getValue(type, name)); + } + + @Override + public final boolean insert() throws ConditionsObjectException, SQLException { + if (!isNew()) { + throw new ConditionsObjectException("Cannot insert an existing record."); + } + final StringBuffer sb = new StringBuffer(); + sb.append("INSERT INTO " + this.tableMetaData.getTableName() + " ("); + for (final String fieldName : this.fields.getFieldNames()) { + sb.append(fieldName + ", "); + } + sb.setLength(sb.length() - 2); + sb.append(") VALUES ("); + for (final Object value : this.fields.getValues()) { + if (value instanceof Date) { + sb.append("STR_TO_DATE( '" + DATE_FORMAT.format((Date) value) + "', '%Y-%m-%d %H:%i:%S' ), "); + } else { + sb.append("'" + value + "', "); + } + } + sb.setLength(sb.length() - 2); + sb.append(")"); + final String sql = sb.toString(); + // if (this.verbose) { + // System.out.println(sql); + // } + Statement statement = null; + ResultSet resultSet = null; + int rowsUpdated = 0; + try { + statement = this.connection.createStatement(); + rowsUpdated = statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); + resultSet = statement.getGeneratedKeys(); + while (resultSet.next()) { + final int key = resultSet.getInt(1); + this.id = key; + break; + } + } finally { + if (resultSet != null) { + resultSet.close(); + } + if (statement != null) { + statement.close(); + } + } + return rowsUpdated != 0; + } + + @Override + public final boolean isDirty() { + return this.isDirty; + } + + @Override + public final boolean isNew() { + return getId() == UNSET_ID; + } + + @Override + public final boolean select(final int id) throws ConditionsObjectException, SQLException { + this.id = id; + if (id < 1) { + throw new IllegalArgumentException("bad row ID value: " + id); + } + final StringBuffer sb = new StringBuffer(); + sb.append("SELECT"); + for (final String fieldName : this.tableMetaData.getFieldNames()) { + sb.append(" " + fieldName + ","); + } + sb.setLength(sb.length() - 1); + sb.append(" FROM " + this.tableMetaData.getTableName()); + sb.append(" WHERE id = " + this.getId()); + final String sql = sb.toString(); + // if (this.verbose) { + // System.out.println(sql); + // } + Statement statement = null; + ResultSet resultSet = null; + boolean selected = false; + try { + statement = this.connection.createStatement(); + resultSet = statement.executeQuery(sql); + while (resultSet.next()) { + for (int columnIndex = 1; columnIndex <= this.tableMetaData.getFieldNames().length; columnIndex++) { + this.setValue(this.tableMetaData.getFieldNames()[columnIndex - 1], resultSet.getObject(columnIndex)); + } + selected = true; + } + } finally { + if (resultSet != null) { + resultSet.close(); + } + if (statement != null) { + statement.close(); + } + } + return selected; + } + + @Override + public void setCollectionId(final int collectionId) throws ConditionsObjectException { + if (this.getCollectionId() != UNSET_COLLECTION_ID) { + throw new ConditionsObjectException("The collection ID is already set on this object."); + } + if (collectionId <= UNSET_COLLECTION_ID) { + throw new ConditionsObjectException("Invalid collection ID value: " + collectionId); + } + this.setValue(COLLECTION_ID_FIELD, collectionId); + } + + @Override + public final void setConnection(final Connection connection) { + this.connection = connection; + } + + @Override + public void setId(final int id) { + this.id = id; + } + + @Override + public final void setTableMetaData(final TableMetaData tableMetaData) { + this.tableMetaData = tableMetaData; + } + + @Override + public final void setValue(final String name, final Object value) { + this.fields.setValue(name, value); + this.isDirty = true; + } + + @Override + public String toString() { + return defaultToString(this); + } + + @Override + public final boolean update() throws ConditionsObjectException, SQLException { + if (isDirty()) { + if (isNew()) { + throw new ConditionsObjectException("Cannot update a new object."); + } + final StringBuffer sb = new StringBuffer(); + sb.append("UPDATE " + this.tableMetaData.getTableName() + " SET "); + for (final String fieldName : this.fields.getFieldNames()) { + sb.append(fieldName + "="); + final Object value = this.fields.getValue(fieldName); + if (value instanceof Date) { + // FIXME: Is there a more generic way to handle this? + sb.append("STR_TO_DATE( '" + DATE_FORMAT.format((Date) value) + "', '%Y-%m-%d %H:%i:%S' ), "); + } else { + sb.append("'" + value + "', "); + } + } + sb.setLength(sb.length() - 2); + sb.append(" WHERE id=" + this.getId()); + final String sql = sb.toString(); + // if (this.verbose) { + // System.out.println(sql); + // } + Statement statement = null; + int rowsUpdated = 0; + try { + statement = this.connection.createStatement(); + rowsUpdated = statement.executeUpdate(sql); + if (rowsUpdated > 0) { + this.isDirty = false; + } + } finally { + if (statement != null) { + statement.close(); + } + } + return rowsUpdated != 0; + } else { + return false; + } + } +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObjectCollection.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObjectCollection.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObjectCollection.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,241 @@ +package org.hps.conditions.apinew; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.hps.conditions.api.ConditionsObjectException; + +/** + * + */ +public class BaseConditionsObjectCollection<ObjectType extends ConditionsObject> implements + ConditionsObjectCollection<ObjectType> { + + private int collectionId; + private Connection connection; + private final Set<ObjectType> objects = new LinkedHashSet<ObjectType>(); + private TableMetaData tableMetaData; + + protected BaseConditionsObjectCollection() { + } + + public BaseConditionsObjectCollection(final Connection connection, final TableMetaData tableMetaData, + final int collectionId) throws SQLException, ConditionsObjectException { + this.connection = connection; + this.tableMetaData = tableMetaData; + this.collectionId = collectionId; + if (collectionId != -1) { + select(collectionId); + } + } + + @Override + public void add(final ObjectType object) throws ConditionsObjectException { + // System.out.println("adding object " + object + " to collection " + getCollectionId()); + if (getCollectionId() != BaseConditionsObject.UNSET_COLLECTION_ID) { + if (object.getCollectionId() != BaseConditionsObject.UNSET_ID) { + if (object.getCollectionId() != this.collectionId) { + throw new ConditionsObjectException("Cannot add object with different collection ID: " + + object.getCollectionId()); + } + } else { + // System.out.println("object.setCollectionId - " + this.collectionId); + object.setCollectionId(this.collectionId); + } + } + this.objects.add(object); + } + + /** + * @throws ConditionsObjectException + * @throws SQLException + */ + @Override + public void deleteAll() throws ConditionsObjectException, SQLException { + Statement statement = null; + try { + final String sql = "DELETE FROM `" + this.tableMetaData.getTableName() + "` WHERE collection_id = '" + + getCollectionId() + "'"; + statement = this.connection.createStatement(); + statement.executeUpdate(sql); + // System.out.println("result from delete: " + result); + this.connection.commit(); + } catch (final SQLException e) { + e.printStackTrace(); + } finally { + if (statement != null) { + statement.close(); + } + } + } + + @Override + public ObjectType get(final int index) { + if (index + 1 > this.size() || index < 0) { + throw new IndexOutOfBoundsException("index out of bounds: " + index); + } + int current = 0; + final Iterator<ObjectType> iterator = this.objects.iterator(); + ObjectType object = iterator.next(); + while (current != index && iterator.hasNext()) { + object = iterator.next(); + current++; + } + return object; + } + + @Override + public int getCollectionId() { + return this.collectionId; + } + + @Override + public Collection<ObjectType> getObjects() { + return this.objects; + } + + @Override + public TableMetaData getTableMetaData() { + return this.tableMetaData; + } + + /** + * @param collectionId + * @throws ConditionsObjectException + * @throws SQLException + */ + @Override + public void insertAll(final int collectionId) throws ConditionsObjectException, SQLException { + if (this.collectionId != -1) { + throw new RuntimeException("collection already exists"); + } + this.collectionId = collectionId; + + PreparedStatement insertObjects = null; + final StringBuffer sb = new StringBuffer(); + sb.append("INSERT INTO " + this.getTableMetaData().getTableName() + " ("); + for (final String field : this.getTableMetaData().getFieldNames()) { + sb.append(field + ", "); + } + sb.setLength(sb.length() - 2); + sb.append(") VALUES ("); + for (int fieldIndex = 0; fieldIndex < this.getTableMetaData().getFieldNames().length; fieldIndex++) { + sb.append("?, "); + } + sb.setLength(sb.length() - 2); + sb.append(")"); + final String updateStatement = sb.toString(); + // System.out.println(updateStatement); + try { + this.connection.setAutoCommit(false); + insertObjects = this.connection.prepareStatement(updateStatement, Statement.RETURN_GENERATED_KEYS); + for (final ObjectType object : this.getObjects()) { + for (int fieldIndex = 0; fieldIndex < this.getTableMetaData().getFieldNames().length; fieldIndex++) { + final String fieldName = this.getTableMetaData().getFieldNames()[fieldIndex]; + object.getValue(getTableMetaData().getFieldType(fieldName), fieldName); + // System.out.println(fieldName + "=" + value); + if (fieldName.equals(BaseConditionsObject.COLLECTION_ID_FIELD)) { + insertObjects.setObject(fieldIndex + 1, getCollectionId()); + } else { + insertObjects.setObject(fieldIndex + 1, + object.getValue(getTableMetaData().getFieldType(fieldName), fieldName)); + } + } + insertObjects.executeUpdate(); + this.connection.commit(); + final ResultSet resultSet = insertObjects.getGeneratedKeys(); + resultSet.next(); + object.setId(resultSet.getInt(1)); + // System.out.println("set id to " + resultSet.getInt(1) + " from generated keys"); + resultSet.close(); + } + } catch (final SQLException e1) { + e1.printStackTrace(); + if (this.connection != null) { + try { + System.err.println("Transaction is being rolled back ..."); + this.connection.rollback(); + System.err.println("Done rolling back transaction!"); + } catch (final SQLException e2) { + e2.printStackTrace(); + } + } + } finally { + if (insertObjects != null) { + insertObjects.close(); + } + } + } + + @Override + public boolean remove(final int index) { + final ObjectType object = get(index); + if (object != null) { + return this.objects.remove(object); + } else { + return false; + } + } + + @Override + public void select(final int collectionId) throws SQLException, ConditionsObjectException { + // System.out.println("BaseConditionsObjectCollection.select - " + collectionId); + this.collectionId = collectionId; + Statement statement = null; + try { + statement = this.connection.createStatement(); + final StringBuffer sb = new StringBuffer(); + sb.append("SELECT id, "); + for (final String fieldName : this.tableMetaData.getFieldNames()) { + sb.append(fieldName + ", "); + } + sb.setLength(sb.length() - 2); + sb.append(" FROM " + this.tableMetaData.getTableName() + " WHERE collection_id=" + collectionId); + final String sql = sb.toString(); + // System.out.println(sql); + final ResultSet resultSet = statement.executeQuery(sql); + while (resultSet.next()) { + try { + final ObjectType newObject = (ObjectType) this.tableMetaData.getObjectClass().newInstance(); + newObject.setConnection(this.connection); + newObject.setTableMetaData(this.tableMetaData); + final int id = resultSet.getInt(1); + newObject.setId(id); + for (int fieldIndex = 0; fieldIndex < this.tableMetaData.getFieldNames().length; fieldIndex++) { + final String fieldName = this.tableMetaData.getFieldNames()[fieldIndex]; + newObject.setValue(fieldName, resultSet.getObject(fieldIndex + 2)); + } + add(newObject); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } finally { + if (statement != null) { + statement.close(); + } + } + } + + @Override + public int size() { + return this.objects.size(); + } + + @Override + // FIXME: Should execute prepared statement in transaction. + public void updateAll() throws ConditionsObjectException, SQLException { + for (final ObjectType object : this.objects) { + if (object.isDirty()) { + object.update(); + } + } + } +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObject.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObject.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObject.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,86 @@ +package org.hps.conditions.apinew; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.hps.conditions.api.ConditionsObjectException; + +public interface ConditionsObject { + + /** + * @return + * @throws ConditionsObjectException + * @throws SQLException + */ + boolean delete() throws ConditionsObjectException, SQLException; + + /** + * @return + */ + int getCollectionId(); + + FieldValues getFieldValues(); + + /** + * @return + */ + int getId(); + + /** + * @return + */ + TableMetaData getTableMetaData(); + + /** + * @param type + * @param name + * @return + */ + <T> T getValue(final Class<T> type, final String name); + + /** + * @return + * @throws ConditionsObjectException + * @throws SQLException + */ + boolean insert() throws ConditionsObjectException, SQLException; + + /** + * @return + */ + boolean isDirty(); + + /** + * @return + */ + boolean isNew(); + + boolean select(final int rowId) throws ConditionsObjectException, SQLException; + + /** + * @param collectionId + * @throws ConditionsObjectException + */ + void setCollectionId(int collectionId) throws ConditionsObjectException; + + /** + * @param connection + */ + void setConnection(Connection connection); + + void setId(int id); + + void setTableMetaData(TableMetaData tableMetaData); + + /** + * @param name + * @param value + */ + void setValue(String name, Object value); + + /** + * @return + * @throws ConditionsObjectException + */ + boolean update() throws ConditionsObjectException, SQLException; +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectCollection.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectCollection.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectCollection.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,31 @@ +package org.hps.conditions.apinew; + +import java.sql.SQLException; +import java.util.Collection; + +import org.hps.conditions.api.ConditionsObjectException; + +public interface ConditionsObjectCollection<ObjectType extends ConditionsObject> { + + void add(final ObjectType object) throws ConditionsObjectException; + + void deleteAll() throws ConditionsObjectException, SQLException; + + ObjectType get(final int index); + + int getCollectionId(); + + Collection<ObjectType> getObjects(); + + TableMetaData getTableMetaData(); + + void insertAll(final int collectionId) throws ConditionsObjectException, SQLException; + + boolean remove(final int index); + + void select(final int collectionId) throws ConditionsObjectException, SQLException; + + int size(); + + void updateAll() throws ConditionsObjectException, SQLException; +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValues.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValues.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValues.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,26 @@ +package org.hps.conditions.apinew; + +import java.util.Collection; +import java.util.Set; + +/** + * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a> + */ +public interface FieldValues { + + Set<String> getFieldNames(); + + <T> T getValue(Class<T> type, String name); + + Object getValue(String name); + + Collection<Object> getValues(); + + boolean hasField(String name); + + boolean isNonNull(String name); + + void setValue(String name, Object value); + + int size(); +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValuesMap.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValuesMap.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValuesMap.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,63 @@ +package org.hps.conditions.apinew; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a> + */ +public class FieldValuesMap implements FieldValues { + + Map<String, Object> data = new HashMap<String, Object>(); + + FieldValuesMap() { + } + + FieldValuesMap(final TableMetaData tableMetaData) { + for (final String fieldName : tableMetaData.getFieldNames()) { + this.data.put(fieldName, null); + } + } + + @Override + public Set<String> getFieldNames() { + return this.data.keySet(); + } + + @Override + public <T> T getValue(final Class<T> type, final String name) { + return type.cast(this.data.get(name)); + } + + @Override + public Object getValue(final String name) { + return this.data.get(name); + } + + @Override + public Collection<Object> getValues() { + return this.data.values(); + } + + @Override + public boolean hasField(final String name) { + return this.data.containsKey(name); + } + + @Override + public boolean isNonNull(final String name) { + return this.data.get(name) != null; + } + + @Override + public void setValue(final String name, final Object value) { + this.data.put(name, value); + } + + @Override + public int size() { + return this.data.size(); + } +} Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableMetaData.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableMetaData.java (added) +++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableMetaData.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,208 @@ +package org.hps.conditions.apinew; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * <p> + * This class provides meta data about a conditions table, including a list of conditions data fields. The list of + * fields does not include the collection ID or row ID, which are implicitly assumed to exist. + * <p> + * It also has references to the implementation classes which are used for the ORM onto {@link ConditionsObject} and + * {@link ConditionsObjectCollection}. + * + * @see org.hps.conditions.api.ConditionsObject + * @see org.hps.conditions.api.BaseConditionsObjectCollection + * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a> + */ +public final class TableMetaData { + + /** + * Find table meta data by object type. + * + * @param tableMetaDataList the list of table meta data e.g. from the registry + * @param objectType the type of the object + * @return the list of table meta data that have that object type + */ + public static List<TableMetaData> findByObjectType(final List<TableMetaData> tableMetaDataList, + final Class<? extends ConditionsObject> objectType) { + final List<TableMetaData> list = new ArrayList<TableMetaData>(); + for (final TableMetaData tableMetaData : tableMetaDataList) { + if (tableMetaData.getObjectClass().equals(objectType)) { + + list.add(tableMetaData); + } + } + return list; + } + + /** + * The collection class. + */ + private Class<? extends BaseConditionsObjectCollection<?>> collectionClass; + + /** + * The set of field names. + */ + private Set<String> fieldNames = new LinkedHashSet<String>(); + + /** + * The map of field names to their types. + */ + private Map<String, Class<?>> fieldTypes = new HashMap<String, Class<?>>(); + + /** + * The conditions key named (unused???). + */ + private String key; + + /** + * The object class. + */ + private Class<? extends ConditionsObject> objectClass; + + /** + * The table name. + */ + private String tableName; + + public TableMetaData() { + } + + /** + * Fully qualified constructor. + * + * @param key the conditions key + * @param tableName the table name + * @param objectClass the object class + * @param collectionClass the collection class + * @param fieldNames the field names + * @param fieldTypes the field types + */ + public TableMetaData(final String key, final String tableName, final Class<? extends ConditionsObject> objectClass, + final Class<? extends BaseConditionsObjectCollection<?>> collectionClass, final Set<String> fieldNames, + final Map<String, Class<?>> fieldTypes) { + if (key == null) { + throw new IllegalArgumentException("key is null"); + } + if (tableName == null) { + throw new IllegalArgumentException("tableName is null"); + } + if (objectClass == null) { + throw new IllegalArgumentException("objectClass is null"); + } + if (fieldNames == null) { + throw new IllegalArgumentException("fieldNames is null"); + } + if (collectionClass == null) { + throw new IllegalArgumentException("collectionClass is null"); + } + if (fieldTypes == null) { + throw new IllegalArgumentException("fieldTypes is null"); + } + this.key = key; + this.tableName = tableName; + this.objectClass = objectClass; + this.collectionClass = collectionClass; + this.fieldNames = fieldNames; + this.fieldTypes = fieldTypes; + } + + /** + * Get the type of collection this table maps onto. + * + * @return the collection class + */ + public Class<? extends BaseConditionsObjectCollection<?>> getCollectionClass() { + return this.collectionClass; + } + + /** + * Get the names of the fields. Types are implied from the database tables. + * + * @return the names of the fields + */ + public String[] getFieldNames() { + return this.fieldNames.toArray(new String[] {}); + } + + /** + * Get the type of the field called <code>fieldName</code>. + * + * @return the type of the field + */ + public Class<?> getFieldType(final String fieldName) { + return this.fieldTypes.get(fieldName); + } + + /** + * Get the key of this conditions type. May be different from table name but is usually the same. + * + * @return the key name of the conditions type + */ + public String getKey() { + return this.key; + } + + /** + * Get the type of object this table maps onto. + * + * @return the type of object + */ + public Class<? extends ConditionsObject> getObjectClass() { + return this.objectClass; + } + + /** + * Get the name of the table. + * + * @return the name of the table + */ + public String getTableName() { + return this.tableName; + } + + void setFieldNames(final String[] fieldNames) { + this.fieldNames = new HashSet<String>(); + for (final String fieldName : fieldNames) { + this.fieldNames.add(fieldName); + } + } + + void setFieldType(final String fieldName, final Class<?> fieldType) { + this.fieldTypes.put(fieldName, fieldType); + } + + void setObjectClass(final Class<? extends ConditionsObject> objectClass) { + this.objectClass = objectClass; + } + + void setTableName(final String tableName) { + this.tableName = tableName; + } + + /** + * Convert to a string. + * + * @return This object converted to a string. + */ + @Override + public String toString() { + final StringBuffer buff = new StringBuffer(); + buff.append("tableMetaData: tableName = " + this.getTableName()); + buff.append(", objectClass = " + this.getObjectClass().getCanonicalName()); + buff.append(", collectionClass = " + this.getCollectionClass().getCanonicalName()); + buff.append(", fieldNames = "); + for (final String field : this.getFieldNames()) { + buff.append(field + " "); + } + buff.setLength(buff.length() - 1); + buff.append('\n'); + return buff.toString(); + } +} Added: java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectCollectionTest.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectCollectionTest.java (added) +++ java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectCollectionTest.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,97 @@ +package org.hps.conditions.apinew; + +import java.sql.Connection; +import java.sql.SQLException; + +import junit.framework.TestCase; + +import org.hps.conditions.api.ConditionsObjectException; +import org.hps.conditions.database.DatabaseConditionsManager; + +public class BaseConditionsObjectCollectionTest extends TestCase { + + /** + * A dummy conditions object type. + */ + static class DummyConditionsObject extends BaseConditionsObject { + + public DummyConditionsObject() { + } + + DummyConditionsObject(final Connection connection, final TableMetaData tableMetaData) { + super(connection, tableMetaData); + } + } + + /** + * A dummy conditions object collection type. + */ + static class DummyConditionsObjectCollection extends BaseConditionsObjectCollection<DummyConditionsObject> { + public DummyConditionsObjectCollection() { + } + + DummyConditionsObjectCollection(final Connection connection, final TableMetaData tableMetaData) + throws SQLException, ConditionsObjectException { + super(connection, tableMetaData, -1); + } + } + + public void testBaseConditionsObjectCollection() throws Exception { + + // Configure the conditions system. + final DatabaseConditionsManager manager = DatabaseConditionsManager.getInstance(); + manager.setConnectionResource("/org/hps/conditions/config/jeremym_dev_connection.prop"); + manager.setXmlConfig("/org/hps/conditions/config/conditions_database_no_svt.xml"); + final Connection connection = manager.getConnection(); + + // Setup basic table meta data. + final TableMetaData tableMetaData = new TableMetaData(); + tableMetaData.setTableName("dummy"); + tableMetaData.setFieldNames(new String[] {"collection_id", "dummy"}); + tableMetaData.setFieldType("collection_id", Integer.class); + tableMetaData.setFieldType("dummy", Double.class); + tableMetaData.setObjectClass(DummyConditionsObject.class); + + // Create a new collection. + final DummyConditionsObjectCollection collection = new DummyConditionsObjectCollection(connection, + tableMetaData); + + // Add object to collection. + final DummyConditionsObject object1 = new DummyConditionsObject(connection, tableMetaData); + object1.setValue("dummy", 1.0); + collection.add(object1); + + // Add object to collection. + final DummyConditionsObject object2 = new DummyConditionsObject(connection, tableMetaData); + object2.setValue("dummy", 2.0); + collection.add(object2); + + final int collectionId = 1001; + + // Insert all objects into the database. + System.out.println("inserting objects from new collection ID " + collectionId); + collection.insertAll(collectionId); + + System.out.println("inserted " + collection.size() + " objects"); + + // Create another collection. + final DummyConditionsObjectCollection anotherCollection = new DummyConditionsObjectCollection(connection, + tableMetaData); + + // Select the previously created objects into this collection by using the collection_id value. + anotherCollection.select(collectionId); + System.out.println("selected " + anotherCollection.size() + " objects into collection"); + + // TODO: change objects in collection and then call updateAll + anotherCollection.get(0).setValue("dummy", 3.0); + anotherCollection.get(1).setValue("dummy", 4.0); + + // Update all objects. + System.out.println("updating objects from collection " + collection.getCollectionId()); + anotherCollection.updateAll(); + + // Delete all objects. + System.out.println("deleting objects from collection " + collection.getCollectionId()); + collection.deleteAll(); + } +} Added: java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectTest.java ============================================================================= --- java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectTest.java (added) +++ java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/BaseConditionsObjectTest.java Fri Apr 17 17:20:40 2015 @@ -0,0 +1,75 @@ +package org.hps.conditions.apinew; + +import java.sql.Connection; + +import junit.framework.TestCase; + +import org.hps.conditions.database.DatabaseConditionsManager; + +public class BaseConditionsObjectTest extends TestCase { + + /** + * A dummy conditions object type. + */ + static class DummyConditionsObject extends BaseConditionsObject { + DummyConditionsObject(final Connection connection, final TableMetaData tableMetaData) { + super(connection, tableMetaData); + } + } + + /** + * Perform basic CRUD operations on the <code>BaseConditionsObject</code> class. + * + * @throws Exception if some test error occurs + */ + public void testBaseConditionsObject() throws Exception { + + // Configure the conditions system. + final DatabaseConditionsManager manager = DatabaseConditionsManager.getInstance(); + manager.setConnectionResource("/org/hps/conditions/config/jeremym_dev_connection.prop"); + manager.setXmlConfig("/org/hps/conditions/config/conditions_database_no_svt.xml"); + + // Open the database connection. + final Connection connection = manager.getConnection(); + + // Setup basic table meta data. + final TableMetaData tableMetaData = new TableMetaData(); + tableMetaData.setTableName("dummy"); + tableMetaData.setFieldNames(new String[] {"collection_id", "dummy"}); + + // Insert a new object. + final DummyConditionsObject newObject = new DummyConditionsObject(connection, tableMetaData); + newObject.setValue("collection_id", 1); + newObject.setValue("dummy", 1.0); + final boolean inserted = newObject.insert(); + assertTrue("Insert failed.", inserted); + System.out.println("Inserted new object with id " + newObject.getId()); + + // Update the same object. + newObject.setValue("dummy", 2.0); + boolean updated = newObject.update(); + assertTrue("Update failed.", updated); + + // Update which should be ignored on non-dirty record. + updated = newObject.update(); + assertTrue("Update should not have been executed.", !updated); + System.out.println("Update ignored on non-dirty record."); + + // Select into another object using the row ID. + final DummyConditionsObject anotherObject = new DummyConditionsObject(connection, tableMetaData); + final boolean selected = anotherObject.select(newObject.getId()); + assertTrue("Select failed.", selected); + assertEquals("Select object has wrong row ID.", newObject.getId(), anotherObject.getId()); + assertEquals("Select object has wrong collcetion ID.", newObject.getValue(Integer.class, "collection_id"), + anotherObject.getValue(Integer.class, "collection_id")); + assertEquals("Select object has wrong value.", newObject.getValue(Double.class, "dummy"), + anotherObject.getValue(Double.class, "dummy")); + + // Delete the object. + final boolean deleted = newObject.delete(); + assertTrue("Delete failed.", deleted); + + // Close the database connection. + connection.close(); + } +}