/*
* 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;
//import com.draagon.meta.manager.db.ObjectMapping;
//import com.draagon.meta.manager.db.MappingHandler;
import com.draagon.meta.*;
import com.draagon.meta.manager.exp.Expression;
import com.draagon.meta.manager.exp.ExpressionGroup;
import com.draagon.meta.manager.exp.ExpressionOperator;
import com.draagon.meta.manager.exp.Range;
import com.draagon.meta.manager.exp.SortOrder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.*;
//import javax.servlet.*;
/**
* The Object Manager Base is able to add, update, delete,
* and retrieve objects of those types from a datastore.
*/
public abstract class ObjectManager
{
private static Log log = LogFactory.getLog( ObjectManager.class );
public final static String IS_KEY = "isKey";
public final static String IS_READONLY = "isReadOnly";
public final static String AUTO = "auto";
public final static String AUTO_CREATE = "create";
public final static String AUTO_UPDATE = "update";
public final static int CREATE = 1;
public final static int UPDATE = 2;
public final static int DELETE = 3;
public static final int AUTO_NONE = 0;
public static final int AUTO_PRIOR = 1;
public static final int AUTO_DURING = 2;
public static final int AUTO_POST = 3;
//private MappingHandler mMappingHandler = null;
public ObjectManager()
{
}
///////////////////////////////////////////////////////
// MANAGEMENT METHODS
//
/**
* Initializes the ObjectManager
*/
public void init()
throws Exception
{
log.info( "Object Manager " + toString() + " initialized" );
}
/**
* Destroys the Object Manager
*/
public void destroy()
{
log.info( "Object Manager " + toString() + " destroyed" );
}
///////////////////////////////////////////////////////
// CONNECTION HANDLING METHODS
//
/**
* Retrieves a connection object representing the datastore
*/
public abstract ObjectConnection getConnection() throws MetaException;
public abstract void releaseConnection( ObjectConnection oc ) throws MetaException;
///////////////////////////////////////////////////////
// ABSTRACT PERSISTENCE METHODS
//
/** Is this a createable class */
public abstract boolean isCreateableClass( MetaClass mc );
/** Is this a readable class */
public abstract boolean isReadableClass( MetaClass mc );
/** Gets the update mapping to the DB */
public abstract boolean isUpdateableClass( MetaClass mc );
/** Gets the delete mapping to the DB */
public abstract boolean isDeleteableClass( MetaClass mc );
protected String getPersistenceAttribute( MetaData md, String name )
{
try {
if ( md.hasAttribute( name ))
return (String) md.getAttribute( name );
} catch ( MetaAttributeNotFoundException e ) {
throw new RuntimeException( "[" + md + "] had attribute [" + name + "], but threw exception reading it", e );
}
return null;
}
protected boolean isReadOnly( MetaData md )
{
try {
if ( "true".equals( md.getAttribute( IS_READONLY ))) return true;
} catch( MetaAttributeNotFoundException e ) {}
return false;
}
/**
* Gets an objects reference which can be used to later load the object
*/
public ObjectRef getObjectRef( Object obj )
{
MetaClass mc = MetaClassLoader.findMetaClass( obj );
Collection<MetaField> keys = getPrimaryKeys( mc );
if ( keys.size() == 0 )
throw new IllegalArgumentException( "MetaClass [" + mc + "] has no primary keys, so no object reference is available" );
String [] ids = new String[ keys.size() ];
int j = 0;
for( Iterator<MetaField> i = keys.iterator(); i.hasNext(); j++ )
{
MetaField f = i.next();
ids[ j ] = f.getString( obj );
}
return new ObjectRef( mc, ids );
}
/**
* Gets an object reference which can be used to later load the object, based on the string reference
*/
public ObjectRef getObjectRef( String refStr ) {
return new ObjectRef( refStr );
}
/**
* Gets the object by the id; throws exception if it did not exist
*/
public abstract Object getObjectByRef( ObjectConnection c, String refStr ) throws MetaException;
/**
* Load the specified object from the datastore
*/
public abstract void loadObject( ObjectConnection c, Object obj ) throws MetaException;
/**
* Add the specified object to the datastore
*/
public abstract void createObject( ObjectConnection c, Object obj ) throws MetaException;
/**
* Update the specified object in the datastore
*/
public abstract void updateObject( ObjectConnection c, Object obj ) throws MetaException;
/**
* Delete the specified object from the datastore
*/
public abstract void deleteObject( ObjectConnection c, Object obj ) throws MetaException;
///////////////////////////////////////////////////////
// DEFAULT IMPEMENTATIONS
// May be overridden for enhanced performance
@SuppressWarnings("unchecked")
protected List<MetaField> getAutoFields( MetaClass mc )
{
final String KEY = "getAutoFields()";
ArrayList<MetaField> auto = (ArrayList<MetaField>) mc.getCacheValue( KEY );
if ( auto == null )
{
auto = new ArrayList<MetaField>();
for( MetaField f : mc.getMetaFields() )
{
if ( f.hasAttribute( AUTO ))
auto.add( f );
}
mc.setCacheValue( KEY, auto );
}
return auto;
}
protected void handleAutoFields( ObjectConnection c, MetaClass mc, Object obj, int state, int action ) throws MetaException
{
for( MetaField f : getAutoFields( mc ))
{
Object auto = f.getAttribute( AUTO );
handleAutoField( c, mc, f, obj, auto, state, action );
}
}
public void handleAutoField( ObjectConnection c, MetaClass mc, MetaField mf, Object obj, Object auto, int state, int action ) throws MetaException
{
if ( state == AUTO_PRIOR )
{
if ( AUTO_CREATE.equals( auto ) && action == CREATE ) {
mf.setLong( obj, new Long( System.currentTimeMillis() ));
}
else if ( AUTO_UPDATE.equals( auto )) {
mf.setLong( obj, new Long( System.currentTimeMillis() ));
}
}
}
public void prePersistence( ObjectConnection c, MetaClass mc, Object obj, int action ) throws MetaException
{
handleAutoFields( c, mc, obj, AUTO_PRIOR , action);
}
public void postPersistence( ObjectConnection c, MetaClass mc, Object obj, int action ) throws MetaException
{
handleAutoFields( c, mc, obj, AUTO_POST , action);
if ( action == CREATE ) {
if ( mc instanceof StatefulMetaClass )
{
// Update the state of the Object
((StatefulMetaClass) mc ).setNew( obj, false );
((StatefulMetaClass) mc ).setModified( obj, false );
((StatefulMetaClass) mc ).setDeleted( obj, false );
}
}
else if ( action == UPDATE ) {
// Update the state of the Object
if ( mc instanceof StatefulMetaClass )
{
((StatefulMetaClass) mc).setNew( obj, false );
((StatefulMetaClass) mc).setModified( obj, false );
((StatefulMetaClass) mc).setDeleted( obj, false );
}
}
else if ( action == DELETE ) {
if ( mc instanceof StatefulMetaClass ) {
((StatefulMetaClass) mc).setDeleted( obj, true );
}
}
}
/**
* Determines whether the MetaField is a key
*/
public boolean isPrimaryKey( MetaField mf )
{
try {
if ( "true".equals( mf.getAttribute( IS_KEY ))) return true;
} catch( MetaAttributeNotFoundException e ) {}
return false;
}
/**
* Retrieves a new object of the specified class
*/
public Object getNewObject( MetaClass mc ) throws PersistenceException
{
Object o = mc.newInstance();
//attachManager( o );
return o;
}
/**
* Attaches the object to the specified meta manager
*/
/*public void attachManager( Object obj )
{
MetaClass mc = getClassForObject( obj );
if ( mc instanceof ManagedMetaClass )
((ManagedMetaClass) mc ).attachManager( this, obj );
}*/
/**
* Verifies the object belongs to this ObjectManager
*/
/*protected void verifyObjectManager( Object obj ) throws PersistenceException
{
if ( obj == null ) throw new IllegalArgumentException( "Cannot persist a null object" );
MetaClass mc = getClassForObject( obj );
if ( !( mc instanceof ManagedMetaClass )) return;
ManagedMetaClass mmc = (ManagedMetaClass) mc;
ObjectManager mm = mmc.getManager( obj );
// WARNING: Do we care?!
// Verify that it is the same meta manager
if ( mm != null && !mm.equals( this ))
throw new PersistenceException( "This object is attached to a different Object Manager [" + mm + "]" );
// If not attached, then attach it
if ( mm == null )
mmc.attachManager( this, obj );
}*/
/**
* Retrieves the fields of a MetaClass which are keys
*/
@SuppressWarnings("unchecked")
public Collection<MetaField> getPrimaryKeys( MetaClass mc )
//throws MetaException
{
final String KEY = "getPrimaryKeys()";
ArrayList<MetaField> fields = (ArrayList<MetaField>) mc.getCacheValue( KEY );
if ( fields == null )
{
fields = new ArrayList<MetaField>();
for( Iterator i = mc.getMetaFields().iterator(); i.hasNext(); )
{
MetaField f = (MetaField) i.next();
if ( isPrimaryKey( f )) fields.add( f );
}
mc.setCacheValue( KEY, fields );
}
//if ( fields.size() == 0 )
// throw new RuntimeException( "No keys found for MetaClass [" + mc + "]" );
return fields;
}
/**
* Get all objects of the specified kind from the datastore
*/
/*public Collection getObjects( ObjectConnection c, MetaClass mc, Collection fields )
throws MetaException
{
return getObjects( c, mc, fields, new QueryOptions() );
}*/
/**
* Stores the specified object by adding, updating, or deleting
*/
public void storeObject( ObjectConnection c, Object obj ) throws PersistenceException
{
StatefulMetaClass pmc = getStatefulMetaClass( obj );
if ( pmc.isNew( obj ) ) createObject( c, obj );
else if ( pmc.isModified( obj ) ) updateObject( c, obj );
else if ( pmc.isDeleted( obj ) ) deleteObject( c, obj );
}
/**
* Retrieves a persistable metaclass from the given object
*/
protected StatefulMetaClass getStatefulMetaClass( Object obj )
{
MetaClass mc = getClassForObject( obj );
if ( !( mc instanceof StatefulMetaClass )) return null;
return (StatefulMetaClass) mc;
}
/**
* Gets the object by the reference; throws exception if it did not exist
*/
/*public Object getObjectByRef( ObjectConnection c, String refStr )
throws MetaException
{
ObjectRef ref = getObjectRef( refStr );
Collection fields = getReadableFields( ref.getMetaClass() );
return getObjectByRef( c, fields, refStr );
}*/
/** Gets the total count of objects */
public long getObjectsCount( ObjectConnection c, MetaClass mc ) throws MetaException
{
return getObjectsCount( c, mc, null );
}
/** Gets the total count of objects with the specified options */
public long getObjectsCount( ObjectConnection c, MetaClass mc, Expression exp ) throws MetaException
{
Collection<MetaField> fields = new ArrayList<MetaField>();
fields.add( getPrimaryKeys(mc).iterator().next() );
QueryOptions options = new QueryOptions( exp );
options.setFields( fields );
return getObjects( c, mc, new QueryOptions( exp )).size();
}
/**
* Get all objects of the specified kind from the datastore
*/
public Collection<?> getObjects( ObjectConnection c, MetaClass mc ) throws MetaException
{
// Collection fields = getReadableFields( mc );
return getObjects( c, mc, new QueryOptions() );
}
/**
* Get all objects of the specified kind from the datastore
*/
public abstract Collection<?> getObjects( ObjectConnection c, MetaClass mc, QueryOptions options ) throws MetaException;
/* {
Collection fields = null;
if ( options.getWriteableOnly() )
fields = getWriteableFields( mc );
else
fields = getReadableFields( mc );
return getObjects( c, mc, fields, options );
}*/
/**
* Get all objects of the specified kind from the datastore
*/
/*public Collection getObjects( ObjectConnection c, MetaClass mc, QueryOptions options )
throws MetaException
{
Collection results = getObjects( c, mc, fields );
Expression exp = options.getExpression();
SortOrder sort = options.getSortOrder();
Range range = options.getRange();
if ( exp != null )
results = filterObjects( results, exp );
if ( sort != null )
results = sortObjects( results, sort );
if ( range != null )
results = clipObjects( results, range );
if( options.isDistinct() )
results = distinctObjects( results );
return results;
}*/
/**
* Loads the specified object from the datastore
*/
/*public void loadObject( ObjectConnection c, Object obj )
throws MetaException
{
Collection fields = getReadableFields( getClassForObject( obj ));
loadObject( c, fields, obj );
}*/
/**
* Loads the specified fields for the objects from the datastore
*/
/*public void loadObjects( ObjectConnection c, Collection fields, Collection objs )
throws MetaException
{
for( Iterator i = objs.iterator(); i.hasNext(); )
loadObject( c, fields, i.next() );
}*/
/**
* Loads the specified objects from the datastore
*/
public void loadObjects( ObjectConnection c, Collection<?> objs )
throws MetaException
{
for( Iterator<?> i = objs.iterator(); i.hasNext(); )
{
Object obj = i.next();
//Collection fields = getReadableFields( getClassForObject( obj ));
loadObject( c, obj );
}
}
/**
* Add the specified objects to the datastore
*/
public void createObjects( ObjectConnection c, Collection<?> objs )
throws MetaException
{
for( Iterator<?> i = objs.iterator(); i.hasNext(); )
createObject( c, i.next() );
}
/**
* Update the specified objects in the datastore
*/
public void updateObjects( ObjectConnection c, Collection<?> objs )
throws MetaException
{
for( Iterator<?> i = objs.iterator(); i.hasNext(); )
updateObject( c, i.next() );
}
/**
* Delete the specified object from the datastore
*/
public void deleteObjectByRef( ObjectConnection c, String ref )
throws MetaException
{
deleteObject( c, getObjectByRef( c, ref ));
}
/**
* Delete the specified objects from the datastore
*/
public int deleteObjects( ObjectConnection c, Collection<?> objs )
throws MetaException
{
int j = 0;
for( Iterator<?> i = objs.iterator(); i.hasNext(); ) {
deleteObject( c, i.next() );
j++;
}
return j;
}
/**
* Delete the objects from the datastore where the field has the specified value
*/
public int deleteObjects( ObjectConnection c, MetaClass mc, Expression exp )
throws MetaException
{
return deleteObjects( c, getObjects( c, mc, new QueryOptions( exp )));
}
/**
* Stores all objecs in the Collection by adding or updating
*/
public void storeObjects( ObjectConnection c, Collection<?> objs )
throws MetaException
{
for( Iterator<?> i = objs.iterator(); i.hasNext(); )
storeObject( c, i.next() );
}
///////////////////////////////////////////////////////
// HELPER METHODS
// May be overridden for enhanced performance
/**
* Sorts the specified objects by the provided sort order
*/
@SuppressWarnings("unchecked")
public static Collection<Object> sortObjects( Collection<Object> objs, SortOrder sort )
throws MetaException
{
if ( objs.size() == 0 ||
sort.getOrder() == SortOrder.NONE ) return objs;
ArrayList<Object> a = new ArrayList<Object>();
a.addAll( 0, objs );
ObjectComparator comp = new ObjectComparator( sort );
Collections.sort( a, comp );
return a;
}
/**
* Retrieves the fields of a MetaClass which are persistable
* and have been modified.
*/
protected Collection<MetaField> getModifiedPersistableFields( StatefulMetaClass smc, Collection<MetaField> fields, Object o )
{
List<MetaField> dirtyFields = new ArrayList<MetaField>();
// Iterate each field on the object that is updateable and see if anything has changed
for( MetaField f : fields ) {
if ( smc.isFieldModified( f, o )) {
dirtyFields.add( f );
}
}
return dirtyFields;
}
/**
* Gets the SQL WHERE clause for the fields of a class
*/
private static boolean getExpressionResult( MetaClass mc, Expression exp, Object obj )
throws MetaException
{
if ( exp instanceof ExpressionGroup )
{
return getExpressionResult( mc, ((ExpressionGroup) exp ).getGroup(), obj );
}
else if ( exp instanceof ExpressionOperator )
{
ExpressionOperator oper = (ExpressionOperator) exp;
boolean a = getExpressionResult( mc, oper.getExpressionA(), obj );
boolean b = getExpressionResult( mc, oper.getExpressionB(), obj );
boolean rc = false;
if ( oper.getOperator() == ExpressionOperator.AND )
rc = a & b;
else
rc = a | b;
//ystem.out.println( "[" + oper.getExpressionA() + "](" + a + ") {" + oper.getOperator() + "} [" + oper.getExpressionB() + "](" + b + ") = " + rc );
return rc;
}
else if ( exp.isSpecial() )
{
throw new MetaException( "Unsupported Special Expression [" + exp + "]" );
}
else
{
MetaField f = mc.getMetaField( exp.getField() );
Object val = f.getObject( obj );
Object val2 = exp.getValue();
int condition = exp.getCondition();
if ( condition == Expression.EQUAL
|| condition == Expression.NOT_EQUAL
|| condition == Expression.GREATER
|| condition == Expression.LESSER
|| condition == Expression.EQUAL_GREATER
|| condition == Expression.EQUAL_LESSER
)
{
return compareValue( val, val2, condition );
}
else if ( condition == Expression.CONTAIN
|| condition == Expression.NOT_CONTAIN
|| condition == Expression.START_WITH
|| condition == Expression.NOT_START_WITH
|| condition == Expression.END_WITH
|| condition == Expression.NOT_END_WITH
|| condition == Expression.EQUALS_IGNORE_CASE
)
{
String s1 = (val==null)?null:val.toString();
String s2 = (val2==null)?null:val2.toString();
if ( s1 == null || s2 == null ) return false;
boolean rc = compareString( s1.toLowerCase(), s2.toLowerCase(), condition );
//ystem.out.println( "[" + s1 + "] (" + condition + ") [" + s2 + "] = " + rc );
return rc;
}
else
throw new MetaException( "Unsupported Expression Condition (" + Expression.condStr(condition) + ") on Expression [" + exp + "]" );
}
}
protected static boolean compareString( String s1, String s2, int condition )
{
switch( condition )
{
case Expression.CONTAIN:
if ( s1.indexOf( s2 ) >= 0 ) return true;
return false;
case Expression.NOT_CONTAIN:
if ( s1.indexOf( s2 ) < 0 ) return true;
return false;
case Expression.START_WITH:
return s1.startsWith( s2 );
case Expression.NOT_START_WITH:
return !s1.startsWith( s2 );
case Expression.END_WITH:
return s1.endsWith( s2 );
case Expression.NOT_END_WITH:
return !s1.endsWith( s2 );
case Expression.EQUALS_IGNORE_CASE:
return s1.equalsIgnoreCase( s2 );
default:
throw new IllegalStateException( "compareString with unsupported condition type (" + Expression.condStr(condition) + ")" );
}
}
protected static boolean compareValue( Object val, Object val2, int condition )
{
// If it's a collection, then iterate the whole array
if ( val2 instanceof Collection ) {
// Has to be EQUAL or NOT EQUAL
if ( condition != Expression.EQUAL && condition != Expression.NOT_EQUAL ) {
throw new IllegalArgumentException( "Can only compare with EQUAL or NOT EQUAL against a collection!" );
}
// Iterate each value in the collection
for( Object v : ((Collection<?>)val2)) {
// Get the resulting condition
boolean ret = compareValue( val, v, condition );
// return true if there are any matches
if ( condition == Expression.EQUAL && ret ) return true;
// if looking for not equal, return false when finding the first one
else if ( condition == Expression.NOT_EQUAL && !ret ) return false;
}
// Return true or false based on the condition
if ( condition == Expression.EQUAL ) return false;
else if ( condition == Expression.NOT_EQUAL ) return true;
else throw new IllegalStateException( "This has to be EQUAL or NOT EQUAL" );
}
int result = 0;
if ( val == null || val2 == null )
{
if ( val == null && val2 == null ) result = 0;
else if ( val == null ) result = -1;
else result = 1;
}
else if ( val instanceof Boolean )
{
if ( ((Boolean) val ).equals( val2 )) result = 0;
else result = 1;
}
else if ( val instanceof Byte )
{
Byte v = (val2 instanceof Byte)?(Byte)val2:new Byte( val2.toString() );
result = ((Byte) val ).compareTo( v );
}
else if ( val instanceof Short )
{
Short v = (val2 instanceof Short)?(Short)val2:new Short( val2.toString() );
result = ((Short) val ).compareTo( v );
}
else if ( val instanceof Integer )
{
Integer v = (val2 instanceof Integer)?(Integer)val2:new Integer( val2.toString() );
result = ((Integer) val ).compareTo( v );
}
else if ( val instanceof Long )
{
Long v = (val2 instanceof Long)?(Long)val2:new Long( val2.toString() );
result = ((Long) val ).compareTo( v );
}
else if ( val instanceof Float )
{
Float v = (val2 instanceof Float)?(Float)val2:new Float( val2.toString() );
result = ((Float) val ).compareTo( v );
}
else if ( val instanceof Double )
{
Double v = (val2 instanceof Double)?(Double)val2:new Double( val2.toString() );
result = ((Double) val ).compareTo( v );
}
else if ( val instanceof Date )
{
Date v = (val2 instanceof Date)?(Date)val2:new Date( Long.parseLong( val2.toString() ));
result = ((Date) val ).compareTo( v );
}
else
{
result = val.toString().toLowerCase().compareTo( val2.toString().toLowerCase() );
}
switch( condition )
{
case Expression.EQUAL:
if ( result == 0 ) return true;
else return false;
case Expression.GREATER:
if ( result > 0 ) return true;
else return false;
case Expression.LESSER:
if ( result < 0 ) return true;
else return false;
case Expression.EQUAL_GREATER:
if ( result >= 0 ) return true;
else return false;
case Expression.EQUAL_LESSER:
if ( result <= 0 ) return true;
else return false;
case Expression.NOT_EQUAL:
if ( result != 0 ) return true;
else return false;
default:
throw new IllegalArgumentException( "Invalid expression condition [" + Expression.condStr(condition) + "]" );
}
}
/**
* Filters the specified objects by the provided expression
*/
public static Collection<Object> filterObjects( Collection<Object> objs, Expression exp )
throws MetaException
{
ArrayList<Object> a = new ArrayList<Object>();
// Check each object for validity
for( Object o : objs )
{
MetaClass mc = MetaClassLoader.findMetaClass( o );
if ( getExpressionResult( mc, exp, o ))
{
//ystem.out.println( "ADDING: " + o );
a.add( o );
}
}
return a;
}
/**
* Clips the specified objects by the provided range
*/
public static Collection<Object> clipObjects( Collection<Object> objs, Range range )
throws MetaException
{
ArrayList<Object> a = new ArrayList<Object>();
int j = 1;
for( Object o : objs )
{
if ( j >= range.getStart() && j <= range.getEnd() )
a.add( o );
}
return a;
}
/**
* Clips the specified objects by the provided range
*/
public static Collection<Object> distinctObjects( Collection<?> objs )
throws MetaException
{
// TODO: Check this, as I don't think it works!
ArrayList<Object> a = new ArrayList<Object>( objs );
for( Iterator<Object> i = a.iterator(); i.hasNext(); )
{
Object o = i.next();
// Find the first index of the object
int fi = a.indexOf( o );
// Remove all objects of the same value
int li = 0;
while (( li = a.lastIndexOf( o )) != fi )
a.remove( li );
}
return a;
}
/**
* Attempts to retrieve the MetaClass for a specified object,
* and throws an exception if one does not exist
*/
public MetaClass getClassForObject( Object o )
throws MetaException
{
MetaClass mc = MetaClassLoader.findMetaClass( o );
if ( mc == null )
throw new MetaClassNotFoundException( "No MetaClass exists for object [" + o + "]" );
return mc;
}
///////////////////////////////////////////////////////
// OBJECT QUERY LANGUAGE METHODS
//
public abstract int execute( ObjectConnection c, String query, Collection<?> arguments ) throws MetaException;
public abstract Collection<?> executeQuery( ObjectConnection c, String query, Collection<?> arguments ) throws MetaException;
///////////////////////////////////////////////////////
// TO STRING METHOD
public String toString()
{
return "Unknown";
}
}