Print

Print


Author: [log in to unmask]
Date: Mon Apr 20 15:47:54 2015
New Revision: 2756

Log:
implement conditions object conversion

Added:
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/AbstractConditionsObjectConverter.java
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectUtilities.java
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableRegistry.java
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObject.java
    java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObjectConverter.java
    java/branches/conditions-HPSJAVA-488/src/main/resources/org/hps/conditions/config/jeremym_dev_connection.prop
    java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/DummyConditionsObjectConverterTest.java
Modified:
    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

Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/AbstractConditionsObjectConverter.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/AbstractConditionsObjectConverter.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/AbstractConditionsObjectConverter.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,178 @@
+package org.hps.conditions.apinew;
+
+import java.sql.SQLException;
+
+import org.hps.conditions.api.ConditionsObjectException;
+import org.hps.conditions.api.ConditionsRecord;
+import org.hps.conditions.api.ConditionsRecord.ConditionsRecordCollection;
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.hps.conditions.database.MultipleCollectionsAction;
+import org.lcsim.conditions.ConditionsConverter;
+import org.lcsim.conditions.ConditionsManager;
+
+/**
+ * <p>
+ * Implementation of default conversion from database tables to a {@link ConditionsObject} class.
+ * <p>
+ * This class actually returns collections and not individual objects.
+ *
+ * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
+ * @param <T> The type of the returned data which should be a class extending {@link BaseConditionsObjectCollection}.
+ */
+public abstract class AbstractConditionsObjectConverter<T> implements ConditionsConverter<T> {
+
+    /**
+     * Create a conditions object collection.
+     *
+     * @param conditionsRecord the conditions record
+     * @param tableMetaData the table data
+     * @return the conditions object collection
+     * @throws ConditionsObjectException if there is a problem creating the collection
+     */
+    static final BaseConditionsObjectCollection<?> createCollection(final DatabaseConditionsManager manager,
+            final ConditionsRecord conditionsRecord, final TableMetaData tableMetaData)
+            throws ConditionsObjectException {
+        BaseConditionsObjectCollection<?> collection;
+        try {
+            collection = tableMetaData.getCollectionClass().newInstance();
+            if (conditionsRecord != null) {
+                collection.setConnection(manager.getConnection());
+                collection.setTableMetaData(tableMetaData);
+                collection.setCollectionId(conditionsRecord.getCollectionId());
+            }
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new ConditionsObjectException("Error creating conditions object collection.", e);
+        }
+        return collection;
+    }
+
+    /**
+     * The action to take if multiple overlapping conditions sets are found. The default is using the most recently
+     * updated one.
+     */
+    private MultipleCollectionsAction multipleCollections = MultipleCollectionsAction.LAST_UPDATED;
+
+    /**
+     * Class constructor.
+     */
+    public AbstractConditionsObjectConverter() {
+    }
+
+    /**
+     * Get the conditions data based on the name, e.g. "ecal_channels". The table information is found using the type
+     * handled by the Converter.
+     *
+     * @param conditionsManager the current conditions manager
+     * @param name the name of the conditions set (maps to table name)
+     * @return the conditions data
+     */
+    @Override
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public T getData(final ConditionsManager conditionsManager, final String name) {
+
+        // Get the DatabaseConditionsManager which is required for using this converter.
+        final DatabaseConditionsManager databaseConditionsManager = (DatabaseConditionsManager) conditionsManager;
+
+        // Setup connection if necessary.
+        final boolean openedConnection = databaseConditionsManager.openConnection();
+
+        // Get the TableMetaData from the table name.
+        final TableMetaData tableMetaData = TableRegistry.getTableRegistry().findByTableName(name);
+
+        // Throw an exception if the table name does not map to a known type.
+        if (tableMetaData == null) {
+            throw new RuntimeException(new ConditionsObjectException("No table information found for name: " + name));
+        }
+
+        // Get the ConditionsRecordCollection with the run number assignments.
+        final ConditionsRecordCollection conditionsRecords = databaseConditionsManager.findConditionsRecords(name);
+
+        // The record with the collection information.
+        ConditionsRecord conditionsRecord = null;
+
+        // Now we need to determine which ConditionsRecord object to use.
+        if (conditionsRecords.size() == 0) {
+            // No conditions records were found for the key.
+            throw new RuntimeException("No conditions were found with key: " + name);
+        } else if (conditionsRecords.size() == 1) {
+            // Use the single conditions set that was found.
+            conditionsRecord = conditionsRecords.get(0);
+        } else if (conditionsRecords.size() > 1) {
+            if (this.multipleCollections.equals(MultipleCollectionsAction.LAST_UPDATED)) {
+                // Use the conditions set with the latest updated date.
+                conditionsRecord = conditionsRecords.sortedByUpdated().get(conditionsRecords.size() - 1);
+            } else if (this.multipleCollections.equals(MultipleCollectionsAction.LAST_CREATED)) {
+                // Use the conditions set with the latest created date.
+                conditionsRecord = conditionsRecords.sortedByCreated().get(conditionsRecords.size() - 1);
+            } else if (this.multipleCollections.equals(MultipleCollectionsAction.LATEST_RUN_START)) {
+                // Use the conditions set with the greatest run start value.
+                conditionsRecord = conditionsRecords.sortedByRunStart().get(conditionsRecords.size() - 1);
+            } else if (this.multipleCollections.equals(MultipleCollectionsAction.ERROR)) {
+                // The converter has been configured to throw an error.
+                throw new RuntimeException("Multiple ConditionsRecord object found for conditions key " + name);
+            }
+        }
+
+        // Create a collection of objects to return.
+        ConditionsObjectCollection collection = null;
+        try {
+            collection = createCollection(databaseConditionsManager, conditionsRecord, tableMetaData);
+        } catch (final ConditionsObjectException e) {
+            throw new RuntimeException(e);
+        }
+
+        DatabaseConditionsManager.getLogger().info("loading conditions set..." + '\n' + conditionsRecord);
+
+        // Select the objects into the collection by the collection ID.
+        try {
+            collection.select(conditionsRecord.getCollectionId());
+        } catch (ConditionsObjectException | SQLException e) {
+            throw new RuntimeException("Error creating conditions collection from table " + name
+                    + " with collection ID " + conditionsRecord.getCollectionId(), e);
+        }
+
+        if (openedConnection) {
+            // Close connection if one was opened.
+            databaseConditionsManager.closeConnection();
+        }
+
+        return (T) collection;
+    }
+
+    /**
+     * Get the multiple collections action.
+     *
+     * @return the multiple collections action
+     */
+    public final MultipleCollectionsAction getMultipleCollectionsAction() {
+        return this.multipleCollections;
+    }
+
+    /**
+     * Get the specific type converted by this class.
+     *
+     * @return the class that this converter handles
+     */
+    @Override
+    public abstract Class<T> getType();
+
+    /**
+     * Set the action that the converter will use to disambiguate when multiple conditions sets are found.
+     *
+     * @param multipleCollections the multiple collections action
+     */
+    final void setMultipleCollectionsAction(final MultipleCollectionsAction multipleCollections) {
+        this.multipleCollections = multipleCollections;
+    }
+
+    /**
+     * Convert object to string.
+     *
+     * @return the object converted to string
+     */
+    @Override
+    public String toString() {
+        return "ConditionsObjectConverter: type = " + this.getType() + ", multipleCollectionsAction = "
+                + this.getMultipleCollectionsAction().toString();
+    }
+}

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObject.java	Mon Apr 20 15:47:54 2015
@@ -8,6 +8,7 @@
 import java.util.Date;
 
 import org.hps.conditions.api.ConditionsObjectException;
+import org.hps.conditions.database.Field;
 
 /**
  * This is a basic ORM class for performing CRUD (create, read, update, delete) operations on objects in the conditions
@@ -50,7 +51,7 @@
     /**
      * The field values.
      */
-    private final FieldValues fields;
+    private FieldValues fieldValues;
 
     /**
      * The row ID of the object in its table. This will be -1 for new objects that are not in the database.
@@ -68,7 +69,7 @@
     private TableMetaData tableMetaData;
 
     protected BaseConditionsObject() {
-        this.fields = new FieldValuesMap();
+        this.fieldValues = new FieldValuesMap();
     }
 
     /**
@@ -83,7 +84,7 @@
     public BaseConditionsObject(final Connection connection, final TableMetaData tableMetaData) {
         this.connection = connection;
         this.tableMetaData = tableMetaData;
-        this.fields = new FieldValuesMap(tableMetaData);
+        this.fieldValues = new FieldValuesMap(tableMetaData);
     }
 
     /**
@@ -98,7 +99,7 @@
     public BaseConditionsObject(final Connection connection, final TableMetaData tableMetaData, final FieldValues fields) {
         this.connection = connection;
         this.tableMetaData = tableMetaData;
-        this.fields = fields;
+        this.fieldValues = fields;
     }
 
     /**
@@ -117,7 +118,7 @@
             throws ConditionsObjectException, SQLException {
         this.connection = connection;
         this.tableMetaData = tableMetaData;
-        this.fields = new FieldValuesMap(tableMetaData);
+        this.fieldValues = new FieldValuesMap(tableMetaData);
         final boolean selected = select(rowId);
         if (!selected) {
             throw new ConditionsObjectException("Failed to select data into object using row ID " + rowId);
@@ -147,8 +148,9 @@
     }
 
     @Override
-    public final int getCollectionId() {
-        if (this.fields.isNonNull(COLLECTION_ID_FIELD)) {
+    @Field(names = {"collection_id"})
+    public final Integer getCollectionId() {
+        if (this.fieldValues.isNonNull(COLLECTION_ID_FIELD)) {
             return getValue(Integer.class, COLLECTION_ID_FIELD);
         } else {
             return UNSET_COLLECTION_ID;
@@ -157,7 +159,7 @@
 
     @Override
     public FieldValues getFieldValues() {
-        return this.fields;
+        return this.fieldValues;
     }
 
     @Override
@@ -172,7 +174,7 @@
 
     @Override
     public final <T> T getValue(final Class<T> type, final String name) {
-        return type.cast(this.fields.getValue(type, name));
+        return type.cast(this.fieldValues.getValue(type, name));
     }
 
     @Override
@@ -182,12 +184,12 @@
         }
         final StringBuffer sb = new StringBuffer();
         sb.append("INSERT INTO " + this.tableMetaData.getTableName() + " (");
-        for (final String fieldName : this.fields.getFieldNames()) {
+        for (final String fieldName : this.fieldValues.getFieldNames()) {
             sb.append(fieldName + ", ");
         }
         sb.setLength(sb.length() - 2);
         sb.append(") VALUES (");
-        for (final Object value : this.fields.getValues()) {
+        for (final Object value : this.fieldValues.getValues()) {
             if (value instanceof Date) {
                 sb.append("STR_TO_DATE( '" + DATE_FORMAT.format((Date) value) + "', '%Y-%m-%d %H:%i:%S' ), ");
             } else {
@@ -291,6 +293,11 @@
     }
 
     @Override
+    public void setFieldValues(final FieldValues fieldValues) {
+        this.fieldValues = fieldValues;
+    }
+
+    @Override
     public void setId(final int id) {
         this.id = id;
     }
@@ -302,7 +309,7 @@
 
     @Override
     public final void setValue(final String name, final Object value) {
-        this.fields.setValue(name, value);
+        this.fieldValues.setValue(name, value);
         this.isDirty = true;
     }
 
@@ -319,9 +326,9 @@
             }
             final StringBuffer sb = new StringBuffer();
             sb.append("UPDATE " + this.tableMetaData.getTableName() + " SET ");
-            for (final String fieldName : this.fields.getFieldNames()) {
+            for (final String fieldName : this.fieldValues.getFieldNames()) {
                 sb.append(fieldName + "=");
-                final Object value = this.fields.getValue(fieldName);
+                final Object value = this.fieldValues.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' ), ");

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/BaseConditionsObjectCollection.java	Mon Apr 20 15:47:54 2015
@@ -6,6 +6,7 @@
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -18,7 +19,7 @@
 public class BaseConditionsObjectCollection<ObjectType extends ConditionsObject> implements
         ConditionsObjectCollection<ObjectType> {
 
-    private int collectionId;
+    private int collectionId = BaseConditionsObject.UNSET_COLLECTION_ID;
     private Connection connection;
     private final Set<ObjectType> objects = new LinkedHashSet<ObjectType>();
     private TableMetaData tableMetaData;
@@ -98,7 +99,7 @@
 
     @Override
     public Collection<ObjectType> getObjects() {
-        return this.objects;
+        return Collections.unmodifiableCollection(this.objects);
     }
 
     @Override
@@ -137,16 +138,13 @@
             this.connection.setAutoCommit(false);
             insertObjects = this.connection.prepareStatement(updateStatement, Statement.RETURN_GENERATED_KEYS);
             for (final ObjectType object : this.getObjects()) {
+                object.setCollectionId(this.collectionId);
                 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));
-                    }
+                    final Object value = object.getValue(getTableMetaData().getFieldType(fieldName), fieldName);
+                    System.out.println(fieldName + "=" + value);
+                    insertObjects.setObject(fieldIndex + 1,
+                            object.getValue(getTableMetaData().getFieldType(fieldName), fieldName));
                 }
                 insertObjects.executeUpdate();
                 this.connection.commit();
@@ -225,6 +223,20 @@
     }
 
     @Override
+    public void setCollectionId(final int collectionId) {
+        this.collectionId = collectionId;
+    }
+
+    public void setConnection(final Connection connection) {
+        this.connection = connection;
+    }
+
+    @Override
+    public void setTableMetaData(final TableMetaData tableMetaData) {
+        this.tableMetaData = tableMetaData;
+    }
+
+    @Override
     public int size() {
         return this.objects.size();
     }

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObject.java	Mon Apr 20 15:47:54 2015
@@ -17,7 +17,7 @@
     /**
      * @return
      */
-    int getCollectionId();
+    Integer getCollectionId();
 
     FieldValues getFieldValues();
 
@@ -68,6 +68,8 @@
      */
     void setConnection(Connection connection);
 
+    void setFieldValues(FieldValues fieldValues);
+
     void setId(int id);
 
     void setTableMetaData(TableMetaData tableMetaData);

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectCollection.java	Mon Apr 20 15:47:54 2015
@@ -25,6 +25,10 @@
 
     void select(final int collectionId) throws ConditionsObjectException, SQLException;
 
+    void setCollectionId(int collectionId);
+
+    void setTableMetaData(TableMetaData tableMetaData);
+
     int size();
 
     void updateAll() throws ConditionsObjectException, SQLException;

Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectUtilities.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectUtilities.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/ConditionsObjectUtilities.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,115 @@
+package org.hps.conditions.apinew;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+
+import javassist.Modifier;
+
+import org.hps.conditions.database.Field;
+import org.hps.conditions.database.Table;
+import org.reflections.Reflections;
+
+/**
+ * This is a collection of utility methods for {@link ConditionsObject}.
+ *
+ * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
+ */
+public final class ConditionsObjectUtilities {
+
+    /**
+     * Find all available classes that extend ConditionsObject.
+     *
+     * @return The set of all available classes that extend ConditionsObject.
+     */
+    public static Set<Class<? extends ConditionsObject>> findConditionsObjectTypes() {
+        final Reflections reflections = new Reflections("org.hps.conditions");
+        final Set<Class<? extends ConditionsObject>> objectTypes = new HashSet<Class<? extends ConditionsObject>>();
+        for (final Class<? extends ConditionsObject> objectType : reflections.getSubTypesOf(ConditionsObject.class)) {
+            if (Modifier.isAbstract(objectType.getModifiers())) {
+                continue;
+            }
+            if (objectType.getAnnotation(Table.class) == null) {
+                continue;
+            }
+            objectTypes.add(objectType);
+        }
+        return objectTypes;
+    }
+
+    /**
+     * Get the class for the collection of the ConditionsObject type.
+     *
+     * @param type the class of the ConditionsObject
+     * @return the class of the collection
+     */
+    @SuppressWarnings("unchecked")
+    public static Class<? extends BaseConditionsObjectCollection<? extends ConditionsObject>> getCollectionType(
+            final Class<? extends ConditionsObject> type) {
+        final String collectionClassName = type.getCanonicalName() + "$" + type.getSimpleName() + "Collection";
+        Class<?> rawCollectionClass;
+        try {
+            rawCollectionClass = Class.forName(collectionClassName);
+        } catch (final ClassNotFoundException e) {
+            throw new RuntimeException("The type does not define a nested collection class.", e);
+        }
+        if (!BaseConditionsObjectCollection.class.isAssignableFrom(rawCollectionClass)) {
+            throw new RuntimeException("The class " + rawCollectionClass.getSimpleName()
+                    + " does not extend ConditionsObjectCollection.");
+        }
+        return (Class<? extends BaseConditionsObjectCollection<? extends ConditionsObject>>) rawCollectionClass;
+    }
+
+    /**
+     * Get the list of database field names for the class.
+     *
+     * @param type the class
+     * @return the list of field names
+     */
+    public static Set<String> getFieldNames(final Class<? extends ConditionsObject> type) {
+        final Set<String> fieldNames = new HashSet<String>();
+        for (final Method method : type.getMethods()) {
+            System.out.println(method.getName());
+            if (!method.getReturnType().equals(Void.TYPE)) {
+                for (final Annotation annotation : method.getAnnotations()) {
+                    if (annotation.annotationType().equals(Field.class)) {
+                        if (!Modifier.isPublic(method.getModifiers())) {
+                            throw new RuntimeException("The method " + type.getName() + "." + method.getName()
+                                    + " has a Field annotation but is not public.");
+                        }
+                        final Field field = (Field) annotation;
+                        for (final String fieldName : field.names()) {
+                            if (fieldName != null && !"".equals(fieldName)) {
+                                System.out.println("  " + fieldName);
+                                fieldNames.add(fieldName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return fieldNames;
+    }
+
+    /**
+     * Get the list of table names for the class.
+     *
+     * @param type the class
+     * @return the list of table names
+     */
+    public static String[] getTableNames(final Class<? extends ConditionsObject> type) {
+        final Table tableAnnotation = type.getAnnotation(Table.class);
+        if (tableAnnotation != null) {
+            return tableAnnotation.names();
+        } else {
+            return new String[] {};
+        }
+    }
+
+    /**
+     * Do not allow class to be instantiated.
+     */
+    private ConditionsObjectUtilities() {
+    }
+}

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValues.java	Mon Apr 20 15:47:54 2015
@@ -20,6 +20,8 @@
 
     boolean isNonNull(String name);
 
+    boolean isNull(String name);
+
     void setValue(String name, Object value);
 
     int size();

Modified: 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	(original)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/FieldValuesMap.java	Mon Apr 20 15:47:54 2015
@@ -52,6 +52,11 @@
     }
 
     @Override
+    public boolean isNull(final String name) {
+        return this.data.get(name) == null;
+    }
+
+    @Override
     public void setValue(final String name, final Object value) {
         this.data.put(name, value);
     }

Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableRegistry.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableRegistry.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/apinew/TableRegistry.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,172 @@
+package org.hps.conditions.apinew;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.hps.conditions.database.Field;
+
+/**
+ * This is a registry providing a map between tables and their meta-data.
+ *
+ * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
+ */
+@SuppressWarnings("serial")
+public final class TableRegistry extends HashMap<String, TableMetaData> {
+
+    /**
+     * Maps collection types to table meta data.
+     */
+    static class CollectionTypeMap extends
+            HashMap<Class<? extends BaseConditionsObjectCollection<?>>, List<TableMetaData>> {
+
+        /**
+         * Add a mapping between a collection type and table meta data.
+         *
+         * @param type the collection type
+         * @param metaData the table meta data
+         */
+        void add(final Class<? extends BaseConditionsObjectCollection<?>> type, final TableMetaData metaData) {
+            if (this.get(type) == null) {
+                this.put(type, new ArrayList<TableMetaData>());
+            }
+            this.get(type).add(metaData);
+        }
+    }
+
+    /**
+     * Maps types to table meta data.
+     */
+    static class ObjectTypeMap extends HashMap<Class<? extends ConditionsObject>, List<TableMetaData>> {
+        /**
+         * Add a connection between an object type and table meta data.
+         *
+         * @param type the object type
+         * @param metaData the table meta data
+         */
+        void add(final Class<? extends ConditionsObject> type, final TableMetaData metaData) {
+            if (this.get(type) == null) {
+                this.put(type, new ArrayList<TableMetaData>());
+            }
+            this.get(type).add(metaData);
+        }
+    }
+
+    static TableRegistry instance = null;
+
+    /**
+     * Create a new table meta data registry.
+     *
+     * @return the meta data registry
+     */
+    static TableRegistry create() {
+        final TableRegistry registry = new TableRegistry();
+        for (final Class<? extends ConditionsObject> objectType : ConditionsObjectUtilities.findConditionsObjectTypes()) {
+
+            // Get the collection type.
+            final Class<? extends BaseConditionsObjectCollection<?>> collectionType = ConditionsObjectUtilities
+                    .getCollectionType(objectType);
+
+            // Get the list of field names.
+            final Set<String> fieldNames = ConditionsObjectUtilities.getFieldNames(objectType);
+
+            // Create map of fields to their types.
+            final Map<String, Class<?>> fieldTypes = new HashMap<String, Class<?>>();
+            for (final Method method : objectType.getMethods()) {
+                if (!method.getReturnType().equals(Void.TYPE)) {
+                    for (final Annotation annotation : method.getAnnotations()) {
+                        if (annotation.annotationType().equals(Field.class)) {
+                            final Field field = (Field) annotation;
+                            for (final String fieldName : field.names()) {
+                                fieldTypes.put(fieldName, method.getReturnType());
+                            }
+                        }
+                    }
+                }
+            }
+
+            for (final String name : ConditionsObjectUtilities.getTableNames(objectType)) {
+                // Create a meta data mapping for each table name in the class description.
+                final TableMetaData data = new TableMetaData(name, name, objectType, collectionType, fieldNames,
+                        fieldTypes);
+                registry.put(name, data);
+                registry.objectTypeMap.add(objectType, data);
+                registry.collectionTypeMap.add(collectionType, data);
+            }
+        }
+        return registry;
+    }
+
+    public synchronized static TableRegistry getTableRegistry() {
+        if (instance == null) {
+            instance = TableRegistry.create();
+        }
+        return instance;
+    }
+
+    /**
+     * Map between collection types and meta data.
+     */
+    private final CollectionTypeMap collectionTypeMap = new CollectionTypeMap();
+
+    /**
+     * Map between object types and meta data.
+     */
+    private final ObjectTypeMap objectTypeMap = new ObjectTypeMap();
+
+    /**
+     * Class should not be directly instantiated.
+     * <p>
+     * Use the {@link #create()} method instead.
+     */
+    private TableRegistry() {
+    }
+
+    /**
+     * Find meta data by collection type.
+     *
+     * @param collectionType the collection type
+     * @return the meta data or <code>null</code> if none exists.
+     */
+    List<TableMetaData> findByCollectionType(final Class<?> collectionType) {
+        return this.collectionTypeMap.get(collectionType);
+    }
+
+    /**
+     * Find meta data by object type.
+     *
+     * @param objectType the object type
+     * @return the meta data or <code>null</code> if none exists.
+     */
+    List<TableMetaData> findByObjectType(final Class<? extends ConditionsObject> objectType) {
+        return this.objectTypeMap.get(objectType);
+    }
+
+    /**
+     * Find meta data by table name.
+     *
+     * @param name the table name
+     * @return the meta data or <code>null</code> if none exists
+     */
+    TableMetaData findByTableName(final String name) {
+        return this.get(name);
+    }
+
+    /**
+     * Convert this object to a string.
+     *
+     * @return this object converted to a string
+     */
+    @Override
+    public String toString() {
+        final StringBuffer buff = new StringBuffer();
+        for (final TableMetaData tableMetaData : this.values()) {
+            buff.append(tableMetaData.toString());
+        }
+        return buff.toString();
+    }
+}

Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObject.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObject.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObject.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,40 @@
+package org.hps.conditions.dummy;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.hps.conditions.api.ConditionsObjectException;
+import org.hps.conditions.apinew.BaseConditionsObject;
+import org.hps.conditions.apinew.BaseConditionsObjectCollection;
+import org.hps.conditions.apinew.TableMetaData;
+import org.hps.conditions.database.Field;
+import org.hps.conditions.database.Table;
+
+/**
+ * A dummy conditions object type.
+ */
+@Table(names = {"dummy"})
+public final class DummyConditionsObject extends BaseConditionsObject {
+
+    public static class DummyConditionsObjectCollection extends BaseConditionsObjectCollection<DummyConditionsObject> {
+        public DummyConditionsObjectCollection() {
+        }
+
+        DummyConditionsObjectCollection(final Connection connection, final TableMetaData tableMetaData)
+                throws SQLException, ConditionsObjectException {
+            super(connection, tableMetaData, -1);
+        }
+    }
+
+    public DummyConditionsObject() {
+    }
+
+    public DummyConditionsObject(final Connection connection, final TableMetaData tableMetaData) {
+        super(connection, tableMetaData);
+    }
+
+    @Field(names = {"dummy"})
+    public Double getDummy() {
+        return this.getValue(Double.class, "dummy");
+    }
+}

Added: java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObjectConverter.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObjectConverter.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/java/org/hps/conditions/dummy/DummyConditionsObjectConverter.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,12 @@
+package org.hps.conditions.dummy;
+
+import org.hps.conditions.apinew.AbstractConditionsObjectConverter;
+import org.hps.conditions.dummy.DummyConditionsObject.DummyConditionsObjectCollection;
+
+public final class DummyConditionsObjectConverter extends
+        AbstractConditionsObjectConverter<DummyConditionsObjectCollection> {
+    @Override
+    public Class<DummyConditionsObjectCollection> getType() {
+        return DummyConditionsObjectCollection.class;
+    }
+}

Added: java/branches/conditions-HPSJAVA-488/src/main/resources/org/hps/conditions/config/jeremym_dev_connection.prop
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/main/resources/org/hps/conditions/config/jeremym_dev_connection.prop	(added)
+++ java/branches/conditions-HPSJAVA-488/src/main/resources/org/hps/conditions/config/jeremym_dev_connection.prop	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,4 @@
+hostname = localhost
+user = root
+password = derp
+database = hps_conditions_dev

Added: java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/DummyConditionsObjectConverterTest.java
 =============================================================================
--- java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/DummyConditionsObjectConverterTest.java	(added)
+++ java/branches/conditions-HPSJAVA-488/src/test/java/org/hps/conditions/apinew/DummyConditionsObjectConverterTest.java	Mon Apr 20 15:47:54 2015
@@ -0,0 +1,47 @@
+package org.hps.conditions.apinew;
+
+import junit.framework.TestCase;
+
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.hps.conditions.dummy.DummyConditionsObject;
+import org.hps.conditions.dummy.DummyConditionsObject.DummyConditionsObjectCollection;
+import org.hps.conditions.dummy.DummyConditionsObjectConverter;
+
+/**
+ * @author <a href="mailto:[log in to unmask]">Jeremy McCormick</a>
+ */
+public class DummyConditionsObjectConverterTest extends TestCase {
+
+    public void testConditionsObjectConverter() throws Exception {
+
+        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");
+        manager.registerConditionsConverter(new DummyConditionsObjectConverter());
+        manager.setDetector("HPS-dummy-detector", 1);
+        manager.openConnection();
+
+        final TableMetaData tableMetaData = TableRegistry.getTableRegistry().findByTableName("dummy");
+
+        final DummyConditionsObjectCollection newCollection = new DummyConditionsObjectCollection();
+        newCollection.setTableMetaData(tableMetaData);
+        newCollection.setConnection(manager.getConnection());
+
+        final DummyConditionsObject object = new DummyConditionsObject(manager.getConnection(), tableMetaData);
+        object.setValue("dummy", 1.2345);
+        newCollection.add(object);
+
+        try {
+            newCollection.insertAll(1002);
+
+            final DummyConditionsObjectCollection readCollection = manager.getCachedConditions(
+                    DummyConditionsObjectCollection.class, "dummy").getCachedData();
+
+            System.out.println("got dummy collection " + readCollection.getCollectionId() + " with "
+                    + readCollection.size() + " objects");
+        } finally {
+            System.out.println("deleting collection " + newCollection.getCollectionId());
+            newCollection.deleteAll();
+        }
+    }
+}