org.cloudgraph.store.lang.DefaultAssembler.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudgraph.store.lang.DefaultAssembler.java

Source

/**
 * Copyright 2017 TerraMeta Software, Inc.
 * 
 * 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 org.cloudgraph.store.lang;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudgraph.common.CloudGraphConstants;
import org.plasma.query.collector.SelectionCollector;
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.provider.common.PropertyPair;
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.profile.KeyType;

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

/**
 * Supports serial graph assembly. Common graph assembler functionality
 * resulting from initial re-factoring and addition of parallel assembly across
 * RDB and Cassandra services.
 * 
 * @author Scott Cinnamond
 * @since 0.6.2
 */
public abstract class DefaultAssembler extends AssemblerSupport {
    private static Log log = LogFactory.getLog(DefaultAssembler.class);
    public static final Set<Property> EMPTY_PROPERTY_SET = new HashSet<Property>();
    public static final List<DataObject> EMPTY_DATA_OBJECT_LIST = new ArrayList<DataObject>();
    protected PlasmaType rootType;
    protected PlasmaDataObject root;
    protected Timestamp snapshotDate;
    /**
     * stores a mapping of previously created objects using a PK based hash, such
     * that if the object is discovered again, no graph find is necessary
     */
    protected Map<Integer, PlasmaDataObject> dataObjectMap;
    protected Comparator<PropertyPair> nameComparator;

    public DefaultAssembler(PlasmaType rootType, SelectionCollector collector, StatementFactory statementFactory,
            StatementExecutor statementExecutor, Map<Integer, PlasmaDataObject> dataObjectMap,
            Timestamp snapshotDate) {
        super(collector, statementFactory, statementExecutor);
        this.rootType = rootType;
        this.dataObjectMap = dataObjectMap;
        this.snapshotDate = snapshotDate;

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

    /**
     * Assembles a data object of the given target type by first forming a query
     * using the given key/property pairs. If an existing data object is mapped
     * for the given key pairs, the existing data object is linked.
     * 
     * @param targetType
     *          the type for the data object to be assembled
     * @param source
     *          the source data object
     * @param sourceProperty
     *          the source property
     * @param childKeyPairs
     *          the key pairs for the data object to be assembled
     */
    protected abstract void assemble(PlasmaType targetType, PlasmaDataObject source, PlasmaProperty sourceProperty,
            List<PropertyPair> childKeyPairs, int level);

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

    protected DataGraph initRoot(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
        rootNode.setValue(CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP, snapshotDate);
        rootNode.getValueObject().put(CloudGraphConstants.GRAPH_NODE_THREAD_NAME, Thread.currentThread().getName());
        // set data properties
        for (PropertyPair pair : results) {
            if (pair.getProp().getType().isDataType()) {
                rootNode.setValue(pair.getProp().getName(), pair.getValue());
            }
        }

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

        return dataGraph;
    }

    public Map<PlasmaDataObject, List<PropertyPair>> collectResults(PlasmaType targetType, PlasmaDataObject source,
            PlasmaProperty sourceProperty, List<List<PropertyPair>> result) {
        // 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 = this.findDataObject(targetType, row);
            // if no existing data-object in graph
            if (target == null) {
                target = this.createDataObject(row, source, sourceProperty);
                resultMap.put(target, row); // add only new object for later
                // traversal
            } else {
                this.link(target, source, sourceProperty);
                continue; // don't map it for later traversal
                // 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.
            }
        }
        return resultMap;
    }

    /**
     * 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
     */
    protected PlasmaDataObject createDataObject(List<PropertyPair> row, PlasmaDataObject source,
            PlasmaProperty sourceProperty) {

        PlasmaDataObject target = (PlasmaDataObject) source.createDataObject(sourceProperty);
        CoreNode node = (CoreNode) target;
        if (log.isDebugEnabled())
            log.debug("create: " + source.getType().getName() + "." + sourceProperty.getName() + "->"
                    + target.getType().getName());

        // add concurrency fields
        node.setValue(CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP, snapshotDate);
        node.getValueObject().put(CloudGraphConstants.GRAPH_NODE_THREAD_NAME, Thread.currentThread().getName());

        // 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 the 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
     */
    protected 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 hash 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
     */
    protected int createHashKey(PlasmaType type, List<PropertyPair> row) {
        PropertyPair[] pairs = new PropertyPair[row.size()];
        row.toArray(pairs);
        Arrays.sort(pairs, this.nameComparator);
        int pkHash = type.getQualifiedName().hashCode();
        int fallBackHash = type.getQualifiedName().hashCode();

        int pks = 0;
        for (int i = 0; i < pairs.length; i++) {
            Object value = pairs[i].getValue();
            if (value == null) {
                log.warn("null voue for property, " + pairs[i].getProp().toString());
                continue;
            }
            if (pairs[i].getProp().isKey(KeyType.primary)) {
                pkHash = pkHash ^ value.hashCode();
                fallBackHash = fallBackHash ^ value.hashCode();
                pks++;
            } else {
                fallBackHash = fallBackHash ^ value.hashCode();
            }
        }
        if (pks > 0) {
            List<Property> pkProps = type.findProperties(KeyType.primary);
            if (pkProps.size() == pks)
                return pkHash;
        }

        return fallBackHash;
    }

    /**
     * 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
     */
    protected 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) {
                    log.warn("encountered existing opposite (" + existingOpposite.getType().getName()
                            + ") value found while creating link " + source.toString() + "."
                            + sourceProperty.getName() + "->" + target.toString() + " - no link created");
                    return;
                }
            }
            @SuppressWarnings("unchecked")
            List<DataObject> list = source.getList(sourceProperty);
            if (list == null)
                list = EMPTY_DATA_OBJECT_LIST;

            if (!list.contains(target)) {
                // check if any existing list members already have the opposite
                // property set
                for (DataObject existing : list) {
                    if (opposite != null && !opposite.isMany() && existing.isSet(opposite)) {
                        PlasmaDataObject existingOpposite = (PlasmaDataObject) existing.get(opposite);
                        if (existingOpposite != null) {
                            log.warn("encountered existing opposite (" + existingOpposite.getType().getName()
                                    + ") value found while creating link " + source.toString() + "."
                                    + sourceProperty.getName() + "->" + target.toString() + " - no link created");
                            return;
                        }
                    }
                }

                if (log.isDebugEnabled())
                    log.debug("adding target " + source.toString() + "." + sourceProperty.getName() + "->"
                            + target.toString());
                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.equals(target))
                if (log.isDebugEnabled())
                    log.debug("encountered existing (" + existing.getType().getName()
                            + ") value found while creating link " + source.toString() + "."
                            + sourceProperty.getName() + "->" + target.toString());
        }
    }

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

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

}