com.draagon.meta.manager.db.ObjectManagerDB.java Source code

Java tutorial

Introduction

Here is the source code for com.draagon.meta.manager.db.ObjectManagerDB.java

Source

/*
 * Copyright 2003 Draagon Software LLC. All Rights Reserved.
 *
 * This software is the proprietary information of Draagon Software LLC.
 * Use is subject to license terms.
 */
package com.draagon.meta.manager.db;

import com.draagon.meta.field.MetaField;
import com.draagon.meta.field.MetaFieldNotFoundException;
import com.draagon.meta.manager.StateAwareMetaObject;
import com.draagon.meta.object.MetaObject;
import com.draagon.meta.*;
import com.draagon.meta.object.value.ValueMetaObject;
import com.draagon.meta.manager.*;
import com.draagon.meta.manager.db.driver.*;
//import com.draagon.util.InitializationException;
import com.draagon.cache.Cache;
import com.draagon.meta.manager.exp.Expression;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import javax.sql.DataSource;

/**
 * The Object Manager Base is able to add, update, delete, and retrieve objects
 * of those types from a datastore.
 */
public class ObjectManagerDB extends ObjectManager {

    private static Log log = LogFactory.getLog(ObjectManagerDB.class);
    private final static String CREATE_MAP_ATTR = "dbCreateMap";
    private final static String READ_MAP_ATTR = "dbReadMap";
    private final static String UPDATE_MAP_ATTR = "dbUpdateMap";
    private final static String DELETE_MAP_ATTR = "dbDeleteMap";
    private final static String HAS_CREATE_MAP_ATTR = "hasDbCreateMap";
    private final static String HAS_READ_MAP_ATTR = "hasDbReadMap";
    private final static String HAS_UPDATE_MAP_ATTR = "hasDbUpdateMap";
    private final static String HAS_DELETE_MAP_ATTR = "hasDbDeleteMap";
    public final static String ALLOW_DIRTY_WRITE = "dbAllowDirtyWrite";
    public final static String DIRTY_WRITE_CHECK_FIELD = "dbDirtyWriteCheckField";
    public final static String POPULATE_FILE = "dbPopulateFile";
    private MappingHandler mMappingHandler = null;
    private DatabaseDriver mDriver = null;
    private DataSource mSource = null;
    private boolean enforceTransaction = false;
    private static Cache<String, MetaObject> templateCache = new Cache<String, MetaObject>(true, 3000, 1500);

    //private ArrayList mValidatedClasses = new ArrayList();
    //private boolean autoCreateTables = false;
    //private HashMap mDirtyFieldCache = new HashMap();
    public ObjectManagerDB() {
    }

    /**
     * Handles enforcing transactions on SQL queries
     */
    public void setEnforceTransaction(boolean enforce) {
        enforceTransaction = enforce;
    }

    /**
     * Returns whether to enforce transactions
     */
    public boolean enforceTransaction() {
        return enforceTransaction;
    }

    /**
     * Checks to see if a transaction exists or not
     */
    protected void checkTransaction(Connection c, boolean throwEx) throws MetaException {
        try {
            if (enforceTransaction() && c.getAutoCommit()) {
                MetaException me = new MetaException(
                        "The connection retrieved is not operating under a transaction and transactions are being enforced");
                if (throwEx) {
                    throw me;
                } else {
                    log.warn(me.getMessage(), me);
                }
            }
        } catch (SQLException e) {
            throw new MetaException("Error checking connection for transaction enforcement: " + e.getMessage(), e);
        }
    }

    ///////////////////////////////////////////////////////
    // CONNECTION HANDLING METHODS
    //
    /**
     * Retrieves a connection object representing the datastore
     */
    public ObjectConnection getConnection() {
        DataSource ds = getDataSource();
        if (ds == null) {
            throw new IllegalArgumentException(
                    "No DataSource was specified for this ObjectManager, cannot request connection");
        }

        Connection c;
        try {
            c = ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("Could not retrieve a connection from the datasource: " + e.getMessage(), e);
        }

        return new ObjectConnectionDB(c);
    }

    /**
     * Release the Database Connection
     */
    public void releaseConnection(ObjectConnection oc) throws MetaException {
        oc.close();
    }

    /**
     * Sets the Data Source to use for database connections
     */
    public void setDataSource(DataSource ds) {
        mSource = ds;
    }

    /**
     * Retrieves the data source
     */
    public DataSource getDataSource() {
        return mSource;
    }

    /**
     * Initializes the ObjectManager
     */
    public void init() throws Exception {
        super.init();

        if (getDataSource() == null) {
            throw new IllegalStateException("No DataSource was specified");
        }
    }

    ///////////////////////////////////////////////////////
    // DATABASE DRIVER METHODS
    //
    public void setDriverClass(String className)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> c = Class.forName(className);
        setDatabaseDriver((DatabaseDriver) c.newInstance());
    }

    public void setDatabaseDriver(DatabaseDriver dd) {
        mDriver = dd;
        mDriver.setManager(this);
    }

    public synchronized DatabaseDriver getDatabaseDriver() {
        if (mDriver == null) {
            mDriver = new GenericSQLDriver();
            mDriver.setManager(this);
        }

        return mDriver;
    }

    ///////////////////////////////////////////////////////
    // PERSISTENCE METHODS
    //
    public MappingHandler getDefaultMappingHandler() {
        return new SimpleMappingHandlerDB();
    }

    public void setMappingHandler(MappingHandler handler) {
        mMappingHandler = handler;
    }

    public MappingHandler getMappingHandler() {
        if (mMappingHandler == null) {
            mMappingHandler = getDefaultMappingHandler();
        }
        return mMappingHandler;
    }

    /**
     * Gets the create mapping
     */
    protected ObjectMapping getCreateMapping(MetaObject mc) {
        ObjectMapping mapping = (ObjectMapping) mc.getCacheValue(CREATE_MAP_ATTR);
        if (mapping == null) {
            mapping = getMappingHandler().getCreateMapping(mc);
            if (mapping != null) {
                mc.setCacheValue(CREATE_MAP_ATTR, mapping);
            }
        }
        return mapping;
    }

    /**
     * Gets the read mapping
     */
    protected ObjectMapping getReadMapping(MetaObject mc) {
        ObjectMapping mapping = (ObjectMapping) mc.getCacheValue(READ_MAP_ATTR);
        if (mapping == null) {
            mapping = getMappingHandler().getReadMapping(mc);
            if (mapping != null) {
                mc.setCacheValue(READ_MAP_ATTR, mapping);
                mc.setCacheValue(HAS_READ_MAP_ATTR, Boolean.TRUE);
            } else {
                mc.setCacheValue(HAS_READ_MAP_ATTR, Boolean.FALSE);
            }
        }
        return mapping;
    }

    /**
     * Gets the update mapping
     */
    protected ObjectMapping getUpdateMapping(MetaObject mc) {
        ObjectMapping mapping = (ObjectMapping) mc.getCacheValue(UPDATE_MAP_ATTR);
        if (mapping == null) {
            mapping = getMappingHandler().getUpdateMapping(mc);
            if (mapping != null) {
                mc.setCacheValue(UPDATE_MAP_ATTR, mapping);
                mc.setCacheValue(HAS_UPDATE_MAP_ATTR, Boolean.TRUE);
            } else {
                mc.setCacheValue(HAS_UPDATE_MAP_ATTR, Boolean.FALSE);
            }
        }
        return mapping;
    }

    /**
     * Gets the delete mapping
     */
    protected ObjectMapping getDeleteMapping(MetaObject mc) {
        ObjectMapping mapping = (ObjectMapping) mc.getCacheValue(DELETE_MAP_ATTR);
        if (mapping == null) {
            mapping = getMappingHandler().getUpdateMapping(mc);
            if (mapping != null) {
                mc.setCacheValue(DELETE_MAP_ATTR, mapping);
                mc.setCacheValue(HAS_DELETE_MAP_ATTR, Boolean.TRUE);
            } else {
                mc.setCacheValue(HAS_DELETE_MAP_ATTR, Boolean.FALSE);
            }
        }
        return mapping;
    }

    /**
     * Is this a createable class
     */
    public boolean isCreateableClass(MetaObject mc) {
        Boolean hasMapping = (Boolean) mc.getCacheValue(HAS_CREATE_MAP_ATTR);
        if (hasMapping == null) {
            if (getCreateMapping(mc) == null) {
                return false;
            } else {
                return true;
            }
        }
        return hasMapping.booleanValue();
    }

    /**
     * Is this a readable class
     */
    public boolean isReadableClass(MetaObject mc) {
        Boolean hasMapping = (Boolean) mc.getCacheValue(HAS_READ_MAP_ATTR);
        if (hasMapping == null) {
            if (getReadMapping(mc) == null) {
                return false;
            } else {
                return true;
            }
        }
        return hasMapping.booleanValue();
    }

    /**
     * Gets the update mapping to the DB
     */
    public boolean isUpdateableClass(MetaObject mc) {
        Boolean hasMapping = (Boolean) mc.getCacheValue(HAS_UPDATE_MAP_ATTR);
        if (hasMapping == null) {
            if (getUpdateMapping(mc) == null) {
                return false;
            } else {
                return true;
            }
        }
        return hasMapping.booleanValue();
    }

    /**
     * Gets the delete mapping to the DB
     */
    public boolean isDeleteableClass(MetaObject mc) {
        Boolean hasMapping = (Boolean) mc.getCacheValue(HAS_DELETE_MAP_ATTR);
        if (hasMapping == null) {
            if (getDeleteMapping(mc) == null) {
                return false;
            } else {
                return true;
            }
        }
        return hasMapping.booleanValue();
    }

    /**
     * Breaks apart the values from the id field represented by the keys
     */
    /*protected Collection getKeyValuesFromRef( MetaClass mc, Collection keys, String ref )
     throws MetaException
     {
     ArrayList values = new ArrayList();
        
     String tmp = ref;
        
     // Split apart the id field
     for ( Iterator i = keys.iterator(); i.hasNext(); )
     {
     MetaField f = (MetaField) i.next();
        
     if ( tmp == null || tmp.length() == 0 )
     throw new MetaException( "Invalid Reference [" + ref + "] for MetaClass [" + mc + "]" );
        
     String val = null;
     int j = tmp.indexOf( '-' );
     if ( j >= 0 )
     {
     val = tmp.substring( 0, j );
     tmp = tmp.substring( j + 1 );
     }
     else
     {
     val = tmp;
     tmp = null;
     }
        
     values.add( val );
     }
        
     return values;
     }*/
    /**
     * Sets the prepared statement values for the keys of a class and a specifed
     * id.
     */

    /*    protected void setStatementValuesForRef( PreparedStatement s, Collection keys, int start, ObjectRef ref )
     throws MetaException, SQLException
     {
     //Collection values = getKeyValuesFromRef( keys, ref );
     String [] ids = ref.getIds();
        
     int k = 0;
     int j = start;
     //Iterator v = values.iterator();
     for ( Iterator i = keys.iterator(); i.hasNext(); j++, k++ )
     {
     MetaField f = (MetaField) i.next();
     String value = ids[ k ];
        
     setStatementValue( s, f, j, value );
     }
     }*/
    /**
     * Parses an Object returned from the database
     */
    public void parseObject(ResultSet rs, Collection<MetaField> fields, MetaObject mc, Object o)
            throws SQLException, MetaException {
        if (!isReadableClass(mc)) {
            throw new MetaException("MetaClass [" + mc + "] is not readable");
        }

        int j = 1;
        for (MetaField f : fields) {
            parseField(o, f, rs, j++);
        }

        if (mc instanceof StateAwareMetaObject) {
            // It was pulled from the database, so it doesn't need to be flagged as modified
            ((StateAwareMetaObject) mc).setModified(o, false);

            // It is also no longer a new item
            ((StateAwareMetaObject) mc).setNew(o, false);
        }
    }

    protected void parseField(Object o, MetaField f, ResultSet rs, int j) throws SQLException {
        switch (f.getType()) {
        case MetaField.BOOLEAN: {
            boolean bv = rs.getBoolean(j);
            if (rs.wasNull()) {
                f.setBoolean(o, null);
            } else {
                f.setBoolean(o, new Boolean(bv));
            }
        }
            break;

        case MetaField.BYTE: {
            byte bv = rs.getByte(j);
            if (rs.wasNull()) {
                f.setByte(o, null);
            } else {
                f.setByte(o, new Byte(bv));
            }
        }
            break;

        case MetaField.SHORT: {
            short sv = rs.getShort(j);
            if (rs.wasNull()) {
                f.setShort(o, null);
            } else {
                f.setShort(o, new Short(sv));
            }
        }
            break;

        case MetaField.INT: {
            int iv = rs.getInt(j);
            if (rs.wasNull()) {
                f.setInt(o, null);
            } else {
                f.setInt(o, new Integer(iv));
            }
        }
            break;

        case MetaField.DATE: {
            Timestamp tv = rs.getTimestamp(j);
            if (rs.wasNull()) {
                f.setDate(o, null);
            } else {
                f.setDate(o, new java.util.Date(tv.getTime()));
            }
        }
            break;

        case MetaField.LONG: {
            long lv = rs.getLong(j);
            if (rs.wasNull()) {
                f.setLong(o, null);
            } else {
                f.setLong(o, new Long(lv));
            }
        }
            break;

        case MetaField.FLOAT: {
            float fv = rs.getFloat(j);
            if (rs.wasNull()) {
                f.setFloat(o, null);
            } else {
                f.setFloat(o, new Float(fv));
            }
        }
            break;

        case MetaField.DOUBLE: {
            double dv = rs.getDouble(j);
            if (rs.wasNull()) {
                f.setDouble(o, null);
            } else {
                f.setDouble(o, new Double(dv));
            }
        }
            break;

        case MetaField.STRING:
            f.setString(o, rs.getString(j));
            break;

        case MetaField.OBJECT:
            f.setObject(o, rs.getObject(j));
            break;
        }
    }

    /**
     * Reset the objects to not be new or modified
     */
    protected void resetObjects(MetaObject mc, Collection<Object> objects) {

        if (!(mc instanceof StateAwareMetaObject)) {
            return;
        }

        // Reset all the objects
        for (Object o : objects) {
            resetObject(mc, o);
        }
    }

    /**
     * Reset the object to not be new or modified
     */
    protected void resetObject(MetaObject mc, Object o) {

        if (mc instanceof StateAwareMetaObject) {

            // It was pulled from the database, so it doesn't need to be flagged as modified
            ((StateAwareMetaObject) mc).setModified(o, false);

            // It is also no longer a new item
            ((StateAwareMetaObject) mc).setNew(o, false);
        }
    }

    /**
     * Gets the object by the id; throws exception if it did not exist
     */
    @Override
    public Object getObjectByRef(ObjectConnection c, String refStr) {
        ObjectRef ref = getObjectRef(refStr);
        MetaObject mc = ref.getMetaClass();

        if (!isReadableClass(mc)) {
            throw new PersistenceException("MetaClass [" + mc + "] is not readable");
        }

        ObjectMappingDB readMap = (ObjectMappingDB) getReadMapping(mc);

        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, false);

        if (log.isDebugEnabled()) {
            log.debug("Loading object [" + mc + "] with reference [" + ref + "]");
        }

        try {

            // Create the Expression for the Primary Keys
            Expression exp = null;
            int i = 0;
            for (MetaField mf : getPrimaryKeys(mc)) {
                Expression e = new Expression(mf.getName(), ref.getIds()[i]);
                if (exp == null) {
                    exp = e;
                } else {
                    exp = exp.and(e);
                }
                i++;
            }

            if (exp == null) {
                throw new PersistenceException(
                        "MetaClass [" + mc + "] has no primary keys get object by reference [" + ref + "]");
            }

            // Create the QueryOptions and limit to the first 1
            QueryOptions qo = new QueryOptions();
            qo.setRange(1, 1);

            // Read the objects from the database driver
            Collection<Object> objects = getDatabaseDriver().readMany(conn, mc, readMap, qo);

            // Reset the object persistence states
            resetObjects(mc, objects);

            // Return the object if found
            if (objects.size() > 0) {
                return objects.iterator().next();
            } else {
                throw new ObjectNotFoundException(refStr);
            }
        } catch (SQLException e) {
            //log.error( "Unable to load object [" + mc + "] with reference [" + ref + "]: " + e.getMessage(), e );
            throw new PersistenceException(
                    "Unable to load object [" + mc + "] with reference [" + ref + "]: " + e.getMessage(), e);
        }
    }

    /**
     * Delete the objects from the datastore where the field has the specified
     * value
     */
    @Override
    public int deleteObjects(ObjectConnection c, MetaObject mc, Expression exp) {

        if (!isDeleteableClass(mc)) {
            throw new PersistenceException("MetaClass [" + mc + "] is not deletable");
        }

        ObjectMappingDB mapping = (ObjectMappingDB) getDeleteMapping(mc);

        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, true);

        if (log.isDebugEnabled()) {
            log.debug("Deleting Objects of Class [" + mc + "] where [" + exp + "]");
        }

        //int failures = 0;
        //while( true )
        //{
        try {
            return getDatabaseDriver().deleteMany(conn, mc, mapping, exp);
        } catch (SQLException e) {
            //log.error( "Unable to delete objects of class [" + mc.getName() + "] with expression [" + exp + "]: " + e.getMessage() );
            //if ( ++failures > 5 )
            throw new PersistenceException("Unable to delete objects of class [" + mc.getName()
                    + "] with expression [" + exp + "]: " + e.getMessage(), e);
        }

        // Sleep on a delete failure
        //try { Thread.sleep( 200 * failures ); }
        //catch (InterruptedException e) {}
        //}
    }

    /**
     * Gets the total count of objects with the specified search criteria
     */
    @Override
    public long getObjectsCount(ObjectConnection c, MetaObject mc, Expression exp) throws MetaException {
        if (!isReadableClass(mc)) {
            throw new PersistenceException("MetaClass [" + mc + "] is not persistable");
        }

        Connection conn = (Connection) c.getDatastoreConnection();

        ObjectMappingDB mapping = (ObjectMappingDB) getReadMapping(mc);

        // Check for a valid transaction if enforced
        checkTransaction(conn, false);

        try {
            // Read the objects
            return getDatabaseDriver().getCount(conn, mc, mapping, exp);
        } catch (SQLException e) {
            throw new PersistenceException("Unable to get objects count of class [" + mc.getName()
                    + "] with expression [" + exp + "]: " + e.getMessage(), e);
        }
    }

    /**
     * Gets the objects by the field with the specified search criteria
     */
    @Override
    public Collection<?> getObjects(ObjectConnection c, MetaObject mc, QueryOptions options) throws MetaException {
        if (!isReadableClass(mc)) {
            throw new PersistenceException("MetaClass [" + mc + "] is not persistable");
        }

        Connection conn = (Connection) c.getDatastoreConnection();

        ObjectMappingDB mapping = (ObjectMappingDB) getReadMapping(mc);

        // Check for a valid transaction if enforced
        checkTransaction(conn, false);

        //int failures = 0;
        //while( true )
        //{
        try {
            // Read the objects
            Collection<Object> objects = getDatabaseDriver().readMany(conn, mc, mapping, options);

            // Reset the object persistence states
            resetObjects(mc, objects);

            // Return the objects
            return objects;
        } catch (SQLException e) {
            //log.error( "Unable to load objects of class [" + mc.getName() + "]: " + e.getMessage() );

            //if ( ++failures > 5 )
            throw new PersistenceException("Unable to get objects of class [" + mc.getName() + "] with options ["
                    + options + "]: " + e.getMessage(), e);
        }

        // Sleep on a read failure
        //try { Thread.sleep( 200 * failures ); }
        //catch (InterruptedException e) {}
        //}
    }

    /**
     * Load the specified object from the database
     */
    public void loadObject(ObjectConnection c, Object o) throws MetaException {
        // Verify this object was loaded by the same object manager
        //verifyObjectManager( o );

        // Get the MetaClass for the object
        MetaObject mc = getMetaObjectFor(o);

        // If it's not a readable class throw an exception
        if (!isReadableClass(mc)) {
            throw new PersistenceException("MetaClass [" + mc + "] is not persistable");
        }

        // Get the read mapping
        ObjectMappingDB mapping = (ObjectMappingDB) getReadMapping(mc);

        // Get the connection
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, false);

        if (log.isDebugEnabled()) {
            log.debug("Loading object [" + o + "] of class [" + mc + "]");
        }

        // Create the Expression for the Primary Keys
        Expression exp = null;
        for (MetaField mf : getPrimaryKeys(mc)) {
            Expression e = new Expression(mf.getName(), mf.getObject(o));
            if (exp == null) {
                exp = e;
            } else {
                exp = exp.and(e);
            }
        }

        if (exp == null) {
            throw new PersistenceException(
                    "MetaClass [" + mc + "] has no primary keys defined to load object [" + o + "]");
        }

        // Try to read the object
        try {
            // Read the object from the mapping
            boolean found = getDatabaseDriver().read(conn, mc, mapping, o, exp);

            // If not found throw an exception
            if (!found) {
                throw new ObjectNotFoundException(o);
            }

            // Reset the object after it's loaded
            resetObject(mc, o);
        } catch (SQLException e) {
            //log.error( "Unable to load object [" + o + "]: " + e.getMessage(), e );
            throw new PersistenceException("Unable to load object [" + o + "]: " + e.getMessage(), e);
        }
    }

    /**
     * Add the specified object to the datastore
     */
    @Override
    public void createObject(ObjectConnection c, Object obj) throws PersistenceException {
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, true);

        MetaObject mc = getMetaObjectFor(obj);

        if (!isCreateableClass(mc)) {
            throw new PersistenceException("Object of class [" + mc + "] is not createable");
        }

        // Get the create mapping
        ObjectMappingDB mapping = (ObjectMappingDB) getCreateMapping(mc);

        //verifyObjectManager( obj );

        prePersistence(c, mc, obj, CREATE);

        if (log.isDebugEnabled()) {
            log.debug("Adding object [" + obj + "] of class [" + mc + "]");
        }

        try {
            if (!getDatabaseDriver().create(conn, mc, mapping, obj)) {
                throw new PersistenceException("Now rows created for object [" + obj + "] of class [" + mc + "]");
            }

            postPersistence(c, mc, obj, CREATE);
        } catch (SQLException e) {
            //log.error( "Unable to add object of class [" + mc + "]: " + e.getMessage() );
            throw new PersistenceException("Unable to add object of class [" + mc + "]:" + e.getMessage(), e);
        }
    }

    /**
     * Update the specified object in the datastore
     */
    public void updateObject(ObjectConnection c, Object obj) throws PersistenceException {
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, true);

        // Get the metaclass and make sure it is updateable
        MetaObject mc = getMetaObjectFor(obj);
        if (!isUpdateableClass(mc)) {
            throw new PersistenceException("Object of class [" + mc + "] is not writeable");
        }

        // check the object manager
        //verifyObjectManager( obj );

        // Get the update mapping
        ObjectMappingDB mapping = (ObjectMappingDB) getUpdateMapping(mc);

        // Check whether there are dirty writes
        boolean allowsDirtyWrites = allowsDirtyWrites(mc);

        MetaField dirtyField = null;
        Object dirtyFieldValue = null;

        // If we don't allow dirty writes then get the field we're filtering from
        if (!allowsDirtyWrites) {
            dirtyField = getDirtyField(mc);
            dirtyFieldValue = dirtyField.getObject(obj);
        }

        if (log.isDebugEnabled()) {
            log.debug("Updating object [" + obj + "] of class [" + mc + "]");
        }

        // Run the pre-peristence methods
        prePersistence(c, mc, obj, UPDATE);

        // Get the modified fields
        Collection<MetaField> fields = mc.getMetaFields(); //mapping.getMetaFields();
        if (mc instanceof StateAwareMetaObject) {
            fields = getModifiedPersistableFields((StateAwareMetaObject) mc, fields, obj);
        }

        // If nothing needs to be persisted, then don't bother
        if (fields.size() == 0) {
            if (log.isDebugEnabled()) {
                log.debug("No need to update object of class [" + mc + "]");
            }
            return;
        }

        try {
            // Update the object
            if (!getDatabaseDriver().update(conn, mc, mapping, obj, fields, getPrimaryKeys(mc), dirtyField,
                    dirtyFieldValue)) {

                // If no dirty writes, see if that was the issue
                if (!allowsDirtyWrites) {

                    mapping = (ObjectMappingDB) getReadMapping(mc);

                    // Create the Expression for the Primary Keys
                    Expression exp = null;
                    for (MetaField mf : getPrimaryKeys(mc)) {
                        Expression e = new Expression(mf.getName(), mf.getObject(obj));
                        if (exp == null) {
                            exp = e;
                        } else {
                            exp = exp.and(e);
                        }
                    }

                    if (exp == null) {
                        throw new PersistenceException("MetaClass [" + mc
                                + "] has no primary keys defined to update object [" + obj + "]");
                    }

                    Collection<Object> results = getDatabaseDriver().readMany(conn, mc, mapping,
                            new QueryOptions(exp));
                    if (results.size() > 0) {
                        throw new DirtyWriteException(obj);
                    }
                }

                throw new ObjectNotFoundException(obj);
            }

            postPersistence(c, mc, obj, UPDATE);
        } catch (SQLException e) {
            //log.error( "Unable to update object of class [" + mc + "]: " + e.getMessage() );
            throw new PersistenceException(
                    "Unable to update object [" + obj + "] of class [" + mc + "]: " + e.getMessage(), e);
        }
    }

    /**
     * Delete the specified object from the datastore
     */
    public void deleteObject(ObjectConnection c, Object obj) throws PersistenceException {
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, true);

        MetaObject mc = getMetaObjectFor(obj);

        if (!isDeleteableClass(mc)) {
            throw new PersistenceException("Object [" + obj + "] of class [" + mc + "] is not deleteable");
        }

        //verifyObjectManager( obj );

        // Get the update mapping
        ObjectMappingDB mapping = (ObjectMappingDB) getDeleteMapping(mc);

        // Check whether there are dirty writes
        boolean allowsDirtyWrites = allowsDirtyWrites(mc);

        //MetaField dirtyField = null;
        //Object dirtyFieldValue = null;

        // If we don't allow dirty writes then get the field we're filtering from
        //if ( !allowsDirtyWrites ) {
        //dirtyField = getDirtyField( mc );
        //dirtyFieldValue = dirtyField.getObject( obj );
        //}

        prePersistence(c, mc, obj, DELETE);

        if (log.isDebugEnabled()) {
            log.debug("Deleting object [" + obj + "] of class [" + mc + "]");
        }

        try {
            boolean success = getDatabaseDriver().delete(conn, mc, mapping, obj, getPrimaryKeys(mc));

            if (!success) {

                // If no dirty writes, see if that was the issue
                if (!allowsDirtyWrites) {

                    // Create the Expression for the Primary Keys
                    Expression exp = null;
                    for (MetaField mf : getPrimaryKeys(mc)) {
                        Expression e = new Expression(mf.getName(), mf.getObject(obj));
                        if (exp == null) {
                            exp = e;
                        } else {
                            exp = exp.and(e);
                        }
                    }

                    if (exp == null) {
                        throw new PersistenceException("MetaClass [" + mc
                                + "] has no primary keys defined to delete object [" + obj + "]");
                    }

                    Collection<Object> results = getDatabaseDriver().readMany(conn, mc, mapping,
                            new QueryOptions(exp));
                    if (results.size() > 0) {
                        throw new DirtyWriteException(obj);
                    }
                }

                throw new ObjectNotFoundException(obj);
            }

            postPersistence(c, mc, obj, DELETE);
        } catch (SQLException e) {
            //log.error( "Unable to delete object of class [" + mc + "]: " + e.getMessage() );
            throw new PersistenceException(
                    "Unable to delete object [" + obj + "] of class [" + mc + "]: " + e.getMessage(), e);
        }
    }

    /*public boolean isAutoCreateTables() {
     return autoCreateTables;
     }
        
     public void setAutoCreateTables(boolean autoCreateTables) {
     this.autoCreateTables = autoCreateTables;
     }*/
    ///////////////////////////////////////////////////////
    // OBJECT QUERY LANGUAGE METHODS
    //
    //private final static String ROOT_CLASS_KEY = "*ROOT_CLASS_KEY*";

    /*protected Map<String,MetaClass> getMetaClassMap( String query ) throws MetaException
     {
     Map<String,MetaClass> m = new HashMap<String,MetaClass>();
        
     int i = -1;
        
     while( ( i = query.indexOf( '[', i + 1 )) > 0 )
     {
     int j = query.indexOf( ']', i );
     int k = query.indexOf( '=', i );
     if ( j <= 0 )
     throw new IllegalArgumentException( "Malformed OQL, missing closing '}': [" + query + "]" );
        
     if ( k >= 0 && k < j )
     {
     String cn = query.substring( i + 1, k );
     String var = query.substring( k + 1, j );
     m.put( var, MetaClass.forName( cn ));
     }
     else
     {
     String cn = query.substring( i + 1, j );
     m.put( ROOT_CLASS_KEY, MetaClass.forName( cn ));
     }
        
     i = j;
     }
        
     //ystem.out.println( "MAP: " + m );
        
     return m;
     }*/

    /* private String convertToSQL( String query, Map<String,MetaClass> m ) throws MetaException
     {
     //StringBuffer b = new StringBuffer();
     //ystem.out.println( "IN: " + query );
        
     int i = -1;
        
     // Replace tables
     while( ( i = query.indexOf( '{', i + 1 )) > 0 )
     {
     int j = query.indexOf( '}', i );
     if ( j <= 0 )
     throw new IllegalArgumentException( "Malformed OQL, missing closing '}': [" + query + "]" );
        
     String field = null;
     String var = null;
     MetaClass mc = null;
        
     int k = query.indexOf( '.', i );
     if ( k >= 0 && k < j )
     {
     var = query.substring( i + 1, k );
     field = query.substring( k + 1, j );
        
     mc = (MetaClass) m.get( var );
        
     if ( mc == null )
     throw new IllegalArgumentException( "Malformed OQL, unmapped metaclass variable '" + var + "': [" + query + "]" );
        
     var += ".";
     }
     else
     {
     field = query.substring( i + 1, j );
     mc = (MetaClass) m.get( ROOT_CLASS_KEY );
     var = "";
        
     if ( mc == null )
     throw new IllegalArgumentException( "Malformed OQL, no default metaclass defined: [" + query + "]" );
     }
        
     String colName = null;
     if ( field.equals( "*" ))
     colName = "*";
     else
     colName = getColumnName( mc.getMetaField( field ));
        
     query = query.substring( 0, i ) +
     var + colName +
     query.substring( j + 1 );
     }
        
     // Replace fields
     i = -1;
     while( ( i = query.indexOf( '[', i + 1 )) > 0 )
     {
     int j = query.indexOf( ']', i );
     if ( j <= 0 )
     throw new IllegalArgumentException( "Malformed OQL, missing closing '}': [" + query + "]" );
        
     String var = null;
     MetaClass mc = null;
        
     int k = query.indexOf( '=', i );
     if ( k >= 0 && k < j )
     {
     var = query.substring( k + 1, j );
        
     mc = (MetaClass) m.get( var );
        
     if ( mc == null )
     throw new IllegalArgumentException( "Malformed OQL, unmapped metaclass variable '" + var + "': [" + query + "]" );
        
     var = " " + var;
     }
     else
     {
     mc = (MetaClass) m.get( ROOT_CLASS_KEY );
        
     if ( mc == null )
     throw new IllegalArgumentException( "Malformed OQL, no default metaclass defined: [" + query + "]" );
        
     var = "";
     }
        
     query = query.substring( 0, i ) +
     getViewName( mc ) + var +
     query.substring( j + 1 );
     }
        
     //ystem.out.println( "OUT: " + query );
        
     //return b.toString();
     return query;
     }*/
    /**
     * Returns whether a MetaClass allows dirty writes
     */
    public boolean allowsDirtyWrites(MetaObject mc) {
        if (!mc.hasAttribute(ALLOW_DIRTY_WRITE) || !("false".equals(mc.getAttribute(ALLOW_DIRTY_WRITE)))) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Gets the name of the dirty field of a metaclass
     */
    protected MetaField getDirtyField(MetaObject mc) {
        final String KEY = "getDirtyField()";

        MetaField field = (MetaField) mc.getCacheValue(KEY);

        if (field == null) {
            if (mc.hasAttribute(DIRTY_WRITE_CHECK_FIELD)) {
                field = mc.getMetaField(mc.getAttribute(DIRTY_WRITE_CHECK_FIELD).toString());
            } else {
                for (MetaField f : getAutoFields(mc)) {
                    if (AUTO_UPDATE.equals(f.getAttribute(AUTO))) {
                        field = f;
                        break;
                    }
                }
            }

            if (field == null) {
                throw new MetaException("No MetaField that is useable to prevent dirty writes was found");
            }

            mc.setCacheValue(KEY, field);
        }

        return field;
    }

    ///**
    // * Retrieves the value of the dirty field for an object
    // */
    //protected Object getDirtyFieldValue( MetaClass mc, Object obj ) {
    //   return getDirtyField( mc ).getObject( obj );
    //}
    //private Cache<String,String> mOQLCache = new Cache<String,String>( true, 900, 3600 );
    protected PreparedStatement getPreparedStatement(Connection c, String query, Collection<?> args)
            throws MetaException, SQLException {
        String sql = query; // (String) mOQLCache.get( query );

        // If it's not in the cache, then parse it and put it there
        //if ( sql == null )
        //{
        //Map<String,MetaClass> m = getMetaClassMap( query );

        //if ( m.size() > 0 ) {
        //   sql = convertToSQL( query, m );
        //}
        //else sql = query;

        //mOQLCache.put( query, sql );
        //}

        PreparedStatement s = c.prepareStatement(sql);

        if (args != null) {
            int i = 1;
            for (Object o : args) {
                if (o instanceof Boolean) {
                    s.setBoolean(i, (Boolean) o);
                } else if (o instanceof Byte) {
                    s.setByte(i, (Byte) o);
                } else if (o instanceof Short) {
                    s.setShort(i, (Short) o);
                } else if (o instanceof Integer) {
                    s.setInt(i, (Integer) o);
                } else if (o instanceof Long) {
                    s.setLong(i, (Long) o);
                } else if (o instanceof Float) {
                    s.setFloat(i, (Float) o);
                } else if (o instanceof Double) {
                    s.setDouble(i, (Double) o);
                } else if (o instanceof Date) {
                    s.setTimestamp(i, new Timestamp(((Date) o).getTime()));
                } else if (o == null) {
                    s.setString(i, null);
                } else {
                    s.setString(i, o.toString());
                }

                // Increment the i
                i++;
            }
        }

        return s;
    }

    public int execute(ObjectConnection c, String query, Collection<?> arguments) throws MetaException {
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, true);

        try {
            PreparedStatement s = getPreparedStatement(conn, query, arguments);

            try {
                if (log.isDebugEnabled()) {
                    log.debug("SQL (" + conn.hashCode() + ") - execute: [" + query + " " + arguments + "]");
                }

                return s.executeUpdate();
            } finally {
                s.close();
            }
        } catch (SQLException e) {
            log.error("Unable to execute object query [" + query + "]: " + e.getMessage());
            throw new MetaException("Unable to execute object query [" + query + "]", e);
        }
    }

    protected MetaField getFieldForColumn(MetaObject resultClass, ObjectMapping mapping, String col)
            throws MetaException {
        // Generate a cache key
        //final String KEY = ( new StringBuilder( "getFieldForColumn(" ) ).append( col ).append( ")" ).toString();

        MetaField rc = null; //(MetaField) resultClass.getCacheValue( KEY );

        if (rc == null) {
            // First check against the read mapping
            //ObjectMapping om = getReadMapping( resultClass );
            if (mapping != null) {
                rc = mapping.getField(col);
            }

            // Next try to match by the metafield name
            if (rc == null) {
                try {
                    rc = resultClass.getMetaField(col);
                } catch (MetaFieldNotFoundException e) {
                }
            }

            // Add it to the cache to speed things up
            //if ( rc != null ) resultClass.setCacheValue( KEY, rc );
        }

        return rc;
    }

    /**
     * Executes the specified query and maps it to the given object.
     *
     * String oql = "[" + Product.CLASSNAME + "]" + " SELECT {P.*}, {M.name} AS
     * manuName" + " FROM [" + Product.CLASSNAME + "=P]," + " [" +
     * Manufacturer.CLASSNAME + "=M]" + " WHERE {M.id}={P.manuId} AND {M.id} >
     * ?";
     *
     * String oql = "[{min:int,max:int,num:int}]" + " SELECT MIN({extra2}) AS
     * min, MAX({extra2}) AS max, COUNT(1) AS num" + " FROM [" +
     * Product.CLASSNAME + "]";
     */
    public Collection<?> executeQuery(ObjectConnection c, String query, Collection<?> arguments)
            throws MetaException {
        Connection conn = (Connection) c.getDatastoreConnection();

        // Check for a valid transaction if enforced
        checkTransaction(conn, false);

        try {
            MetaObject resultClass = null;

            query = query.trim();
            if (query.startsWith("[{")) {
                int i = query.indexOf("}]");
                if (i <= 0) {
                    throw new MetaException("OQL does not contain a closing '}]': [" + query + "]");
                }

                String classTemplate = query.substring(2, i).trim();
                query = query.substring(i + 2).trim();
                String templateClassname = "draagon::meta::manager::db::OQL" + classTemplate.hashCode();

                // Get the result class, try it from the cache first
                resultClass = templateCache.get(templateClassname);
                if (resultClass == null) {
                    resultClass = ValueMetaObject.createFromTemplate(templateClassname, classTemplate);
                    templateCache.put(templateClassname, resultClass);
                }
            } else if (query.startsWith("[")) {
                int i = query.indexOf("]");
                if (i <= 0) {
                    throw new MetaException("OQL does not contain a closing ']': [" + query + "]");
                }

                String className = query.substring(1, i).trim();
                query = query.substring(i + 1).trim();

                resultClass = MetaObject.forName(className);
            } else {
                throw new MetaException(
                        "OQL does not contain a result set definition using []'s or {}'s: [" + query + "]");
            }

            PreparedStatement s = getPreparedStatement(conn, query, arguments);

            try {
                if (log.isDebugEnabled()) {
                    log.debug("SQL (" + conn.hashCode() + ") - executeQuery: [" + query + " " + arguments + "]");
                }

                ResultSet rs = s.executeQuery();

                LinkedList<Object> data = new LinkedList<Object>();
                try {
                    ObjectMappingDB mapping = (ObjectMappingDB) getReadMapping(resultClass);

                    while (rs.next()) {
                        Object o = resultClass.newInstance();

                        for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                            String col = rs.getMetaData().getColumnName(i);

                            MetaField mf = getFieldForColumn(resultClass, mapping, col);

                            if (mf != null) {
                                parseField(o, mf, rs, i);
                            }
                        }

                        data.add(o);
                    }

                    return data;
                } finally {
                    rs.close();
                }
            } finally {
                s.close();
            }
        } catch (SQLException e) {
            log.error("Unable to execute object query [" + query + " (" + arguments + ")]: " + e.getMessage());
            throw new MetaException(
                    "Unable to execute object query [" + query + " (" + arguments + ")]: " + e.getMessage(), e);
        }
    }

    ///////////////////////////////////////////////////////
    // TO STRING METHOD
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName()).append("[").append(getMappingHandler().getClass().getSimpleName())
                .append("][").append(getDatabaseDriver().getClass().getSimpleName()).append("]");
        return sb.toString();
    }
}