tools.xor.AbstractBO.java Source code

Java tutorial

Introduction

Here is the source code for tools.xor.AbstractBO.java

Source

/**
 * XOR, empowering Model Driven Architecture in J2EE applications
 *
 * Copyright (c) 2012, Dilip Dalton
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and limitations 
 * under the License.
 */

package tools.xor;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

import tools.xor.operation.DenormalizedQueryOperation;
import tools.xor.operation.QueryOperation;
import tools.xor.operation.ReadOperation;
import tools.xor.service.DataAccessService;
import tools.xor.util.ClassUtil;
import tools.xor.util.Constants;
import tools.xor.util.ObjectCreator;
import tools.xor.view.QueryViewProperty;

public abstract class AbstractBO implements BusinessObject {
    private static final Logger logger = LogManager.getLogger(new Exception().getStackTrace()[0].getClassName());
    private static final long serialVersionUID = 1L;

    public static final String CURRENT = ".";

    // SDO Path related constants
    // Do we need to support this? Is this used?
    // Seems like duplicate functionality to view (Probably needed to support collections)
    // Should we move this to AggregateView? and support them in views?
    public static final String PATH_DELIMITER = "/";
    public static final String INDEX_FROM_0 = ".";
    public static final String INDEX_START = "[";
    public static final String INDEX_END = "]";
    public static final String PATH_CONTAINER = "..";
    public static final String ATTR_DELIMITER = "=";
    public static final String INSERT_OPERATOR = "+"; // insert at index for a set operation. Returns the element at index for a get operation.
    public static final String APPEND_OPERATOR = "<<"; // append at end of list for a set operation. Returns the last element in a GET operation.

    protected Object instance; // The object containing the actual data. This can be a JPA/Hibernate/Javabean object.
    protected Type type; // Represents the type of the entity, that holds the properties that make up this entity
    protected boolean persistent; // This flag is set to true, if the object was created from the persistence store
    protected boolean modified; // This flag is set to true, if any fields or collections have been modified
    protected ObjectCreator objectCreator; // Cache to hold objects. Useful to set multiple references to an object and providing repeatable read capability. object.

    // TODO: Move to ObjectGraph
    protected DataObject container; // A reference to owning object of this entity in an aggregate
    protected Property containmentProperty; // Represents the property referenced from the owning entity to this entity
    protected boolean visited; // Used to prevent loops when traversing the graph   

    // TODO: Move to ObjectCreator
    protected ObjectPersister objectPersister; // Mechanism used to resolve dependencies and property order persistence related actions without causing data integrity conflicts.

    @Override
    public boolean isPersistent() {
        return this.persistent;
    }

    @Override
    public void setPersistent(boolean persistent) {
        this.persistent = persistent;
    }

    @Override
    public boolean isDependent() {
        return getContainmentProperty() != null;
    }

    @Override
    public ObjectPersister getObjectPersister() {
        return objectPersister;
    }

    public AbstractBO(Type type, DataObject container, Property containmentProperty, ObjectCreator objectCreator) {
        this.type = type;
        this.container = container;
        this.containmentProperty = containmentProperty;
        this.objectCreator = objectCreator;
    }

    @Override
    public boolean isRoot() {
        return this == getRootObject();
    }

    @Override
    public boolean isVisited() {
        return visited;
    }

    @Override
    public void setVisited(boolean visited) {
        this.visited = visited;
    }

    /**
     * This is also used in the External Business Object for performance reasons
     * to walk only those paths that have been modified
     */
    @Override
    public boolean isModified() {
        return modified;
    }

    @Override
    public void setModified(boolean modified) {
        this.modified = modified;
    }

    @Override
    public void addEntity(BusinessObject entity) {
        Object id = entity.getIdentifierValue();
        if (id != null) {
            EntityKey entityKey = getObjectCreator().getTypeMapper().getEntityKey(id, entity);
            getObjectCreator().addByEntityKey(entityKey, entity);
        }
    }

    @Override
    public void removeEntity(BusinessObject entity) {
        Object id = entity.getIdentifierValue();
        if (id != null) {
            EntityKey entityKey = getObjectCreator().getTypeMapper().getEntityKey(id, entity);
            getObjectCreator().removeByEntityKey(entityKey);
        }
    }

    /**
     * Get the derived data object based off a reference data object
     * <tt>EntityKey</tt>
     */
    @Override
    public BusinessObject getEntity(BusinessObject entity) {

        EntityKey entityKey = getObjectCreator().getTypeMapper().getEntityKey(entity.getIdentifierValue(), entity);
        return getObjectCreator().getByEntityKey(entityKey);
    }

    @Override
    public BusinessObject getBySurrogateKey(Object id, Type type) {
        return getObjectCreator().getByEntityKey(new SurrogateEntityKey(id, type.getName()));
    }

    @Override
    public BusinessObject getByNaturalKey(Map<String, Object> naturalKeyValues, Type type) {
        return getObjectCreator().getByEntityKey(new NaturalEntityKey(naturalKeyValues, type.getName()));
    }

    private Map<String, Object> getKeyValue(Set<String> keys) {
        Map<String, Object> keyValues = new HashMap<String, Object>();
        for (String key : keys) {
            keyValues.put(key, get(key));
        }
        return keyValues;
    }

    @Override
    /**
     * Invoked on the Collection Element data object
     */
    public Object getCollectionElementKey(Property property) {

        Set<String> collectionKey = ((ExtendedProperty) property).getCollectionKey();
        if (collectionKey != null) {
            return getKeyValue(collectionKey);
        } else { // fallback to id
            Type elementType = ((ExtendedProperty) property).getElementType();
            Property identifier = ((EntityType) elementType).getIdentifierProperty();
            return (this.get(identifier) == null) ? null : this.get(identifier).toString();
        }
    }

    @Override
    public String getInstanceClassName() {
        if (instance == null) {
            return getType().getInstanceClass().getName();
        }

        if (instance != JSONObject.class || !((JSONObject) instance).has(Constants.XOR.TYPE)) {
            return objectCreator.getTypeMapper().toDomain(getType()).getName();
        } else {
            return ((JSONObject) instance).getString(Constants.XOR.TYPE);
        }
    }

    @Override
    public String getOpenProperty(String propertyName) {
        if (instance instanceof JSONObject) {
            if (((JSONObject) instance).has(propertyName)) {
                return ((JSONObject) instance).getString(propertyName);
            }
        }

        return null;
    }

    @Override
    public Object getInstance() {
        return instance;
    }

    @Override
    public Object getNormalizedInstance(Settings settings) {
        return objectCreator.getCreationStrategy().getNormalizedInstance(this, settings);
    }

    @Override
    public void setInstance(Object instance) {
        if (this.instance == instance)
            return;

        if (instance != null && this.getContainmentProperty() != null) {
            ExtendedProperty property = (ExtendedProperty) this.getContainmentProperty();

            // We skip check for dynamic types
            if (!property.getType().isOpen() && !objectCreator.getTypeMapper().isOpen(instance.getClass())) {
                if ((property.isSet() && !Set.class.isAssignableFrom(instance.getClass()))
                        || (property.isList() && !List.class.isAssignableFrom(instance.getClass()))
                        || (property.isMap() && !Map.class.isAssignableFrom(instance.getClass()))
                        || (!property.isSet() && !property.isList() && !property.isMap()
                                && !type.getInstanceClass().isAssignableFrom(instance.getClass()))) {
                    logger.error("Instance class and type conflict: Instance class: "
                            + instance.getClass().getName() + ", type: " + getType().getName()
                            + ", containment property type: " + this.getContainmentProperty().getType().getName()
                            + ", containment property class: "
                            + this.getContainmentProperty().getType().getInstanceClass().getName()
                            + ", containment property name: " + this.getContainmentProperty().getName());

                    throw new IllegalArgumentException("The DataObject instance class "
                            + instance.getClass().getName() + " is not compatible with its type "
                            + property.getType().getName() + " property: " + property.getName() + " type class: "
                            + property.getType().getInstanceClass().getName() + " property class: "
                            + property.getClass().getName() + " type class: "
                            + property.getType().getClass().getName());
                }
            }
        }

        if (BusinessObject.class.isAssignableFrom(instance.getClass()))
            throw new RuntimeException("Cannot assign a data object as an instance");

        Object oldInstance = this.instance;
        this.instance = instance;

        if (oldInstance != null) {
            objectCreator.updateInstance(this, oldInstance);
        }
    }

    private boolean isIndexOperation(String indexStr) {
        if (indexStr.trim().endsWith(INSERT_OPERATOR) || indexStr.trim().equals(APPEND_OPERATOR))
            return true;

        return false;
    }

    private Object getIndexObject(String indexStr, String propertyName) {
        if (indexStr.trim().endsWith(INSERT_OPERATOR)) {
            int index = Integer.parseInt(indexStr.substring(0, indexStr.indexOf(INSERT_OPERATOR)).trim());
            return ((List<?>) get(getType().getProperty(propertyName))).get(index);
        } else if (indexStr.trim().equals(APPEND_OPERATOR)) {
            List<?> listObj = (List<?>) get(getType().getProperty(propertyName));
            return listObj.get(listObj.size() - 1);
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    private void setIndexObject(String indexStr, String propertyName, Object value) {
        List<Object> valueList = (List<Object>) get(propertyName);
        if (indexStr.trim().endsWith(INSERT_OPERATOR)) {
            int index = Integer.parseInt(indexStr.substring(0, indexStr.indexOf(INSERT_OPERATOR)).trim());
            valueList.add(index, value);

        } else if (indexStr.trim().equals(APPEND_OPERATOR))
            valueList.add(value);
    }

    /**
     *  SDO Path expression. Enhancements made for XOR.
     * 
     *   path ::= (scheme ':')? '/'? (step '/')*   step
     *   scheme ::= [^:]+
     *   step ::= '@'? property
     *          | property '[' index_from_1 ']'
     *          | property '.' index_from_0
     *          | reference '[' attribute '=' value ']'
     *          | ".."
     *   property ::= NCName ;; may be simple or complex type
     *   attribute ::= NCName ;; must be simple type
     *   reference :: NCName ;; must be DataObject type
     *   index_from_0 ::= Digits ('+')? | "<<"
     *   index_from_1 ::= NotZero (Digits)? ('+')? | "<<"
     *   value ::= Literal
     *          | Number
     *          | Boolean
     *   Literal ::= '"' [^"]* '"'
     *          | "'" [^']*   "'"
     *   Number ::= Digits ('.' Digits?)?
     *          | '.' Digits
     *   Boolean ::= true
     *          | false
     *   NotZero ::= [1-9]
     *   Digits ::= [0-9]+
     *   
     *   ;; leading '/' begins at the root
     *   ;; ".." is the containing DataObject, using containment properties
     *   ;; Only the last step have an attribute as the property
     *   
     *   The scheme is an extension mechanism for supporting additional path expressions 
     *   in the future. No schema and a scheme of "sdo:" are equivalent, representing this syntax.
     *   
     *   If more than one property shares the same name, only the first is matched by the path expression, 
     *   using property.name for name matching. If there are alias names assigned,
     *   those are also used to match. Also, names including any of the special characters of the
     *   syntax (./[]='"@) are not accessible. Each step of the path before the last must return a
     *   single DataObject. When the property is a Sequence, the values returned are those of the
     *   getValue() accessor.
     *   
     * 
     * @param path
     * @return value at path
     */
    private Object getPathObject(String path) {

        if (path == null || "".equals(path))
            throw new IllegalArgumentException("Path cannot be empty or null");

        if (path.equals(PATH_CONTAINER))
            return getContainer();

        if (path.equals(PATH_DELIMITER)) { // root
            DataObject result = this;
            while (result.getContainer() != null)
                result = result.getContainer();
            return result;
        }

        // multiple components
        if (path.contains(PATH_DELIMITER)) {
            if (path.startsWith(PATH_DELIMITER)) {
                return ((DataObject) getPathObject("/")).get(path.substring(path.indexOf(PATH_DELIMITER) + 1));
            } else {
                String firstComponent = path.substring(0, path.indexOf(PATH_DELIMITER));
                String remainingPath = path.substring(path.indexOf(PATH_DELIMITER) + 1);

                // Get the property name related to the first path component
                Object anchor = getPathObject(firstComponent);
                if (anchor != null) {
                    BusinessObject anchorBO = objectCreator.getExistingDataObject(anchor);
                    if (anchorBO != null) {
                        return anchorBO.get(remainingPath);
                    } else {
                        throw new RuntimeException("Cannot find BusinessObject in object creator");
                    }
                } else {
                    return null;
                }
            }
        }

        // single component
        // List element indexed from 0
        if (path.contains(INDEX_FROM_0)) {
            String propertyName = path.substring(0, path.indexOf(INDEX_FROM_0));
            String indexStr = path.substring(path.indexOf(INDEX_FROM_0) + 1);
            try {
                int index = Integer.parseInt(indexStr);
                return ((List<?>) get(getType().getProperty(propertyName))).get(index);
            } catch (NumberFormatException e) {
                if (isIndexOperation(indexStr))
                    return getIndexObject(indexStr, propertyName);

                // Map collection
                Property property = getType().getProperty(propertyName);
                if (Map.class.isAssignableFrom(property.getType().getInstanceClass())) {
                    Map<?, ?> values = (Map<?, ?>) get(property);
                    return values.get(indexStr);
                }
            }
        }

        // List element indexed from 1
        if (path.contains(INDEX_START)) {
            String propertyName = path.substring(0, path.indexOf(INDEX_START));
            String indexStr = path.substring(path.indexOf(INDEX_START) + 1, path.indexOf(INDEX_END));

            try {
                int index = Integer.parseInt(indexStr);
                return ((List<?>) getPathObject(propertyName)).get(index - 1);
            } catch (NumberFormatException e) {
                if (isIndexOperation(indexStr))
                    return getIndexObject(indexStr, propertyName);

                // List element indexed by attribute value
                return getByPropertyFilter(path);
            }
        }

        return get(getType().getProperty(path));
    }

    @SuppressWarnings("unchecked")
    protected Object getByPropertyFilter(String path) {

        String propertyName = path.substring(0, path.indexOf(INDEX_START));
        String indexStr = path.substring(path.indexOf(INDEX_START) + 1, path.indexOf(INDEX_END));

        if (((ExtendedProperty) containmentProperty).isMap() && indexStr.indexOf(ATTR_DELIMITER) == -1) {
            Map<?, ?> values = (Map<?, ?>) instance;
            Object valueInstance = values.get(indexStr);
            return objectCreator.createDataObject(valueInstance,
                    ((ExtendedProperty) containmentProperty).getElementType(), (BusinessObject) container,
                    containmentProperty);
        }

        // TODO: Check path validity 

        if (((ExtendedProperty) containmentProperty).getElementType().isDataType())
            throw new IllegalArgumentException(
                    "The property '" + propertyName + "' refers to a non-DataObject collection");

        if (!Collection.class.isAssignableFrom(containmentProperty.getType().getInstanceClass()))
            throw new IllegalStateException("Expecting a collection instance");

        Collection<DataObject> values = (Collection<DataObject>) getList(containmentProperty);

        String attributeName = indexStr.substring(0, indexStr.indexOf(ATTR_DELIMITER));
        Property attributeProperty = ((ExtendedProperty) containmentProperty).getElementType()
                .getProperty(attributeName);
        if (!attributeProperty.getType().isDataType())
            throw new IllegalArgumentException(
                    "Attribute '" + attributeName + "' cannot have a value of type DataObject");

        String attributeValue = indexStr.substring(indexStr.indexOf(ATTR_DELIMITER) + 1);
        attributeValue = attributeValue.replaceAll("^\"|\"$|^'|'$", "");

        // TODO: ensure the string representation is according to the SDO spec

        // Retrieve by identifier if possible
        if (((EntityType) ((ExtendedProperty) containmentProperty).getElementType())
                .getIdentifierProperty() == attributeProperty) {
            return getBySurrogateKey(attributeValue, ((ExtendedProperty) containmentProperty).getElementType());
        } else {
            for (DataObject dataObject : values) {
                if (dataObject.get(attributeProperty).toString().equals(attributeValue))
                    return dataObject;
            }
        }

        return null;
    }

    @Override
    public Object get(String path) {
        return getPathObject(path);
    }

    protected DataObject getDeepestContainer(String path) {
        if (path == null || "".equals(path))
            throw new IllegalArgumentException("Path cannot be empty or null");

        if (path.equals(PATH_CONTAINER) || path.equals(PATH_DELIMITER))
            throw new IllegalArgumentException("Path should refer to a property");

        // Get the container
        AbstractBO container = this;
        if (path.contains(PATH_DELIMITER)) { // find the correct container
            String containerPath = path.substring(0, path.lastIndexOf(PATH_DELIMITER));
            container = (AbstractBO) getPathObject(containerPath);
        }

        return container;
    }

    @Override
    public BusinessObject createDataObject(Object id, Type instanceType, Property property) throws Exception {
        return this.createDataObject(id, null, instanceType, property);
    }

    public BusinessObject createDataObject(Object id, Map<String, Object> naturalKeyValues, Type instanceType,
            Property property) throws Exception {
        Object propertyInstance = createInstance(objectCreator, id, naturalKeyValues, instanceType);
        BusinessObject result = objectCreator.createDataObject(propertyInstance, instanceType,
                property == null ? null : this, property);

        if (property != null)
            ((ExtendedProperty) property).setValue(this, propertyInstance);

        return result;
    }

    public static Object createInstance(ObjectCreator oc, Object id, Type instanceType) throws Exception {
        return createInstance(oc, id, null, instanceType);
    }

    public static Object createInstance(ObjectCreator oc, Object id, Map<String, Object> naturalKeyValues,
            Type instanceType) throws Exception {
        Object propertyInstance = null;

        // We will use the already loaded object in session if one is available
        // This is done only for the query operation that retrieves managed objects
        if (instanceType == null)
            logger.error("!!!!! instanceType is null for object id: " + id);
        if (oc.isReadOnly() && oc.getTypeMapper().isDomain(instanceType.getInstanceClass())) {
            propertyInstance = oc.getPersistenceOrchestrator().getCached(instanceType.getInstanceClass(), id);

            if (propertyInstance != null) {
                logger.info("Found in cache for type: " + instanceType.getInstanceClass().getName() + " and id: "
                        + id.toString());
            }
        }

        if (propertyInstance == null) {
            propertyInstance = oc.createInstance(instanceType);
            if (!instanceType.isDataType()) {
                if (((EntityType) instanceType).getIdentifierProperty() != null) {
                    ((ExtendedProperty) ((EntityType) instanceType).getIdentifierProperty())
                            .setValue(propertyInstance, id);
                }
                if (((EntityType) instanceType).getNaturalKey() != null && naturalKeyValues != null) {
                    for (String key : ((EntityType) instanceType).getNaturalKey()) {
                        ((ExtendedProperty) ((EntityType) instanceType).getProperty(key)).setValue(propertyInstance,
                                naturalKeyValues.get(key));
                    }
                }
            }
        }

        return propertyInstance;
    }

    @Override
    public BusinessObject createDataObject(Object id, Type instanceType) throws Exception {
        Object propertyInstance = createInstance(objectCreator, id, instanceType);
        return objectCreator.createDataObject(propertyInstance, instanceType, null, null);
    }

    @Override
    public void set(String propertyPath, Map<String, Object> propertyResult, EntityType domainEntityType)
            throws Exception {

        if (domainEntityType.isOpen()) {
            String propertyName = propertyPath.substring(propertyPath.indexOf(OpenType.DELIM) + 1);
            Property property = getType().getProperty(propertyName);
            ((ExtendedProperty) property).setValue(this,
                    propertyResult.get(QueryViewProperty.qualifyProperty(propertyPath)));
            return;
        }

        // If we are setting a null value then nothing needs to be done
        if (propertyResult.get(QueryViewProperty.qualifyProperty(propertyPath)) == null)
            return;

        // Since this builds the path for objects already persisted, the identifier value will not be null
        String[] pathSteps = propertyPath.split(Settings.PATH_DELIMITER_REGEX);
        BusinessObject current = this;
        StringBuilder currentPath = new StringBuilder(QueryViewProperty.ROOT_PROPERTY_NAME);
        for (String step : pathSteps) {
            if (currentPath.length() > 0)
                currentPath.append(Settings.PATH_DELIMITER);
            currentPath.append(step);

            Property property = current.getInstanceProperty(step);
            Property domainProperty = domainEntityType
                    .getProperty(currentPath.substring(currentPath.indexOf(Settings.PATH_DELIMITER) + 1));
            if (property == null)
                throw new RuntimeException("Unable to resolve property: " + propertyPath);

            //Object propertyDO = current.get(property);
            Object propertyDO = current.getDataObject(property);
            if (!property.isMany()) {
                if (property.getType().isDataType()) {
                    // Populate the field and return the data object
                    ((ExtendedProperty) property).setValue(current,
                            propertyResult.get(QueryViewProperty.qualifyProperty(propertyPath)));
                    return;
                }

                if (((EntityType) property.getType()).isEmbedded()) {
                    // Is this true? 
                    // TODO: Check if the embedded object gets created
                    // What about collection of embedded objects?
                    // Maybe we only allow the fast path of object creation, i.e., the result needs to be sorted
                    //   and we use this ability to construct the fields as we scan through each row.
                    //   This also allows optimization using OpenCL
                    continue; // Embedded objects are automatically created as part of its lifecycle owner
                }

                if (propertyDO == null) {
                    // Set the natural key if there is one
                    Map<String, Object> naturalKeyValues = new HashMap<String, Object>();
                    if (((EntityType) property.getType()).getNaturalKey() != null) {
                        Set<String> naturalKey = ((EntityType) property.getType()).getNaturalKey();
                        if (naturalKey != null) {
                            for (String key : naturalKey) {
                                Object keyValue = propertyResult.get(currentPath + Settings.PATH_DELIMITER + key);
                                naturalKeyValues.put(key, keyValue);
                            }
                            propertyDO = getByNaturalKey(naturalKeyValues, domainProperty.getType());
                        }
                    }
                    // Check if there is a data object with the given key
                    // Get the identifier value
                    Object idValue = null;
                    if (propertyDO == null && ((EntityType) property.getType()).getIdentifierProperty() != null) {
                        idValue = propertyResult.get(currentPath + Settings.PATH_DELIMITER
                                + ((EntityType) property.getType()).getIdentifierProperty().getName());
                        propertyDO = getBySurrogateKey(idValue, domainProperty.getType());
                    }

                    if (propertyDO == null) { // create and set the instance object
                        // check if we are narrowing
                        String narrowToType = (String) propertyResult.get(
                                currentPath + Settings.PATH_DELIMITER + QueryViewProperty.ENTITYNAME_ATTRIBUTE);
                        EntityType objectType = (EntityType) property.getType();
                        if (narrowToType != null)
                            objectType = (EntityType) getObjectCreator().getDAS().getType(narrowToType);
                        propertyDO = current.createDataObject(idValue, naturalKeyValues, objectType, property);
                    }
                }
                if (property.isContainment()) {
                    ((BusinessObject) propertyDO).setContainer(current);
                    ((BusinessObject) propertyDO).setContainmentProperty(property);
                }

                // Set the instance value in the container
                ((ExtendedProperty) property).setValue(current, ((BusinessObject) propertyDO).getInstance());

                current = (BusinessObject) propertyDO;
            } else {

                //System.out.println("propertyDO class: " + propertyDO.getClass() + ", property: " + property.getName());
                if (propertyDO == null) { // create and set the collection/map object
                    propertyDO = current.createDataObject(null, property.getType(), property);
                } else if (!BusinessObject.class.isAssignableFrom(propertyDO.getClass())) {
                    propertyDO = objectCreator.createDataObject(propertyDO, property.getType(), current, property);
                }
                current = (BusinessObject) propertyDO;
                Object elementDO = null;

                // Get the identifier value from the collection element
                Type elementType = ((ExtendedProperty) property).getElementType();
                Type domainElementType = ((ExtendedProperty) domainProperty).getElementType();

                // Get by the natural key if there is one
                Map<String, Object> naturalKeyValues = new HashMap<String, Object>();
                if (((EntityType) elementType).getNaturalKey() != null) {
                    Set<String> naturalKey = ((EntityType) elementType).getNaturalKey();
                    for (String key : naturalKey) {
                        Object keyValue = propertyResult.get(currentPath + Settings.PATH_DELIMITER + key);
                        naturalKeyValues.put(key, keyValue);
                    }

                    // Get the element
                    elementDO = getByNaturalKey(naturalKeyValues, domainElementType);
                }

                Object idValue = null;
                if (elementDO == null && ((EntityType) elementType).getIdentifierProperty() != null) {
                    idValue = propertyResult.get(currentPath + Settings.PATH_DELIMITER
                            + ((EntityType) elementType).getIdentifierProperty().getName());
                    elementDO = getBySurrogateKey(idValue, domainElementType);
                }

                // check flag to see if the containment should be set
                if (elementDO != null && property.isContainment()) {
                    ((BusinessObject) elementDO).setContainer(current); // Containment property is null for a collection element
                }

                if (elementDO == null) { // create and set the instance object

                    // Get the property instance if possible
                    Object elementInstance = null;

                    if (((ExtendedProperty) property).isMap()) {
                        Object keyValue = propertyResult
                                .get(currentPath + Settings.PATH_DELIMITER + QueryViewProperty.MAP_KEY_ATTRIBUTE);
                        Map map = (Map) current.getInstance();
                        elementInstance = map.get(keyValue);
                    }

                    if (elementInstance == null) {
                        if (idValue != null || naturalKeyValues.size() > 0)
                            elementDO = current.createDataObject(idValue, naturalKeyValues,
                                    (EntityType) elementType, null);
                        else
                            return; // Does not have a collection element
                    } else
                        // create the data object using the instance
                        elementDO = objectCreator.createDataObject(elementInstance, elementType, current, null);

                    // check flag to see if the containment should be set
                    if (property.isContainment())
                        ((BusinessObject) elementDO).setContainer(current); // Containment property is null for a collection element
                }

                // Add the element
                Object elementInstance = ((BusinessObject) elementDO).getInstance();
                if (((ExtendedProperty) property).isMap()) {
                    // If this is a map, get the key
                    Object keyValue = propertyResult
                            .get(currentPath + Settings.PATH_DELIMITER + QueryViewProperty.MAP_KEY_ATTRIBUTE);
                    Map map = (Map) current.getInstance();
                    map.put(keyValue, elementInstance);
                } else if (((ExtendedProperty) property).isList()) {
                    Object indexValue = propertyResult
                            .get(currentPath + Settings.PATH_DELIMITER + QueryViewProperty.LIST_INDEX_ATTRIBUTE);
                    List list = (List) current.getInstance();
                    int index = Integer.parseInt(indexValue.toString());
                    if (index >= list.size() || list.get(index) != elementInstance)
                        list.add(elementInstance);
                } else if (((ExtendedProperty) property).isSet()) {
                    // Currently Immutable JSON is treated as a set, so we should check for this
                    if (current.getInstance() instanceof JSONArray) {
                        JSONArray jsonArray = (JSONArray) current.getInstance();
                        jsonArray.put(elementInstance);
                    } else {
                        Set set = (Set) current.getInstance();
                        set.add(elementInstance);
                    }
                }

                current = (BusinessObject) elementDO;
            }
        }

        return;
    }

    @Override
    public Property getPropertyByPath(String path) {

        // Get the container
        AbstractBO container = (AbstractBO) getDeepestContainer(path);
        String propertyPath = path.substring(path.lastIndexOf(PATH_DELIMITER) + 1);

        // case 1: List element indexed from 0
        if (propertyPath.contains(INDEX_FROM_0)) {
            String propertyName = propertyPath.substring(0, propertyPath.indexOf(INDEX_FROM_0));
            return container.getType().getProperty(propertyName.trim());
        }

        // case 2: List element indexed from 1
        if (propertyPath.contains(INDEX_START)) {
            String propertyName = propertyPath.substring(0, propertyPath.indexOf(INDEX_START));
            return container.getType().getProperty(propertyName.trim());
        }

        return container.getType().getProperty(propertyPath);
    }

    @Override
    public void set(Property property, Object value) {
        ((ExtendedProperty) property).setValue(this, value);
    }

    @Override
    public void set(String path, Object value) {
        if (path == null || "".equals(path))
            throw new IllegalArgumentException("Path cannot be empty or null");

        if (path.equals(PATH_CONTAINER) || path.equals(PATH_DELIMITER))
            throw new IllegalArgumentException("Path should refer to a property");

        // Get the container
        AbstractBO container = this;
        if (path.contains(PATH_DELIMITER)) { // find the correct container
            String containerPath = path.substring(0, path.lastIndexOf(PATH_DELIMITER));
            container = (AbstractBO) getPathObject(containerPath);
            path = path.substring(path.lastIndexOf(PATH_DELIMITER) + 1);
            container.set(path, value);
            return;
        }

        // Set the value for the property
        // Path now contains the last component
        if (path.contains(INDEX_FROM_0) || path.contains(INDEX_START)) { // List/attribute property setter
            // List element indexed from 0
            if (path.contains(INDEX_FROM_0)) {
                String propertyName = path.substring(0, path.indexOf(INDEX_FROM_0));
                String indexStr = path.substring(path.indexOf(INDEX_FROM_0) + 1);
                try {
                    int index = Integer.parseInt(indexStr);
                    List<Object> valueList = (List<Object>) get(propertyName);
                    valueList.set(index, value);
                } catch (NumberFormatException e) {
                    Property property = getType().getProperty(propertyName);

                    if (isIndexOperation(indexStr)) {
                        setIndexObject(indexStr, propertyName, value);
                    } else if (Map.class.isAssignableFrom(property.getType().getInstanceClass())) {
                        Map<String, Object> values = (Map<String, Object>) get(propertyName);
                        values.put(indexStr, value);
                    }
                }
                return;
            }

            // List element indexed from 1
            if (path.contains(INDEX_START)) {
                String propertyName = path.substring(0, path.indexOf(INDEX_START));
                String indexStr = path.substring(path.indexOf(INDEX_START) + 1, path.indexOf(INDEX_END));

                try {
                    int index = Integer.parseInt(indexStr);
                    List<Object> valueList = (List<Object>) get(propertyName);
                    valueList.set(index - 1, value);
                } catch (NumberFormatException e) {
                    if (isIndexOperation(indexStr))
                        setIndexObject(indexStr, propertyName, value);
                    else
                        // List element indexed by attribute value
                        setByPropertyFilter(path, value);
                }
            }
        } else { // simple property setter
            ExtendedProperty property = (ExtendedProperty) getType().getProperty(path);
            property.setValue(this, value);
        }
    }

    protected void setByPropertyFilter(String path, Object value) {

        if (!DataObject.class.isAssignableFrom(value.getClass()))
            throw new IllegalArgumentException("value should be of type DataObject");

        String propertyName = path.substring(0, path.indexOf(INDEX_START));
        String indexStr = path.substring(path.indexOf(INDEX_START) + 1, path.indexOf(INDEX_END));

        Property property = getType().getProperty(propertyName);
        if (Map.class.isAssignableFrom(property.getType().getInstanceClass())
                && indexStr.indexOf(ATTR_DELIMITER) == -1) {
            Map<String, Object> values = (Map<String, Object>) get(property);
            values.put(indexStr, value);
            return;
        }
        if (!DataObject.class.isAssignableFrom(property.getType().getInstanceClass()))
            throw new IllegalArgumentException(
                    "The property '" + propertyName + "' refers to a non-DataObject collection");
        List<DataObject> values = (List<DataObject>) get(property);

        String attributeName = indexStr.substring(0, indexStr.indexOf(ATTR_DELIMITER));
        Property attributeProperty = getType().getProperty(attributeName);
        if (!attributeProperty.getType().isDataType())
            throw new IllegalArgumentException(
                    "Attribute '" + attributeName + "' cannot have a value of type DataObject");

        String attributeValue = indexStr.substring(indexStr.indexOf(ATTR_DELIMITER) + 1);
        attributeValue = attributeValue.replaceAll("^\"|\"$|^'|'$", "");

        // TODO: ensure the string representation is according to the SDO spec
        for (int i = 0; i < values.size(); i++) {
            DataObject dataObject = values.get(i);
            if (dataObject.get(attributeProperty).toString().equals(attributeValue)) {
                values.set(i, (DataObject) value);
                break;
            }
        }
    }

    @Override
    public DataObject getExistingDataObject(String path) {
        Object instanceObject = getPathObject(path);
        BusinessObject result = (BusinessObject) ((BusinessObject) getRootObject()).getObjectCreator()
                .getExistingDataObject(instanceObject);

        return result;
    }

    @Override
    public void set(int propertyIndex, Object value) {

        ExtendedProperty property = (ExtendedProperty) getType().getProperties().get(propertyIndex);
        set(property, value);
    }

    @Override
    public Object getDataObject(Property property) {
        if (this.instance == null)
            return null;

        Object value = ((ExtendedProperty) property).getValue(this);
        if (value == null)
            return null;

        Object dataObject = objectCreator.getExistingDataObject(value);
        if (dataObject == null) {
            if (((ExtendedProperty) property).isDataType()) {
                dataObject = value;
            } else {
                BusinessObject container = this;
                Property containmentProperty = property;
                if (!property.isContainment()) {
                    container = null;
                    containmentProperty = null;
                }
                if (logger.isDebugEnabled()) {
                    if (SimpleType.class.isAssignableFrom(property.getType().getClass())) {
                        logger.debug("TYPE: " + this.type.getName() + ", PRINTING: " + property.getName());
                    }
                }
                dataObject = objectCreator.createDataObject(value, property.getType(), container,
                        containmentProperty);
            }
        }

        return dataObject;
    }

    @Override
    public Object get(Property property) {
        if (this.instance == null)
            return null;

        if (property.isOpenContent()) {
            return getOpenPropertyValue(property.getName());
        }

        return ((ExtendedProperty) property).getValue(this);
        /*
        Object value = ((ExtendedProperty)property).getValue(this.instance);
        if(value == null)
           return null;
            
        Object dataObject = objectCreator.getExistingDataObject(value);
        if(dataObject == null) {
           if(((ExtendedProperty)property).isDataType()) {
        dataObject = value;
           } else {
        BusinessObject container = this;
        Property containmentProperty = property;
        if(!property.isContainment()) {
           container = null;
           containmentProperty = null;
        }
        if(logger.isDebugEnabled()) {
           if(SimpleType.class.isAssignableFrom(property.getType().getClass())) {
              logger.debug("TYPE: " + this.type.getName() + ", PRINTING: " + property.getName());
           }
        }
        dataObject = objectCreator.createDataObject(value, property.getType(), container, containmentProperty);
           }
        }
            
        return dataObject;
        */
    }

    @Override
    public DataObject getExistingDataObject(Property property) {
        return objectCreator.getExistingDataObject(((ExtendedProperty) property).getValue(this));
    }

    @Override
    public List<?> getList(Property property) {
        return (new DataObjectList(this, property)).list();
    }

    @Override
    public DataObject createDataObject(String propertyName) {
        Property property = getType().getProperty(propertyName);
        return createDataObject(property, property.getType());
    }

    @Override
    public DataObject createDataObject(int propertyIndex) {
        Property property = (Property) getType().getProperties().get(propertyIndex);
        return createDataObject(property, property.getType());
    }

    @Override
    public DataObject createDataObject(Property property) {
        return createDataObject(property, property.getType());
    }

    @Override
    public DataObject createDataObject(String propertyName, String namespaceURI, String typeName) {
        try {
            Class<?> javaClass = Class.forName(namespaceURI + typeName);
            DataAccessService das = ((BusinessObject) getRootObject()).getObjectCreator().getDAS();
            Type type = das.getType(javaClass);

            Property property = getType().getProperty(propertyName);
            return createDataObject(property, type);
        } catch (ClassNotFoundException e) {
            throw ClassUtil.wrapRun(e);
        }
    }

    @Override
    public DataObject createDataObject(int propertyIndex, String namespaceURI, String typeName) {

        try {
            Class<?> javaClass = Class.forName(namespaceURI + typeName);
            DataAccessService das = ((BusinessObject) getRootObject()).getObjectCreator().getDAS();
            Type type = das.getType(javaClass);

            Property property = (Property) getType().getProperties().get(propertyIndex);
            return createDataObject(property, type);
        } catch (ClassNotFoundException e) {
            throw ClassUtil.wrapRun(e);
        }
    }

    @Override
    public DataObject createDataObject(Property property, Type type) {
        if (instance == null)
            return null;

        if (property.getType().isDataType())
            throw new IllegalStateException("A DataObject needs to have properties");

        Object childInstance = ((ExtendedProperty) property).getValue(this);
        BusinessObject dataObject = ((BusinessObject) getRootObject()).getObjectCreator()
                .createDataObject(childInstance, (EntityType) type, this, property);

        return dataObject;
    }

    @Override
    public DataObject getContainer() {
        return container;
    }

    @Override
    public void setContainer(DataObject value) {
        this.container = value;
    }

    @Override
    public void setContainmentProperty(Property value) {
        this.containmentProperty = value;
    }

    @Override
    public Property getContainmentProperty() {
        return containmentProperty;
    }

    @Override
    public Type getType() {
        return type;
    }

    @Override
    public Property getInstanceProperty(String propertyName) {
        return getType().getProperty(propertyName);
    }

    @Override
    public Property getProperty(String propertyName) {
        return getInstanceProperty(propertyName);
    }

    @Override
    public DataObject getRootObject() {
        return (DataObject) get(PATH_DELIMITER);
    }

    @Override
    public ObjectCreator getObjectCreator() {
        return objectCreator;
    }

    @Override
    public BusinessObject load(Settings settings) {
        CallInfo callInfo = new CallInfo(this, null, null, null);
        settings.setAction(AggregateAction.LOAD);
        callInfo.setSettings(settings);

        ObjectCreator oc = new ObjectCreator(getObjectCreator().getDAS(),
                getObjectCreator().getPersistenceOrchestrator(), MapperDirection.DOMAINTODOMAIN);
        return oc.createTarget(callInfo);
    }

    @Override
    public DataObject read(Settings settings) {
        // invoke read only on the root object
        if (!isRoot())
            throw new RuntimeException("Read needs to be invoked on the root data object");

        CallInfo callInfo = new CallInfo(this, null, null, null);
        settings.setAction(AggregateAction.READ);
        callInfo.setSettings(settings);

        // Create an object creator for the target root
        ObjectCreator oc = new ObjectCreator(getObjectCreator().getDAS(),
                getObjectCreator().getPersistenceOrchestrator(), MapperDirection.EXTERNALTOEXTERNAL);
        oc.setReadOnly(true);

        callInfo.setOutputObjectCreator(oc);
        ReadOperation operation = new ReadOperation();
        callInfo.setOperation(operation);

        try {
            Object target = operation.createTarget(callInfo, null);
            callInfo.setOutput(target);

            // Needed to hold references to open property created objects
            if (oc.getCreationStrategy().needsObjectGraph()) {
                oc.setObjectGraph((BusinessObject) target);
            }

            operation.execute(callInfo);
        } catch (Exception e) {
            throw ClassUtil.wrapRun(e);
        }

        return (BusinessObject) callInfo.getOutput();
    }

    @Override
    public List<?> query(Settings settings) {
        if (settings.getView() == null)
            throw new RuntimeException("A view needs to be specified to use the query method");

        CallInfo callInfo = new CallInfo(this, null, null, null);
        AggregateAction savedAction = settings.getAction();
        settings.setAction(AggregateAction.READ);
        callInfo.setSettings(settings);

        MapperDirection direction = MapperDirection.EXTERNALTOEXTERNAL;
        if (settings.doBaseline()) // We need to return a domain object
            direction = direction.toDomain();

        ObjectCreator oc = new ObjectCreator(getObjectCreator().getDAS(),
                getObjectCreator().getPersistenceOrchestrator(), direction);
        oc.setReadOnly(true);

        callInfo.setOutputObjectCreator(oc);
        if (settings.isDenormalized()) {
            callInfo.setOperation(new DenormalizedQueryOperation());
        } else {
            callInfo.setOperation(new QueryOperation());
        }

        try {
            Class<?> narrowedClass = settings.getNarrowedClass();
            Type targetType = narrowedClass != null
                    ? getObjectCreator().getDAS().getType(settings.getNarrowedClass())
                    : settings.getEntityType();
            callInfo.setOutput(callInfo.getOperation().createTarget(callInfo, targetType));

            // Needed to hold references to open property created objects
            if (oc.getCreationStrategy().needsObjectGraph()) {
                oc.setObjectGraph((BusinessObject) callInfo.getOutput());
            }

            callInfo.getOperation().execute(callInfo);
        } catch (Exception e) {
            throw ClassUtil.wrapRun(e);
        }
        settings.setAction(savedAction);

        return (List<Object>) callInfo.getOperation().getResult();
    }

    @Override
    public List<BusinessObject> getList() {
        //if(listStore != null)
        //   return listStore;

        return (new DataObjectList(this)).list();
    }

    @Override
    public int hashCode() {
        int h = super.hashCode();

        if (getType().isDataType()) {
            h = 31 * h + instance.hashCode();
        } else {
            ExtendedProperty identifierProperty = (ExtendedProperty) ((EntityType) type).getIdentifierProperty();
            Object id = identifierProperty != null ? identifierProperty.getValue(this) : null;

            if (id != null) {
                h = 31 * h + id.hashCode();
            }
        }

        return h;
    }

    @Override
    /**
     * This can potentially be expensive depending on how much we check 
     * So we shall keep it simple and only check by either memory identity or by the
     * persistence identifier if it exists
     */
    public boolean equals(Object o) {

        if (this == o)
            return true;

        if (!AbstractBO.class.isAssignableFrom(o.getClass()))
            return false;

        AbstractBO other = (AbstractBO) o;
        if (getType() != other.getType())
            return false;

        if (getType().isDataType()) {
            return instance.equals(other.getInstance());
        }

        // Check if id is the same - then they are equal
        ExtendedProperty identifierProperty = (ExtendedProperty) ((EntityType) type).getIdentifierProperty();
        Object thisId = identifierProperty.getValue(this);
        Object otherId = identifierProperty.getValue(other);

        if (thisId != null) {
            if (thisId.equals(otherId)) {
                throw new RuntimeException("Cannot have two BusinessObjects with the same id.");
            }
        }

        return false;
    }

    @Override
    public Object getIdentifierValue() {
        if (instance == null)
            return null;

        if (!EntityType.class.isAssignableFrom(getType().getClass()))
            return null;

        ExtendedProperty idProperty = (ExtendedProperty) ((EntityType) getType()).getIdentifierProperty();
        if (idProperty == null)
            return null;

        return idProperty.getValue(this);
    }

    @Override
    public void invokePostLogic(Settings settings) {
        ((EntityType) this.getType()).invokePostLogic(settings, this.getInstance());
    }

    @Override
    public boolean isEntity() {
        if (EntityType.class.isAssignableFrom(getType().getClass()) && ((EntityType) getType()).isEntity())
            return true;

        return false;
    }

    @Override
    public boolean isDomainType() {
        return objectCreator.getTypeMapper().isDomain(getType().getInstanceClass());
    }

    @Override
    public Type getDomainType() {
        if (getType().isOpen()) {
            return ((EntityType) getType()).getDomainType();
        }
        return getObjectCreator().getDAS()
                .getType(getObjectCreator().getTypeMapper().toDomain(getType().getInstanceClass()));
    }

    @Override
    public Type getExternalType() {
        return getObjectCreator().getDAS()
                .getType(getObjectCreator().getTypeMapper().toExternal(getType().getInstanceClass()));
    }

    @Override
    public void linkBackPointer() {
        for (Property p : getType().getProperties()) {
            ExtendedProperty property = (ExtendedProperty) p;
            if (property.isContainment()) {
                if (property.getOpposite() != null)
                    property.linkBackPointer(this);
                BusinessObject value = (BusinessObject) getExistingDataObject(property);
                if (value != null)
                    value.linkBackPointer();
            }
        }
    }

    @Override
    public void unlinkBackPointer() {
        for (Property p : getType().getProperties()) {
            ExtendedProperty property = (ExtendedProperty) p;
            if (property.isContainment()) {
                if (property.getOpposite() != null)
                    property.unlinkBackPointer(this);
                BusinessObject value = (BusinessObject) getExistingDataObject(property);
                if (value != null)
                    value.unlinkBackPointer();
            }
        }
    }

    @Override
    public Object createReferenceCopy() {
        if (getInstance() == null)
            return null;

        Object copy = null;
        try {
            copy = getObjectCreator().createInstance(this.getType());
        } catch (Exception e) {
            throw ClassUtil.wrapRun(e);
        }

        for (String propertyName : ((EntityType) getType()).getInitializedProperties()) {
            Object propertyValue = ((ExtendedProperty) getProperty(propertyName)).getValue(this);
            ((ExtendedProperty) getProperty(propertyName)).setValue(copy, propertyValue);
        }

        return copy;
    }

    @Override
    public void createAggregate() {

        // Loop through the data object properties and if it is not a data type, then create a Data Object wrapper and recurse
        objectCreator.clearVisited();
        createWrapper(this);

        objectCreator.clearVisited();
    }

    protected void createWrapper(BusinessObject parent) {

        for (BusinessObject child : parent.getList()) {
            if (parent.getContainmentProperty().isContainment()) {
                child.setContainer(parent);
                child.setContainmentProperty(parent.getContainmentProperty());
            }
            createWrapper(child);
        }

        for (Property property : parent.getType().getProperties()) {
            if (!((ExtendedProperty) property).isDataType()) {
                Object propertyInstance = ((ExtendedProperty) property).getValue(parent);
                if (propertyInstance == null)
                    continue;

                Object target = objectCreator.getExistingDataObject(propertyInstance);
                if (target != null && !BusinessObject.class.isAssignableFrom(target.getClass()))
                    throw new IllegalStateException(
                            "Property refers to a DataObject, but the object is not a DataObject");

                BusinessObject child = null;
                if (target != null)
                    child = (BusinessObject) target;
                else {
                    BusinessObject container = parent;
                    Property containmentProperty = property;
                    if (!property.isContainment()) {
                        container = null;
                        containmentProperty = null;
                    }
                    child = objectCreator.createDataObject(propertyInstance, property.getType(), container,
                            containmentProperty);
                }

                if (child.isVisited())
                    continue;
                else
                    child.setVisited(true);

                if (!property.isContainment())
                    continue;

                createWrapper(child);
            }
        }
    }

    public BusinessObject getOpenPropertyValue(String name) {
        if (objectCreator.getObjectGraph() == null) {
            return null;
        }

        BusinessEdge<BusinessObject> edge = objectCreator.getObjectGraph().getOutEdge(this, name);
        return edge == null ? null : edge.getEnd();
    }
}