Print

Print


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();
+    }
+}