
org.cloudgraph.store.lang.DefaultAssembler Maven / Gradle / Ivy
/**
* 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 EMPTY_PROPERTY_SET = new HashSet();
public static final List EMPTY_DATA_OBJECT_LIST = new ArrayList();
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 dataObjectMap;
protected Comparator nameComparator;
public DefaultAssembler(PlasmaType rootType, SelectionCollector collector,
StatementFactory statementFactory, StatementExecutor statementExecutor,
Map dataObjectMap, Timestamp snapshotDate) {
super(collector, statementFactory, statementExecutor);
this.rootType = rootType;
this.dataObjectMap = dataObjectMap;
this.snapshotDate = snapshotDate;
this.nameComparator = new Comparator() {
@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 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 results);
protected DataGraph initRoot(List 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> collectResults(PlasmaType targetType,
PlasmaDataObject source, PlasmaProperty sourceProperty, List> 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> resultMap = new HashMap>();
for (List 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 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 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 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 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 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();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy