org.plasma.sdo.jdbc.service.GraphAssembler.java Source code

Java tutorial

Introduction

Here is the source code for org.plasma.sdo.jdbc.service.GraphAssembler.java

Source

/**
 *         PlasmaSDO License
 * 
 * This is a community release of PlasmaSDO, a dual-license 
 * Service Data Object (SDO) 2.1 implementation. 
 * This particular copy of the software is released under the 
 * version 2 of the GNU General Public License. PlasmaSDO was developed by 
 * TerraMeta Software, Inc.
 * 
 * Copyright (c) 2013, TerraMeta Software, Inc. All rights reserved.
 * 
 * General License information can be found below.
 * 
 * This distribution may include materials developed by third
 * parties. For license and attribution notices for these
 * materials, please refer to the documentation that accompanies
 * this distribution (see the "Licenses for Third-Party Components"
 * appendix) or view the online documentation at 
 * <http://plasma-sdo.org/licenses/>.
 *  
 */
package org.plasma.sdo.jdbc.service;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.plasma.query.collector.PropertySelectionCollector;
import org.plasma.query.model.Where;
import org.plasma.sdo.PlasmaDataGraph;
import org.plasma.sdo.PlasmaDataObject;
import org.plasma.sdo.PlasmaProperty;
import org.plasma.sdo.PlasmaType;
import org.plasma.sdo.access.DataAccessException;
import org.plasma.sdo.access.DataGraphAssembler;
import org.plasma.sdo.access.provider.common.PropertyPair;
import org.plasma.sdo.access.provider.jdbc.AliasMap;
import org.plasma.sdo.core.CoreConstants;
import org.plasma.sdo.core.CoreNode;
import org.plasma.sdo.core.TraversalDirection;
import org.plasma.sdo.helper.PlasmaDataFactory;
import org.plasma.sdo.jdbc.filter.FilterAssembler;
import org.plasma.sdo.profile.KeyType;

import commonj.sdo.DataGraph;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.Type;

public class GraphAssembler extends JDBCSupport implements DataGraphAssembler {

    private static Log log = LogFactory.getLog(GraphAssembler.class);
    private PlasmaType rootType;
    private PlasmaDataObject root;
    private Map<Type, List<String>> propertyMap;
    private Map<commonj.sdo.Property, Where> predicateMap;
    private Timestamp snapshotDate;
    private Connection con;
    private RDBDataConverter converter;
    private Map<Integer, PlasmaDataObject> dataObjectMap = new HashMap<Integer, PlasmaDataObject>();
    private Comparator<PropertyPair> nameComparator;

    @SuppressWarnings("unused")
    private GraphAssembler() {
    }

    public GraphAssembler(PlasmaType rootType, PropertySelectionCollector collector, Timestamp snapshotDate,
            Connection con) {
        this.rootType = rootType;
        this.propertyMap = collector.getResult();
        this.predicateMap = collector.getPredicateMap();
        this.snapshotDate = snapshotDate;
        this.con = con;
        this.converter = RDBDataConverter.INSTANCE;

        this.nameComparator = new Comparator<PropertyPair>() {
            public int compare(PropertyPair o1, PropertyPair o2) {
                return o1.getProp().getName().compareTo(o2.getProp().getName());
            }
        };
    }

    /**
     * Initiates the assembly of a data graph based on the 
     * given results list. 
     * @param results the results list
     * 
     * @see DataGraphAssembler.getDataGraph()
     */
    public void assemble(List<PropertyPair> results) {

        DataGraph dataGraph = PlasmaDataFactory.INSTANCE.createDataGraph();
        this.root = (PlasmaDataObject) dataGraph.createRootObject(this.rootType);
        if (log.isDebugEnabled())
            log.debug("assembling root: " + this.root.getType().getName());

        CoreNode rootNode = (CoreNode) this.root;
        // add concurrency fields
        if (snapshotDate != null)
            rootNode.setValue(CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP, snapshotDate);
        // set data properties
        for (PropertyPair pair : results) {
            if (pair.getProp().getType().isDataType()) {
                rootNode.setValue(pair.getProp().getName(), pair.getValue());
            }
        }

        // singular reference props
        for (PropertyPair pair : results) {
            if (pair.getProp().isMany() || pair.getProp().getType().isDataType())
                continue;
            List<PropertyPair> childKeyProps = new ArrayList<PropertyPair>();
            List<Property> childPkProps = ((PlasmaType) pair.getProp().getType()).findProperties(KeyType.primary);
            if (childPkProps.size() == 1) {
                childKeyProps.add(new PropertyPair((PlasmaProperty) childPkProps.get(0), pair.getValue()));
            } else
                throwPriKeyError(childPkProps, pair.getProp().getType(), pair.getProp());
            assemble((PlasmaType) pair.getProp().getType(), (PlasmaDataObject) this.root, pair.getProp(),
                    childKeyProps);

        }

        // multi reference props (not found in results)
        List<String> names = this.propertyMap.get(this.rootType);
        for (String name : names) {
            PlasmaProperty prop = (PlasmaProperty) rootType.getProperty(name);
            if (prop.isMany() && !prop.getType().isDataType()) {
                PlasmaProperty opposite = (PlasmaProperty) prop.getOpposite();
                if (opposite == null)
                    throw new DataAccessException("no opposite property found"
                            + " - cannot map from many property, " + prop.getType() + "." + prop.getName());
                List<PropertyPair> childKeyProps = new ArrayList<PropertyPair>();
                List<Property> rootPkProps = ((PlasmaType) root.getType()).findProperties(KeyType.primary);
                if (rootPkProps.size() == 1) {
                    childKeyProps.add(new PropertyPair(opposite, root.get(rootPkProps.get(0))));
                } else
                    throwPriKeyError(rootPkProps, root.getType(), prop);
                assemble((PlasmaType) prop.getType(), (PlasmaDataObject) this.root, prop, childKeyProps);
            }
        }
    }

    /**
     * 
     * @param targetType
     * @param source
     * @param sourceProperty
     * @param childKeyPairs
     */
    private void assemble(PlasmaType targetType, PlasmaDataObject source, PlasmaProperty sourceProperty,
            List<PropertyPair> childKeyPairs) {
        List<String> names = this.propertyMap.get(targetType);
        if (log.isDebugEnabled())
            log.debug("assemble: " + source.getType().getName() + "." + sourceProperty.getName() + ": " + names);

        List<List<PropertyPair>> result = null;
        Where where = this.predicateMap.get(sourceProperty);
        if (where == null) {
            try {
                StringBuilder query = createSelect(targetType, names, childKeyPairs);
                result = fetch(targetType, query, this.con);
            } catch (SQLException e) {
                throw new DataAccessException(e);
            }
        } else {
            AliasMap aliasMap = new AliasMap(targetType);
            FilterAssembler filterAssembler = new FilterAssembler(where, targetType, aliasMap);
            try {
                List<Object> params = new ArrayList<Object>();
                StringBuilder query = createSelect(targetType, names, childKeyPairs, filterAssembler, params,
                        aliasMap);
                Object[] paramArray = new Object[params.size()];
                params.toArray(paramArray);

                result = fetch(targetType, query, paramArray, this.con);
            } catch (SQLException e) {
                throw new DataAccessException(e);
            }
        }

        if (log.isDebugEnabled())
            log.debug("results: " + result.size());

        // first create (or link existing) data objects 
        // "filling out" the containment hierarchy at this traversal level
        // BEFORE recursing, as we may "cancel" out an object
        // at the current level if it is first encountered
        // within the recursion.
        Map<PlasmaDataObject, List<PropertyPair>> resultMap = new HashMap<PlasmaDataObject, List<PropertyPair>>();
        for (List<PropertyPair> row : result) {

            PlasmaDataObject target = findDataObject(targetType, row);
            // if no existing data-object in graph
            if (target == null) {
                target = createDataObject(row, source, sourceProperty);
                resultMap.put(target, row);
            } else {
                link(target, source, sourceProperty);
                continue;
                // Assume we traverse no farther given no traversal
                // direction or containment info. We only know that we 
                // encountered an existing node. Need more path specific 
                // info including containment and traversal direction to construct
                // a directed graph here. 
                // Since the current selection collector maps any and all
                // properties selected to a type, for each type/data-object
                // we will, at this point, have gotten all the properties we expect anyway.
                // So we create a link from the source to the existing DO, but
                // traverse no further. 
            }
        }

        // now traverse
        Iterator<PlasmaDataObject> iter = resultMap.keySet().iterator();
        while (iter.hasNext()) {
            PlasmaDataObject target = iter.next();
            List<PropertyPair> row = resultMap.get(target);
            // traverse singular results props
            for (PropertyPair pair : row) {
                if (pair.getProp().isMany() || pair.getProp().getType().isDataType())
                    continue; // only singular reference props
                List<PropertyPair> nextKeyPairs = new ArrayList<PropertyPair>();
                List<Property> nextKeyProps = ((PlasmaType) pair.getProp().getType())
                        .findProperties(KeyType.primary);

                // FIXME: need UML profile link to target PK props 
                // where there are multiple PKs !!
                if (nextKeyProps.size() == 1) {
                    nextKeyPairs.add(new PropertyPair((PlasmaProperty) nextKeyProps.get(0), pair.getValue()));
                } else
                    throwPriKeyError(nextKeyProps, pair.getProp().getType(), pair.getProp());

                if (log.isDebugEnabled())
                    log.debug("traverse: (" + pair.getProp().isMany() + ") " + pair.getProp().getType().getName()
                            + "." + pair.getProp().getName());
                assemble((PlasmaType) pair.getProp().getType(), target, pair.getProp(), nextKeyPairs);
            }

            // traverse multi props based, not on the results
            // row, but on keys within this data object
            for (String name : names) {
                PlasmaProperty prop = (PlasmaProperty) targetType.getProperty(name);
                if (!prop.isMany() || prop.getType().isDataType())
                    continue; // only many reference props

                PlasmaProperty opposite = (PlasmaProperty) prop.getOpposite();
                if (opposite == null)
                    throw new DataAccessException("no opposite property found"
                            + " - cannot map from many property, " + prop.getType() + "." + prop.getName());
                List<PropertyPair> childKeyProps = new ArrayList<PropertyPair>();
                List<Property> nextKeyProps = ((PlasmaType) targetType).findProperties(KeyType.primary);
                if (nextKeyProps.size() == 1) {
                    childKeyProps.add(new PropertyPair(opposite, target.get(nextKeyProps.get(0))));
                } else
                    throwPriKeyError(nextKeyProps, targetType, prop);
                if (log.isDebugEnabled())
                    log.debug("traverse: (" + prop.isMany() + ") " + target.getType().getName() + "."
                            + prop.getName());
                assemble((PlasmaType) prop.getType(), target, prop, childKeyProps);
            }
        }
    }

    /**
     * Creates a new data object contained by the given source
     * data object and source property.
     * @param row the results row
     * @param source the source data object
     * @param sourceProperty the source containment property
     * @return the new data object
     */
    private PlasmaDataObject createDataObject(List<PropertyPair> row, PlasmaDataObject source,
            PlasmaProperty sourceProperty) {
        PlasmaDataObject target = (PlasmaDataObject) source.createDataObject(sourceProperty);
        if (log.isDebugEnabled())
            log.debug("create: " + source.getType().getName() + "." + sourceProperty.getName() + "->"
                    + target.getType().getName());
        CoreNode node = (CoreNode) target;

        // add concurrency fields
        if (snapshotDate != null)
            node.setValue(CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP, snapshotDate);

        // set data properties bypassing SDO "setter" API
        // to avoid triggering read-only property error 
        for (PropertyPair pair : row) {
            if (pair.getProp().getType().isDataType()) {
                if (log.isDebugEnabled())
                    log.debug("set: (" + pair.getValue() + ") " + pair.getProp().getContainingType().getName() + "."
                            + pair.getProp().getName());
                node.setValue(pair.getProp().getName(), pair.getValue());
            }
        }

        // map it
        int key = createHashKey((PlasmaType) target.getType(), row);
        if (log.isDebugEnabled())
            log.debug("mapping " + key + "->" + target);
        this.dataObjectMap.put(key, target);

        return target;
    }

    /**
     * Finds and returns an existing data object based on hte
     * given results row which is part
     * if this assembly unit, or returns null if not exists
     * @param type the target type
     * @param row the results row
     * @return the data object
     */
    private PlasmaDataObject findDataObject(PlasmaType type, List<PropertyPair> row) {
        int key = createHashKey(type, row);
        PlasmaDataObject result = this.dataObjectMap.get(key);
        if (log.isDebugEnabled()) {
            if (result != null)
                log.debug("found existing mapping " + key + "->" + result);
            else
                log.debug("found no existing mapping for key: " + key);
        }
        return result;
    }

    /**
     * Creates a unique mappable key using the qualified type name
     * and all key property values from the given row.
     * @param type the type
     * @param row the data values
     * @return the key
     */
    private int createHashKey(PlasmaType type, List<PropertyPair> row) {
        PropertyPair[] pairs = new PropertyPair[row.size()];
        row.toArray(pairs);
        Arrays.sort(pairs, this.nameComparator);
        int result = type.getQualifiedName().hashCode();

        int pks = 0;
        for (int i = 0; i < pairs.length; i++) {
            if (pairs[i].getProp().isKey(KeyType.primary)) {
                Object value = pairs[i].getValue();
                result = result ^ value.hashCode();
                pks++;
            }
        }
        if (pks == 0)
            throw new IllegalStateException(
                    "cannot create hash key - no primary keys found for type, " + type.toString());
        return result;
    }

    /**
     * Creates a directed (link) between the given
     * source and target data objects. The reference is
     * created as a containment reference only if the given target
     * has no container.
     * @param target the data object which is the target 
     * @param source the source data object
     * @param sourceProperty the source property
     * 
     * @see TraversalDirection
     */
    private void link(PlasmaDataObject target, PlasmaDataObject source, PlasmaProperty sourceProperty) {
        if (log.isDebugEnabled())
            log.debug("linking source (" + source.getUUIDAsString() + ") " + source.getType().getURI() + "#"
                    + source.getType().getName() + "." + sourceProperty.getName() + "->(" + target.getUUIDAsString()
                    + ") " + target.getType().getURI() + "#" + target.getType().getName());

        if (sourceProperty.isMany()) {

            PlasmaProperty opposite = (PlasmaProperty) sourceProperty.getOpposite();
            if (opposite != null && !opposite.isMany() && target.isSet(opposite)) {
                PlasmaDataObject existingOpposite = (PlasmaDataObject) target.get(opposite);
                if (existingOpposite != null) {
                    if (log.isDebugEnabled())
                        log.debug("encountered existing opposite (" + existingOpposite.getType().getName()
                                + ") value found while creating link (" + source.getUUIDAsString() + ") "
                                + source.getType().getURI() + "#" + source.getType().getName() + "."
                                + sourceProperty.getName() + "->(" + target.getUUIDAsString() + ") "
                                + target.getType().getURI() + "#" + target.getType().getName()
                                + " - no link created");
                    return;
                }
            }
            @SuppressWarnings("unchecked")
            List<DataObject> list = source.getList(sourceProperty);
            if (list == null)
                list = new ArrayList<DataObject>();
            if (log.isDebugEnabled()) {
                for (DataObject existingObject : list) {
                    log.debug("existing: (" + ((org.plasma.sdo.PlasmaNode) existingObject).getUUIDAsString() + ") "
                            + existingObject.getType().getURI() + "#" + existingObject.getType().getName());
                }
            }
            if (!list.contains(target)) {
                if (log.isDebugEnabled())
                    log.debug("adding target  (" + source.getUUIDAsString() + ") " + source.getType().getURI() + "#"
                            + source.getType().getName() + "." + sourceProperty.getName() + "->("
                            + target.getUUIDAsString() + ") " + target.getType().getURI() + "#"
                            + target.getType().getName());
                if (target.getContainer() == null) {
                    target.setContainer(source);
                    target.setContainmentProperty(sourceProperty);
                }
                list.add(target);
                source.setList(sourceProperty, list);
                // FIXME: replaces existing list according to SDO spec (memory churn)
                // store some temp instance-property list on DO and only set using SDO
                // API on completion of graph. 
            }
        } else {
            // Selection map keys are paths from the root entity and
            // elements in the path are often repeated. Expect repeated 
            // events for repeated path elements, which
            // may be useful for some implementations, but not this one. So
            // we screen these out here. 
            PlasmaDataObject existing = (PlasmaDataObject) source.get(sourceProperty);
            if (existing == null) {
                source.set(sourceProperty, target);
                // While the SDO spec seems to indicate (see 3.1.6 Containment) that
                // a Type may have only 1 reference property which a containment 
                // property, this seems too inflexible given the almost infinite
                // number of ways a graph could be constructed. We therefore allow any reference
                // property to be a containment property, and let the graph assembly
                // order determine which properties are containment properties for a particular
                // graph result. The SDO spec is crystal clear that every Data Object
                // other than the root, must have one-and-only-one container. We set the container
                // here as well as the specific reference property that currently is
                // the containment property, based on graph traversal order. Note it would be
                // possible to specify exactly which property is containment in a
                // query specification. We set no indication of containment on the 
                // (source) container object because all reference properties are 
                // potentially containment properties.  
                if (target.getContainer() == null) {
                    target.setContainer(source);
                    target.setContainmentProperty(sourceProperty);
                }
            } else if (!existing.getUUIDAsString().equals(target.getUUIDAsString()))
                if (log.isDebugEnabled())
                    log.debug("encountered existing (" + existing.getType().getName()
                            + ") value found while creating link (" + source.getUUIDAsString() + ") "
                            + source.getType().getURI() + "#" + source.getType().getName() + "."
                            + sourceProperty.getName() + "->(" + target.getUUIDAsString() + ") "
                            + target.getType().getURI() + "#" + target.getType().getName());
        }
    }

    private void throwPriKeyError(List<Property> rootPkProps, Type type, Property prop) {
        if (prop.isMany())
            if (rootPkProps.size() == 0)
                throw new DataAccessException("no pri-keys found for " + type.getURI() + "#" + type.getName()
                        + " - cannot map from many property, " + prop.getType() + "." + prop.getName());
            else
                throw new DataAccessException("multiple pri-keys found for " + type.getURI() + "#" + type.getName()
                        + " - cannot map from many property, " + prop.getType() + "." + prop.getName());
        else if (rootPkProps.size() == 0)
            throw new DataAccessException("no pri-keys found for " + type.getURI() + "#" + type.getName()
                    + " - cannot map from singular property, " + prop.getType() + "." + prop.getName());
        else
            throw new DataAccessException("multiple pri-keys found for " + type.getURI() + "#" + type.getName()
                    + " - cannot map from singular property, " + prop.getType() + "." + prop.getName());
    }

    public PlasmaDataGraph getDataGraph() {
        return (PlasmaDataGraph) this.root.getDataGraph();
    }

    public void clear() {
        this.root = null;
        this.dataObjectMap.clear();
    }

}