/** * 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 * <http://cloudgraph.org/licenses/>. */ package org.cloudgraph.hbase.mutation; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudgraph.config.CloudGraphConfig; 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; import org.cloudgraph.state.GraphState.Edge; import org.cloudgraph.store.service.GraphServiceException; import org.plasma.sdo.PlasmaDataObject; import org.plasma.sdo.PlasmaEdge; import org.plasma.sdo.PlasmaNode; import org.plasma.sdo.PlasmaProperty; import org.plasma.sdo.PlasmaSetting; import org.plasma.sdo.PlasmaType; import org.plasma.sdo.access.RequiredPropertyException; 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.profile.KeyType; import commonj.sdo.ChangeSummary.Setting; import commonj.sdo.DataGraph; import commonj.sdo.DataObject; import commonj.sdo.Property; public class Update extends DefaultMutation implements Collector { private static Log log = LogFactory.getLog(Update.class); public Update(ServiceContext context, SnapshotMap snapshotMap, String username) { super(context, snapshotMap, username); } @Override public void collect(DataGraph dataGraph, PlasmaDataObject dataObject, DistributedWriter graphWriter, TableWriter tableWriter, RowWriter rowWriter) throws IllegalAccessException, IOException { PlasmaType type = (PlasmaType)dataObject.getType(); // FIXME: get rid of cast - define instance properties in 'base type' 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 '" + type.getURI() + "#" + type.getName() + "'"); if (log.isDebugEnabled()) log.debug(dataObject + " timestamp: " + String.valueOf(snapshotDate)); checkLock(dataObject, type, snapshotDate); updateOptimistic(dataObject, type, rowWriter); List<Setting> settings = dataGraph.getChangeSummary().getOldValues(dataObject); for (Setting setting: settings) // for every changed value { PlasmaProperty property = (PlasmaProperty)setting.getProperty(); if (property.isKey(KeyType.primary)) continue; // cannot be modified on an update if (property.getConcurrent() != null) continue; // processed above if (property.isReadOnly()) throw new IllegalAccessException("attempt to modify read-only property, " + property); Object dataValue = dataObject.get(property); if (dataValue != null) if (log.isDebugEnabled()) log.debug("updating " + property.toString()); else if (log.isDebugEnabled()) log.debug("removing " + property.toString()); byte[] valueBytes = null; if (!property.getType().isDataType()) { if (!property.isMany()) { valueBytes = this.collectSingular(graphWriter, tableWriter, rowWriter, dataObject, setting, property, dataValue); } else { valueBytes = this.collectMulti(graphWriter, tableWriter, rowWriter, dataObject, setting, property, dataValue); } } else { // FIXME: research best way to encode multiple // primitives as bytes if (dataValue != null) valueBytes = HBaseDataConverter.INSTANCE.toBytes( property, dataValue); } if (valueBytes != null) { this.updateCell(rowWriter, dataObject, property, valueBytes); } else { this.deleteCell(rowWriter, dataObject, property); } } } private byte[] collectSingular(DistributedWriter graphWriter, TableWriter tableWriter, RowWriter rowWriter, PlasmaDataObject dataObject, Setting setting, PlasmaProperty property, Object value) throws IOException { byte[] valueBytes = null; // get old value setting/value - can be List or single data-object Object oldValue = setting.getValue(); PlasmaNode dataNode = (PlasmaNode)dataObject; List <PlasmaEdge> edges = dataNode.getEdges(property); // remove old value if exists from graph state if (!(oldValue instanceof NullValue)) { if (!(oldValue instanceof List)) { DataObject oldOpposite = (DataObject)oldValue; rowWriter.getGraphState().removeSequence(oldOpposite); boolean typeBound = CloudGraphConfig.getInstance().findTable( ((PlasmaType)oldOpposite.getType()).getQualifiedName()) != null; // FIXME: if old opposite is a duplicate of current, new row key is removed // and never re-added and graph assembly thinks its an internal edge if (typeBound) rowWriter.getGraphState().removeRowKey(oldOpposite); } else throw new GraphServiceException("unexpected List as old value for property, " + property.toString()); } // if has a new value if (value != null) { // add the new value into graph state this.addRowKeys(dataObject, dataNode, property, edges, graphWriter, tableWriter, rowWriter); List<PlasmaEdge> stateEdges = this.findUpdateEdges(dataNode, property, edges, graphWriter, rowWriter); rowWriter.getGraphState().addEdges(dataNode, stateEdges); // Create a formatted column value // for this edge. String valueString = rowWriter.getGraphState().marshalEdges( dataNode, stateEdges); valueBytes = valueString.getBytes(GraphState.charset); if (log.isDebugEnabled()) log.debug("saving edges (singular): " + valueString); } return valueBytes; } private byte[] collectMulti(DistributedWriter graphWriter, TableWriter tableWriter, RowWriter rowWriter, PlasmaDataObject dataObject, Setting setting, PlasmaProperty property, Object value) throws IOException { byte[] valueBytes = null; // get old value setting/value - can be List or single data-object Object oldValue = setting.getValue(); PlasmaNode dataNode = (PlasmaNode)dataObject; List <PlasmaEdge> edges = dataNode.getEdges(property); // get old edges from change summary HashMap<String, DataObject> oldEdgeMap = getOldEdgeMap( oldValue, property); // add the new plasma edge values into graph state this.addRowKeys(dataObject, dataNode, property, edges, graphWriter, tableWriter, rowWriter); List<PlasmaEdge> updateEdges = this.findUpdateEdges(dataNode, property, edges, graphWriter, rowWriter); rowWriter.getGraphState().addEdges(dataNode, updateEdges); // convert to state edge map to compare w/existing Edge[] updatedEdges = rowWriter.getGraphState().createEdges(dataNode, updateEdges); Map<String, Edge> updatedEdgeMap = new HashMap<String, Edge>(); for (int i = 0; i < updatedEdges.length; i++) updatedEdgeMap.put(updatedEdges[i].getUuid(), updatedEdges[i]); // fetch the existing edges from data store and marshall as edges byte[] existingValue = rowWriter.fetchColumnValue(dataObject, property); Edge[] existingEdges = null; if (existingValue != null && existingValue.length > 0) existingEdges = rowWriter.getGraphState().unmarshalEdges(existingValue); // merge if (log.isDebugEnabled()) { log.debug("merging existing: " + toString(existingEdges) + " updated: " + toString(updatedEdges) + " old: " + toString(oldEdgeMap)); } List<Edge> list = new ArrayList<Edge>(); for (Edge updated : updatedEdges) list.add(updated); if (existingEdges != null) for (Edge existing : existingEdges) { // Only add existing edge if NOT found in updated in graph and change summary if (!updatedEdgeMap.containsKey(existing.getUuid())) { DataObject oldDataObject = oldEdgeMap.get(existing.getUuid()); if (oldDataObject == null) { if (log.isDebugEnabled()) log.debug("adding existing: " + existing); list.add(existing); } else { // edge is obsolete - move to history if (log.isDebugEnabled()) log.debug("archiving existing: " + existing); rowWriter.getGraphState().removeSequence(oldDataObject); boolean typeBound = CloudGraphConfig.getInstance().findTable( ((PlasmaType)oldDataObject.getType()).getQualifiedName()) != null; if (typeBound) rowWriter.getGraphState().removeRowKey(oldDataObject); } } // else added it already above } Edge[] resultEdges = new Edge[list.size()]; list.toArray(resultEdges); String valueString = rowWriter.getGraphState().marshalEdges(resultEdges); valueBytes = valueString.getBytes(GraphState.charset); if (log.isDebugEnabled()) { log.debug("saving edges (multi): " + valueString); for (Edge e : resultEdges) { log.debug("saving edge: " + e); rowWriter.getGraphState().getUUID(e.getType(), e.getId()); // ensure it exists } } return valueBytes; } }