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

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