All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.draagon.meta.object.data.DataObjectBase Maven / Gradle / Ivy

/*
 * 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.object.data;

import com.draagon.meta.DataTypes;
import com.draagon.meta.InvalidValueException;
import com.draagon.meta.MetaDataException;
import com.draagon.meta.MetaDataNotFoundException;
import com.draagon.meta.field.MetaField;
import com.draagon.meta.loader.MetaDataLoader;
import com.draagon.meta.loader.MetaDataRegistry;
import com.draagon.meta.object.MetaObject;
import com.draagon.meta.object.MetaObjectAware;
import com.draagon.meta.object.Validatable;
import com.draagon.meta.util.DataConverter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;
import java.util.*;

/**
 * Generic Map of fields and values that can be associated with a MetaObject. The values are retrieved
 * as "attributes".  You can also associate "properties" to the object that can be used for special behaviors
 * when persisting or transforming objects.
 */
public abstract class DataObjectBase implements Serializable, MetaObjectAware, Validatable {

    static final Log log = LogFactory.getLog( DataObjectBase.class );

    /** Holds a Value for the DataObject.
     * Used to know if null was explicitly set or the Value didn't exist.  Can also be enhanced to support the last
     * time a value was created, updated, read, deleted, etc.
     */
    public final class Value implements Serializable {

        private static final long serialVersionUID = 746942287755477951L;

        private DataObjectBase parent = null;
        private Object value = null;
        private DataTypes dataType = null;

        public Value(DataObjectBase parent, Object value, DataTypes dataType) {
            this.parent = parent;
            this.value = value;
            this.dataType = dataType;
        }

        public void setValue(Object value) {
            this.value = DataConverter.toType( dataType, value );
        }

        public DataTypes getDataType() { return dataType; }

        public Object getValue() {
            return value;
        }
    }

    private final Map valueAttributes = new java.util.concurrent.ConcurrentHashMap();
    private final Map valueProperties = new java.util.concurrent.ConcurrentHashMap();

    private boolean allowExtensions = false;
    private boolean enforceStrictness = true;

    // The MetaObject is transient, but the loader name, and object name are needed
    private transient MetaObject metaObject = null;
    private transient List metaFieldNamesCache = null;
    private transient boolean metaObjectForNameFailed = false;
    private boolean hasMetaData = false;
    private String loaderName = null;
    private String objectName = null;

    /**
     * Create a generic value object that supports extensions by default
     */
    public DataObjectBase() { }

    /**
     * Create a generic value object associated to the MetaObject
     * @param mo MetaObject to associated
     */
    public DataObjectBase(MetaObject mo ) {
        setMetaData( mo );
    }

    /**
     * A generic value object with the specified name
     * @param name Name of the object
     */
    public DataObjectBase(String name ) {
        // TODO:  Do we force a MetaObject attached in the future to have the same name...?
        objectName = name;
    }

    protected static  T _newInstance( Class clazz, MetaDataLoader loader, String objectName) {
        return (T) loader.getMetaObjectByName(objectName).newInstance();
    }

    /**
     * Return the name of the object
     * @return the object name
     */
    protected String _getObjectName() {
        return objectName;
    }

    /**
     * Whether this value object supports extensions
     * @return true if extensions are supported
     */
    protected boolean _allowsExtensions() {
        return allowExtensions;
    }

    protected void _allowExtensions( boolean allowExtensions ) {
        this.allowExtensions = allowExtensions;
    }

    protected boolean _enforcesStrictness() {
        return enforceStrictness;
    }

    protected void _enforceStrictness( boolean strict ) {
        this.enforceStrictness = strict;
    }
    
    @Override
    public void setMetaData(MetaObject mo) {

        metaObject = mo;

        // Set the values configured on the DataMetaObject
        if ( mo instanceof DataMetaObject ) {
            DataMetaObject dmo = (DataMetaObject) mo;
            this.allowExtensions = dmo.allowExtensions();
            this.enforceStrictness = dmo.isStrict();
        }

        // Flag that we have metadata set
        hasMetaData = true;

        // Clear flag on metadata failures now that we are setting it
        metaObjectForNameFailed = false;

        // Clear the meta field names cache
        metaFieldNamesCache = null;

        // Set these to handle serialization and re-attaching
        if (mo.getLoader() != null) {
            loaderName = mo.getLoader().getName();
        }
        
        objectName = mo.getName();
    }

    /**
     * Returns whether the DataObject has MetaData attached to it
     * @return true if attached, false if not
     */
    public boolean hasMetaDataAttached() {
        try {
            return (getMetaData() != null);
        } catch( MetaDataNotFoundException e ) {}
        return false;
    }

    /**
     * Return the MetaObject associated with this DataObject
     */
    @Override
    public synchronized MetaObject getMetaData() {

        // If we have the meta object, then return it
        if ( metaObject != null ) return metaObject;

        // If we don't have the MetaObject, but we already tried looking it up before, return null
        if ( metaObjectForNameFailed ) return null;

        // If the object name is set, this could be serialization, so try to reattach the MetaObject
        if ( objectName != null  ) {
            try {
                if (loaderName != null) {
                    MetaDataLoader mcl = MetaDataRegistry.getDataLoader(loaderName);
                    if (mcl != null) {
                        metaObject = mcl.getMetaObjectByName(objectName);
                    } else {
                        metaObject = MetaDataRegistry.findMetaObjectByName(objectName);
                    }
                }

            } catch (MetaDataNotFoundException e) {
                metaObjectForNameFailed = true;
                throw new RuntimeException("Could not re-attach MetaObject: " + e.getMessage(), e);
            }
        }
        // Otherwise, try to find the MetaObject by looking it up in the static MetaDataRegistry method
        else {
            try {
                // If we find the MetaObject, then attach to this DataObject
                MetaObject mo = MetaDataRegistry.findMetaObjectByName(objectName);
                setMetaData( mo );

            } catch( MetaDataNotFoundException e ) {
                metaObjectForNameFailed = true;
            }
        }

        return metaObject;
    }

    /**
     * Check to see if the field is valid and if metadata exists, but only if extensions are allowed
     * @param name Field name to check
     * @return True if the field name is valid
     */
    protected boolean isValidFieldName(String name) {

        if ( allowExtensions ) return true;

        if ( !hasMetaDataAttached()) {
            throw new IllegalArgumentException( "There is no MetaObject attached to this DataObject and it is not extendable, so field ["+name+"] cannot be read or written to");
        }
        else if ( !getMetaData().hasMetaField( name )) {
            if ( enforceStrictness ) {
                throw new IllegalArgumentException( "No field with name ["+name+"] exists on MetaObject, and this is DataObject is set to strictly enforce that it must: " + getMetaData() );
            } else {
                Set ignoreSet = getIgnoreSet( getMetaData() );
                if ( !ignoreSet.contains( name )) {
                    ignoreSet.add( name );
                    log.warn( "No field with name ["+name+"] exists on MetaObject ["+getMetaData()+"], ignored field get/set history: " + ignoreSet );
                }
                return false;
            }
        }
        return true;
    }

    /** Get the ignore sets for the specified MetaObject */
    private static Set getIgnoreSet( MetaObject o ) {

        final String KEY = "DataObject-getIgnoreSet";
        Set set = null;

        synchronized ( o ) {
            set = (Set) o.getCacheValue(KEY);
            if (set == null) {
                set = new HashSet<>();
                o.setCacheValue(o, set);
            }
        }
        return set;
    }

    /**
     * Sets an attribute of the MetaObject
     */
    protected void _setObjectAttribute(String name, Object value ) {

        // Do not store invalid field values
        if (!isValidFieldName( name )) return;

        Value v = (Value) getObjectAttributeValue( name, value );

        value = DataConverter.toType( v.dataType, value );

        // Set the value
        v.setValue(value);
    }

    /**
     * Checks is a property of this object is true based on a value of "true".  Null is false.
     * @param name Name of the property
     * @return Whether the property has a value of "true"
     */
    protected boolean _isObjectPropertyTrue(String name) {
        String s = valueProperties.get( name );
        if ( s != null && s.equalsIgnoreCase( "true" )) return true;
        return false;
    }

    /**
     * Get a property associated with this object.  Properties are used for special operations, not to define fields
     */
    protected String _getObjectProperty(String name ) {
        return valueProperties.get( name );
    }

    /**
     * Sets a property to be associated with this object.  Properties are used for special operations, not to define fields
     */
    protected void _setObjectProperty(String name, String key ) {
        valueProperties.put( name, key );
    }

    /**
     * Returns the MetaFields based on the associated MetaObject
     * @return List of MetaFields or null if no MetaObject attached
     */
    protected List getMetaFieldNames() {
        if ( metaFieldNamesCache != null ) return metaFieldNamesCache;
        metaFieldNamesCache = new ArrayList();
        if ( hasMetaDataAttached() ) {
            for (MetaField f : getMetaData().getMetaFields()) {
                metaFieldNamesCache.add(f.getShortName());
            }
        }
        return metaFieldNamesCache;
    }

    /**
     * Return all field names associated with this object.  Handles all MetaFields on the associated MetaObject
     * if one exists.  Also handles adding any extended fields
     *
     * @return All field names
     */
    protected Collection _getObjectFieldNames() {

        // For the clear cut cases, return the arrays
        if ( hasMetaDataAttached() && !allowExtensions ) return getMetaFieldNames();
        else if ( !hasMetaDataAttached() ) return valueAttributes.keySet();

        // For the mixed case of metadata + extensions, add the extended names
        ArrayList names = new ArrayList();
        if ( hasMetaDataAttached() ) names.addAll( getMetaFieldNames() );
        for ( String name : valueAttributes.keySet() ) {
            if ( !names.contains( name )) names.add( name );
        }
        return names;
    }

    /**
     * Returns whether the attribute is associated with this DataObject (ignoring the MetaObject)
     * @param name Name of the attribute/value
     * @return True if it exists on this DataObject
     */
    protected boolean _hasObjectAttribute( String name ) {
        return valueAttributes.containsKey( name );
    }

    /**
     * Returns the names of all attribute values associated with this DataObject (ignoring the MetaObject)
     * @return Collection of attribute value names
     */
    protected Collection _getObjectAttributes() {
        return valueAttributes.keySet();
    }

    /**
     * Retrieves an attribute of the MetaObject
     */
    protected Object _getObjectAttribute(String name) {

        if (!isValidFieldName( name )) return null;

        Value v = valueAttributes.get(name);
        if ( v != null ) {
            return v.getValue();
        }
        else {
            return null;
        }
    }

    /**
     * Creates a new DataObject.Value
     * @param name Name of the value
     * @param dataType DataType to use
     * @return Created Value
     */
    private Value createObjectAttributeValue( String name, DataTypes dataType ) {

        synchronized (valueAttributes) {
            Value v = (Value) valueAttributes.get(name);
            if (v == null) {
                v = new Value(this, null, dataType);
                valueAttributes.put(name, v);
            } else if ( v.dataType != dataType ) {
                throw new IllegalArgumentException( "A DataObject.Value of name [" + name + "] with dataType [" + v.dataType + "] already exists, cannot create a new Value with dataType [" + dataType + "]" );
            }
            return v;
        }
    }

    /** Get the data type for the specified field */
    private DataTypes getDataType( String name, Object forValue ) {

        // Use the MetaField specific type when setting as an object
        DataTypes type = DataTypes.OBJECT;
        if ( hasMetaDataAttached() && getMetaData().hasMetaField( name )) {
            type = getMetaData().getMetaField(name).getDataType();
        }

        // If no MetaData is attached, then use the value being set to define the data type
        else if ( forValue != null ) {
            return DataTypes.forValueClass( forValue.getClass() );
        }

        return type;
    }

    /**
     * Returns the Value object which contains the actual value and information
     * about when it was created and when it was last modified.
     *
     * @param name The name of the field
     * @param forValue Optional value arg to determine the DataType to assign
     * @return Return the Valuebject.Value
     */
    private Value getObjectAttributeValue(String name, Object forValue) //throws ValueException
    {
        if (!isValidFieldName( name )) return null;

        Value v = valueAttributes.get(name);
        if ( v == null ) {
            // This method is thread safe, so no need to synchronize here
            v = createObjectAttributeValue( name, getDataType( name, forValue ));
        }

        return v;
    }


    /**
     * Returns the Value object which contains the actual value and information
     * about when it was created and when it was last modified.
     *
     * @return Return the DataObject.Value
     */
    protected Value getObjectAttributeValue(String name )  {
        return getObjectAttributeValue( name, null );
    }

    /**
     * Remove a specific object attribute value
     * @param key attribute value to remove
     */
    protected Value _removeObjectAttributeValue(Object key) {
        return valueAttributes.remove(key);
    }

    /**
     * Clears all object attribute values
     */
    protected void _clearObjectAttributeValues() {
        valueAttributes.clear();
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // GET/SET HELPER METHODS

    /** If string is trimmed and empty, set it to null */
    protected String _trimStringToNull(String s ) {
        if ( s != null && s.trim().isEmpty()) s = null;
        return s;
    }

    protected  List _getAndCreateObjectArray( Class clazz, String name ) {
        List current = _objectToTypedArray( clazz, _getObjectAttribute( name ));
        if ( current == null ) {
            current = new ArrayList<>();
            _setObjectAttribute( name, current );
        }
        return current;
    }

    protected  List _objectToTypedArray( Class clazz, Object o ) {
        try {
            return (List) o;
        } catch( ClassCastException e ) {
            throw new InvalidValueException("Expected List of ["+clazz.getName()+"]"+
                    ", but encountered ClassCastException on object ["+o+"]"+
                    " on MetaObject ["+getMetaData().getName()+"]: "+e, e );
        }
    }

    protected  Class _objectToTypedClass( Class clazz, Object o ) {
        Class out = null;
        if ( o != null ) {
            try {
                if (o instanceof Class) {
                    out = (Class) o;
                }
                else if (o instanceof String) {
                    try {
                        out = (Class) Class.forName(o.toString());
                    } catch (ClassNotFoundException e) {
                        throw new InvalidValueException("ClassNotFoundException for value ["+o+"]"
                                +" on MetaObject ["+getMetaData().getName()+"]: ");
                    }
                } else {
                    throw new InvalidValueException("Expected Class or String, but value was "+
                            "[" + o.getClass().getName() + "] on MetaObject "+
                            "[" + getMetaData().getName() + "]");
                }
            } catch (ClassCastException e) {
                throw new InvalidValueException("Expected Class of ["+clazz.getName()+"]"+
                        ", but found class ["+o.getClass().getName()+"]"+
                        " on MetaObject ["+getMetaData().getName()+"]");
            }
        }
        return out;
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // MISC METHODS

    /**
     * Determines if this object is equivalent to the passed in object.
     * If both objects are not associated to a MetaObject, it just
     * compares the attribute key values for equality.  If MetaObjects
     * are attached then it compares each value for all MetaFields.
     *
     * @param o Object to compare
     * @return True if equals, false if not
     */
    @Override
    public boolean equals( Object o ) {

        if (!(o instanceof DataObjectBase))
            return false;

        DataObjectBase v = (DataObjectBase) o;
        MetaObject mo = getMetaData();
        MetaObject mv = v.getMetaData();

        // If they are mismatched on having MetaData attached, they are not equal
        if ( mo == null && mv != null || mo != null && mv == null )
            return false;

        // Compare each field in the MetaObject, if they have the same type, subtype, name
        if ( mo != null
                && mo.isSameTypeSubTypeName( mv )
                && compareValues(v)) {

            return true;
        }

        // Compare the object names && attribute map
        else if ( mo == null
                && objectName.equals( v.objectName )
                && compareValues(v)) {

            return true;
        }

        return false;
    }

    private boolean compareValues(DataObjectBase v) {

        // Get a union of all field names
        Set fields = new HashSet<>( valueAttributes.keySet() );
        fields.addAll( v.valueAttributes.keySet() );

        // Compare all fields' values
        for ( String f : fields ) {
            if ( !compareValue(
                    _getObjectAttribute( f ),
                    v._getObjectAttribute( f ))) {
                return false;
            }
        }

        return true;
    }

    /** Compare one value to another */
    private boolean compareValue( Object o1, Object o2 ) {
        if (( o1 == null && o2 == null )
                || ( o1 != null && o1.equals( o2 ))) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        
        StringBuilder b = new StringBuilder();

        try {

            b.append("[").append(this.getClass().getSimpleName())
                    .append(":").append( _getObjectName() ).append("]");

            boolean first = true;
            b.append('{');
            for (String name : _getObjectFieldNames() ) {
                if (first) {
                    first = false;
                } else {
                    b.append(',');
                }

                b.append(name);
                b.append(':');
                b.append(_getObjectAttribute(name));
            }
            b.append('}');
        } catch (MetaDataException e) {
            // TODO: Eventually put all attributes here
        }

        return b.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy