org.cloudgraph.hbase.mutation.DefaultMutation Maven / Gradle / Ivy
/**
* CloudGraph Community Edition (CE) License
*
* This is a community release of CloudGraph, a dual-license suite of
* Service Data Object (SDO) 2.1 services designed for relational and
* big-table style "cloud" databases, such as HBase and others.
* This particular copy of the software is released under the
* version 2 of the GNU General Public License. CloudGraph 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
* .
*/
package org.cloudgraph.hbase.mutation;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import org.cloudgraph.config.CloudGraphConfig;
import org.cloudgraph.config.DataGraphConfig;
import org.cloudgraph.config.TableConfig;
import org.cloudgraph.config.UserDefinedRowKeyFieldConfig;
import org.cloudgraph.hbase.io.DistributedWriter;
import org.cloudgraph.hbase.io.RowWriter;
import org.cloudgraph.hbase.io.TableWriter;
import org.cloudgraph.hbase.service.HBaseDataConverter;
import org.cloudgraph.hbase.service.ServiceContext;
import org.cloudgraph.state.GraphState.Edge;
import org.cloudgraph.store.service.GraphServiceException;
import org.plasma.sdo.DataFlavor;
import org.plasma.sdo.PlasmaDataObject;
import org.plasma.sdo.PlasmaEdge;
import org.plasma.sdo.PlasmaNode;
import org.plasma.sdo.PlasmaProperty;
import org.plasma.sdo.PlasmaType;
import org.plasma.sdo.access.DataAccessException;
import org.plasma.sdo.access.RequiredPropertyException;
import org.plasma.sdo.access.provider.common.PropertyPair;
import org.plasma.sdo.core.CoreConstants;
import org.plasma.sdo.core.CoreDataObject;
import org.plasma.sdo.core.NullValue;
import org.plasma.sdo.core.SnapshotMap;
import org.plasma.sdo.helper.DataConverter;
import org.plasma.sdo.profile.ConcurrencyType;
import org.plasma.sdo.profile.ConcurrentDataFlavor;
import org.plasma.sdo.profile.KeyType;
import commonj.sdo.ChangeSummary;
import commonj.sdo.DataGraph;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
abstract class DefaultMutation {
private static Log log = LogFactory.getLog(DefaultMutation.class);
private static HashMap EMPTY_EDGE_MAP = new HashMap();
protected SnapshotMap snapshotMap;
protected String username;
protected ServiceContext context;
public DefaultMutation(ServiceContext context, SnapshotMap snapshotMap, String username) {
super();
this.snapshotMap = snapshotMap;
this.username = username;
this.context = context;
}
protected HashMap getOldEdgeMap(Object oldValue,
Property property)
{
HashMap result = EMPTY_EDGE_MAP;
if (!(oldValue instanceof NullValue)) {
if (oldValue instanceof List) {
@SuppressWarnings("unchecked")
List oldValueList = (List)oldValue;
result = new HashMap(oldValueList.size());
for (DataObject dataObject : oldValueList)
result.put(((PlasmaDataObject)dataObject).getUUIDAsString(),
dataObject);
}
else {
PlasmaDataObject oldOpposite = (PlasmaDataObject)oldValue;
result = new HashMap(1);
result.put(oldOpposite.getUUIDAsString(),
oldOpposite);
}
}
return result;
}
protected byte[] createEdgeValueBytes(PlasmaNode dataNode, List edges, RowWriter rowWriter)
throws IOException {
String valueStr = rowWriter.getGraphState().marshalEdges(dataNode, edges);
return Bytes.toBytes(valueStr);
}
protected void addRowKeys(
PlasmaDataObject dataObject,
PlasmaNode dataNode,
Property property,
List edges,
DistributedWriter graphWriter,
TableWriter tableWriter,
RowWriter rowWriter) throws IOException
{
PlasmaType dataObjectType = (PlasmaType)dataObject.getType();
boolean thisTypeBound = CloudGraphConfig.getInstance().findTable(
dataObjectType.getQualifiedName()) != null;
for (PlasmaEdge edge : edges) {
PlasmaDataObject opposite = edge.getOpposite(dataNode).getDataObject();
//edge.getDirection()
PlasmaType oppositeType = (PlasmaType)opposite.getType();
boolean oppositeTypeBound = CloudGraphConfig.getInstance().findTable(
oppositeType.getQualifiedName()) != null;
RowWriter oppositeRowWriter = graphWriter.findRowWriter(opposite);
if (oppositeRowWriter == null) {
oppositeRowWriter = graphWriter.createRowWriter(opposite);
}
TableWriter oppositeTableWriter = oppositeRowWriter.getTableWriter();
// maps opposite UUID to its row key
// in the state for this row
if (oppositeTypeBound) {
rowWriter.getGraphState().addRowKey(opposite,
oppositeTableWriter.getTableConfig(),
oppositeRowWriter.getRowKey());
if (log.isDebugEnabled())
log.debug("adding row key for opposite data object ("+opposite+") to this data-object state, "
+ dataObject);
}
else {
if (log.isDebugEnabled())
log.debug("ignoring row key for unbound opposite data object, "
+ opposite.toString());
}
// Maps this DO uuid to current row key in opposite row
// If this data object is not "bound" to a
// table, disregard as it will have no opposite row
// but will be contained within this row
Property oppositeProperty = property.getOpposite();
if (oppositeProperty != null && thisTypeBound) {
ChangeSummary changeSummary = dataObject.getChangeSummary();
if (changeSummary.isCreated(opposite) || changeSummary.isModified(opposite)) {
oppositeRowWriter.getGraphState().addRowKey(dataObject,
tableWriter.getTableConfig(),
rowWriter.getRowKey());
if (log.isDebugEnabled())
log.debug("adding row key for this data object ("+dataObject+") to opposite data-object state, "
+ opposite + " for property, " + property);
}
// Otherwise we are just adding to the state for a DO who's ref property
// will never get updated
// And is this call even necessary as if the opposite's property IS modified
// it will get updated above.
}
else {
if (log.isDebugEnabled()) {
if (oppositeProperty == null)
log.debug("ignoring row key for opposite data-object state for property, "
+ property.toString() + " as no opposite property exists");
else
log.debug("ignoring row key for opposite data-object state for property, "
+ property.toString() + " as this type is not bound");
}
}
}
}
/**
* Screens out edges that are not owned by the given data node, but
* still may be present.
*/
protected List findUpdateEdges(
PlasmaNode dataNode,
Property property,
List edges,
DistributedWriter graphWriter,
RowWriter rowWriter) throws IOException
{
List result = new ArrayList();
for (PlasmaEdge edge : edges) {
PlasmaDataObject opposite = edge.getOpposite(dataNode).getDataObject();
PlasmaType oppositeType = (PlasmaType)opposite.getType();
boolean oppositeTypeBound = CloudGraphConfig.getInstance().findTable(
oppositeType.getQualifiedName()) != null;
RowWriter oppositeRowWriter = graphWriter.findRowWriter(opposite);
if (oppositeRowWriter == null) {
oppositeRowWriter = graphWriter.createRowWriter(opposite);
}
// If the opposite not bound to a table and
// it is already linked within another row,
// don't write the edge. This graph does not
// own it.
if (oppositeTypeBound || oppositeRowWriter.equals(rowWriter)) {
result.add(edge);
}
else {
if (log.isDebugEnabled())
log.debug("ignoring non-owned edge for property, "
+ property.toString());
}
}
return result;
}
protected void updateCell(RowWriter rowContext, PlasmaDataObject dataObject, Property property,
byte[] value) throws IOException {
PlasmaProperty prop = (PlasmaProperty) property;
byte[] qualifier = rowContext.getColumnKeyFactory().createColumnKey(dataObject, prop);
TableConfig table = rowContext.getTableWriter().getTableConfig();
if (log.isDebugEnabled()) {
if (prop.getType().isDataType()) {
Object objectValue = HBaseDataConverter.INSTANCE.fromBytes(prop, value);
log.debug("setting " + property + " / " + table.getName()
+ "." + new String(qualifier, table.getCharset()) + " = '"
+ String.valueOf(objectValue)
+ "'");
}
else {
log.debug("setting " + property + " / " + table.getName()
+ "." + new String(qualifier, table.getCharset()) + " = '"
+ new String(value, table.getCharset())
+ "'");
}
}
rowContext.getRow().add(table.getDataColumnFamilyNameBytes(), qualifier, value);
}
/**
* Removes all versions of the associated cell.
* @param rowContext row writer
* @param dataObject the data object
* @param property
* @throws IOException
*/
protected void deleteCell(RowWriter rowContext, PlasmaDataObject dataObject, Property property) throws IOException {
PlasmaProperty prop = (PlasmaProperty) property;
byte[] qualifier = rowContext.getColumnKeyFactory().createColumnKey(dataObject, prop);
TableConfig table = rowContext.getTableWriter().getTableConfig();
if (log.isDebugEnabled())
log.debug("deleting " + property + " / " + table.getName()
+ "." + new String(qualifier, table.getCharset()));
rowContext.getRowDelete().addColumns(table.getDataColumnFamilyNameBytes(), qualifier);
}
protected void setOrigination(PlasmaDataObject dataObject, PlasmaType type) {
// FIXME - could be a reference to a user
Property originationUserProperty = type.findProperty(ConcurrencyType.origination, ConcurrentDataFlavor.user);
if (originationUserProperty != null) {
if (!originationUserProperty.isReadOnly())
dataObject.set(originationUserProperty, username);
else
((CoreDataObject) dataObject).setValue(originationUserProperty.getName(), username); // FIXME:
// bypassing
// readonly
// modification
// detection
} else if (log.isDebugEnabled())
log.debug(
"could not find origination (username) property for type, " + type.getURI() + "#" + type.getName());
Property originationTimestampProperty = type.findProperty(ConcurrencyType.origination,
ConcurrentDataFlavor.time);
if (originationTimestampProperty != null) {
Date dateSnapshot = new Date(this.snapshotMap.getSnapshotDate().getTime());
Object snapshot = DataConverter.INSTANCE.convert(originationTimestampProperty.getType(), dateSnapshot);
if (!originationTimestampProperty.isReadOnly())
dataObject.set(originationTimestampProperty, snapshot);
else
((CoreDataObject) dataObject).setValue(originationTimestampProperty.getName(), snapshot); // FIXME:
// bypassing
// readonly
// modification
// detection
} else if (log.isDebugEnabled())
log.debug("could not find origination date property for type, " + type.getURI() + "#" + type.getName());
}
protected void updateKeys(PlasmaDataObject dataObject, PlasmaType type, RowWriter rowWriter) throws IOException {
UUID uuid = ((CoreDataObject) dataObject).getUUID();
if (uuid == null)
throw new GraphServiceException("expected UUID for created entity '" + type.getName() + "'");
List pkList = type.findProperties(KeyType.primary);
if (pkList == null || pkList.size() == 0)
return; // don't care for NOSQL services
for (Property pkp : pkList) {
if (!pkp.getType().isDataType())
continue; // noop for non-data pks
PlasmaProperty targetPriKeyProperty = (PlasmaProperty) pkp;
Object pk = dataObject.get(targetPriKeyProperty);
if (pk == null) {
DataFlavor dataFlavor = targetPriKeyProperty.getDataFlavor();
switch (dataFlavor) {
case integral:
if (log.isDebugEnabled()) {
log.debug("getting seq-num for " + type.getName());
}
pk = rowWriter.getGraphState().findSequence(dataObject);
if (pk == null)
pk = rowWriter.getGraphState().addSequence(dataObject);
pk = DataConverter.INSTANCE.convert(targetPriKeyProperty.getType(), pk);
byte[] pkBytes = HBaseDataConverter.INSTANCE.toBytes(targetPriKeyProperty, pk);
this.updateCell(rowWriter, dataObject, targetPriKeyProperty, pkBytes);
((CoreDataObject) dataObject).setValue(targetPriKeyProperty.getName(), pk); // FIXME:
// bypassing
// modification
// detection
// on
// pri-key
break;
default:
throw new DataAccessException("found null primary key property '" + targetPriKeyProperty.getName()
+ "' for type, " + type.getURI() + "#" + type.getName());
}
} else {
byte[] pkBytes = HBaseDataConverter.INSTANCE.toBytes(targetPriKeyProperty, pk);
this.updateCell(rowWriter, dataObject, targetPriKeyProperty, pkBytes);
}
if (log.isDebugEnabled()) {
log.debug("mapping UUID '" + uuid + "' to pk (" + String.valueOf(pk) + ")");
}
snapshotMap.put(uuid, new PropertyPair(targetPriKeyProperty, pk));
}
}
protected void updateOrigination(PlasmaDataObject dataObject, PlasmaType type, RowWriter rowContext)
throws IOException {
// FIXME - could be a reference to a user
Property originationUserProperty = type.findProperty(ConcurrencyType.origination, ConcurrentDataFlavor.user);
if (originationUserProperty != null) {
if (!originationUserProperty.isReadOnly())
dataObject.set(originationUserProperty, username);
else
((CoreDataObject) dataObject).setValue(originationUserProperty.getName(), username); // FIXME:
// bypassing
// readonly
// modification
// detection
} else if (log.isDebugEnabled())
log.debug(
"could not find origination (username) property for type, " + type.getURI() + "#" + type.getName());
Property originationTimestampProperty = type.findProperty(ConcurrencyType.origination,
ConcurrentDataFlavor.time);
if (originationTimestampProperty != null) {
Date dateSnapshot = new Date(this.snapshotMap.getSnapshotDate().getTime());
Object snapshot = DataConverter.INSTANCE.convert(originationTimestampProperty.getType(), dateSnapshot);
byte[] bytes = HBaseDataConverter.INSTANCE.toBytes(originationTimestampProperty, snapshot);
this.updateCell(rowContext, dataObject, originationTimestampProperty, bytes);
} else if (log.isDebugEnabled())
log.debug("could not find origination date property for type, " + type + "#" + type.getName());
}
protected void setOptimistic(PlasmaDataObject dataObject, PlasmaType type, Timestamp snapshotDate) {
PlasmaProperty concurrencyUserProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.user);
if (concurrencyUserProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency (username) property for type, " + type.getURI() + "#"
+ type.getName());
} else {
if (!concurrencyUserProperty.isReadOnly())
dataObject.set(concurrencyUserProperty, username);
else
((CoreDataObject) dataObject).setValue(concurrencyUserProperty.getName(), username);
}
PlasmaProperty concurrencyTimestampProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.time);
if (concurrencyTimestampProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency timestamp property for type, " + type.getURI() + "#"
+ type.getName());
} else {
Date dateSnapshot = new Date(this.snapshotMap.getSnapshotDate().getTime());
Object snapshot = DataConverter.INSTANCE.convert(concurrencyTimestampProperty.getType(), dateSnapshot);
if (!concurrencyTimestampProperty.isReadOnly())
dataObject.set(concurrencyTimestampProperty, snapshot);
else
((CoreDataObject) dataObject).setValue(concurrencyTimestampProperty.getName(), snapshot);
}
}
// FIXME: need CG configuration related to enabling and handling concurrency
// checks
protected void updateOptimistic(PlasmaDataObject dataObject, PlasmaType type, RowWriter rowContext)
throws IOException {
PlasmaProperty concurrencyUserProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.user);
if (concurrencyUserProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency (username) property for type, " + type.getURI() + "#"
+ type.getName());
} else {
byte[] bytes = HBaseDataConverter.INSTANCE.toBytes(concurrencyUserProperty, username);
this.updateCell(rowContext, dataObject, concurrencyUserProperty, bytes);
}
PlasmaProperty concurrencyTimestampProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.time);
if (concurrencyTimestampProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency timestamp property for type, " + type.getURI() + "#"
+ type.getName());
} else {
Date dateSnapshot = new Date(this.snapshotMap.getSnapshotDate().getTime());
Object snapshot = DataConverter.INSTANCE.convert(concurrencyTimestampProperty.getType(), dateSnapshot);
byte[] bytes = HBaseDataConverter.INSTANCE.toBytes(concurrencyTimestampProperty, snapshot);
this.updateCell(rowContext, dataObject, concurrencyTimestampProperty, bytes);
}
}
protected void validateModifications(DataGraph dataGraph, PlasmaDataObject dataObject,
RowWriter rowWriter)
throws IllegalAccessException
{
PlasmaType type = (PlasmaType)dataObject.getType();
if (log.isDebugEnabled())
log.debug("updating " + type.getName() + " '" + ((PlasmaDataObject)dataObject).getUUIDAsString()+ "'");
PlasmaNode dataNode = (PlasmaNode)dataObject;
PlasmaType rootType = (PlasmaType)rowWriter.getRootDataObject().getType();
DataGraphConfig dataGraphConfig = CloudGraphConfig.getInstance().getDataGraph(rootType.getQualifiedName());
List properties = type.getProperties();
for (Property p : properties)
{
PlasmaProperty property = (PlasmaProperty)p;
Object oldValue = dataGraph.getChangeSummary().getOldValue(dataObject, property);
if (oldValue == null)
continue; // it's not been modified
if (property.isReadOnly())
throw new IllegalAccessException("attempt to modify read-only property, "
+ type.getURI() + "#" + type.getName() + "." + property.getName());
UserDefinedRowKeyFieldConfig userDefinedField =
dataGraphConfig.findUserDefinedRowKeyField(property);
if (userDefinedField != null) {
throw new IllegalAccessException("attempt to modify row-key property, "
+ type.getURI() + "#" + type.getName() + "." + property.getName()
+ " - this property is configured as a row-key field for table '"
+ dataGraphConfig.getTable().getName() + "'");
}
//FIXME: what if an entire entity is deleted which is part
// of the row key. Detect this. Or added for that matter.
}
}
protected void checkConcurrency(DataGraph dataGraph, PlasmaDataObject dataObject) {
PlasmaType type = (PlasmaType)dataObject.getType();
if (dataGraph.getChangeSummary().isCreated(dataObject)) {
this.setOrigination(dataObject, type);
}
else if (dataGraph.getChangeSummary().isModified(dataObject)) {
Timestamp snapshotDate = (Timestamp)((CoreDataObject)dataObject).getValue(CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP);
if (snapshotDate == null)
throw new RequiredPropertyException("instance property '" + CoreConstants.PROPERTY_NAME_SNAPSHOT_TIMESTAMP
+ "' is required to update entity "
+ dataObject);
//FIXME: check optimistic/pessimistic concurrency
this.setOptimistic(dataObject, type, snapshotDate);
}
else if (dataGraph.getChangeSummary().isDeleted(dataObject)) {
//FIXME: check optimistic/pessimistic concurrency
}
}
// FIXME: need CG configuration related to enabling and handling concurrency
// checks
protected void checkOptimistic(PlasmaDataObject dataObject, PlasmaType type, Timestamp snapshotDate) {
PlasmaProperty concurrencyUserProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.user);
if (concurrencyUserProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency (username) property for type, " + type.getURI() + "#"
+ type.getName());
}
PlasmaProperty concurrencyTimestampProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.optimistic,
ConcurrentDataFlavor.time);
if (concurrencyTimestampProperty == null) {
if (log.isDebugEnabled())
log.debug("could not find optimistic concurrency timestamp property for type, " + type.getURI() + "#"
+ type.getName());
}
}
// FIXME: need CG configuration related to enabling and handling concurrency
// checks
protected void checkLock(PlasmaDataObject dataObject, PlasmaType type, Timestamp snapshotDate) {
PlasmaProperty lockingUserProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.pessimistic,
ConcurrentDataFlavor.user);
if (lockingUserProperty == null)
if (log.isDebugEnabled())
log.debug("could not find locking user property for type, " + type.getURI() + "#" + type.getName());
PlasmaProperty lockingTimestampProperty = (PlasmaProperty) type.findProperty(ConcurrencyType.pessimistic,
ConcurrentDataFlavor.time);
if (lockingTimestampProperty == null)
if (log.isDebugEnabled())
log.debug(
"could not find locking timestamp property for type, " + type.getURI() + "#" + type.getName());
}
protected String toString(Edge[] edges) {
StringBuilder buf = new StringBuilder();
if (edges != null)
for (int i = 0; i < edges.length; i++) {
if (i > 0)
buf.append(", ");
buf.append(edges[i].toString());
}
return buf.toString();
}
protected String toString(List edges) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < edges.size(); i++) {
if (i > 0)
buf.append(", ");
buf.append(edges.get(i).toString());
}
return buf.toString();
}
protected String toString(Map map) {
StringBuilder buf = new StringBuilder();
if (map != null) {
Iterator iter = map.keySet().iterator();
int i = 0;
while (iter.hasNext()) {
if (i > 0)
buf.append(", ");
buf.append(iter.next());
}
}
return buf.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy