/** * 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.state; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.bind.JAXBException; import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudgraph.config.TableConfig; import org.cloudgraph.state.GraphState.Edge; import org.plasma.sdo.PlasmaDataObject; import org.plasma.sdo.PlasmaEdge; import org.plasma.sdo.PlasmaNode; import org.plasma.sdo.PlasmaType; import org.plasma.sdo.core.CoreConstants; import org.plasma.sdo.helper.PlasmaTypeHelper; import commonj.sdo.DataObject; import commonj.sdo.Type; /** * State management and mapping implementation class * designed to facilitate: * 1.) Fast XML marshalling and unmarshalling (using JAXB though * StAX is a potential replacement candidate) * 2.) Minimal data footprint using appropriate data groupings, e.g. * types grouped under URI's, to reduce redundancy * 3.) Minimal data footprint using mappings, e.g. types mapped to QName hash codes * and integral sequence numbers, to reduce redundancy * 4.) Integral sequence number management for all types and data object * instances (represented by UUID) * 5.) Row key mapping and management * * <p> * In general, mappings within the state structure are * designed and included for the purpose of consolidation of * potentially repetitive data which would otherwise be included * within cells as column data or as part of composite column * qualifiers. * </p> * * @see org.cloudgraph.state.StateModel * @see org.cloudgraph.state.URI * @see org.cloudgraph.state.TypeEntry * @see org.cloudgraph.state.UUID * @see org.cloudgraph.state.RowKey * * * @author Scott Cinnamond * @since 0.5 */ public class GraphState implements State { private static Log log = LogFactory.getLog(GraphState.class); public static final Charset charset = Charset.forName( CoreConstants.UTF8_ENCODING ); /** * The name of the table column which stores the UUID for the * data object which is the data graph root. */ @Deprecated public static final String ROOT_UUID_COLUMN_NAME = "__ROOT__"; /** * The name bytes of the table column which stores the UUID for the * data object which is the data graph root. */ @Deprecated public static final byte[] ROOT_UUID_COLUMN_NAME_BYTES = ROOT_UUID_COLUMN_NAME.getBytes(charset); /** * The name of the table column containing the mapped row key state * of a graph. */ public static final String STATE_COLUMN_NAME = "__STATE__"; /** * The name bytes of the table column containing the mapped row key state * of a graph. */ public static final byte[] STATE_COLUMN_NAME_BYTES = STATE_COLUMN_NAME.getBytes(charset); /** * The name of the table column containing the toumbstone * of a graph. */ public static final String TOUMBSTONE_COLUMN_NAME = "__TOUMBSTONE__"; /** * The name of the table column containing the toumbstone * of a graph. */ public static final byte[] TOUMBSTONE_COLUMN_NAME_BYTES = TOUMBSTONE_COLUMN_NAME.getBytes(charset); private static final String EDGE_RIGHT = "R"; private static final String EDGE_LEFT = "L"; private static final String EDGE_DELIM = ":"; private StringBuilder buf = new StringBuilder(); private StateMarshalingContext context; /** The UUID which identified the root data object within a graph */ private java.util.UUID rootUUID; /** Maps UUID strings to UUID state structures */ private Map<String, UUID> uuidMap = new HashMap<String, UUID>(); /** * Maps qualified type names to list of UUIDs (including * UUID's archived in history) linked to the type */ private Map<QName, Map<Integer, UUID>> uuidTypeMap = new HashMap<QName, Map<Integer, UUID>>(); /** Maps UUID strings to row Keys */ private Map<String, RowKey> rowKeyMap; /** Maps URI strings to URI state structures */ private Map<String, URI> uriMap = new HashMap<String, URI>(); /** Maps qualified names to types */ private Map<QName, TypeEntry> typeNameMap = new HashMap<QName, TypeEntry>(); /** Maps type sequence identifiers to types */ private Map<Integer, TypeEntry> typeIdMap = new HashMap<Integer, TypeEntry>(); @SuppressWarnings("unused") private GraphState() {} public GraphState(java.util.UUID rootUUID, StateMarshalingContext context) { this.rootUUID = rootUUID; this.context = context; if (this.rootUUID == null) throw new IllegalArgumentException("expected arg, rootUUID"); if (this.context == null) throw new IllegalArgumentException("expected arg, context"); } public GraphState(String state, StateMarshalingContext context) { this.context = context; if (state == null) throw new IllegalArgumentException("expected arg, state"); if (this.context == null) throw new IllegalArgumentException("expected arg, context"); if (log.isDebugEnabled()) log.debug("unmarshal raw: " + state); StateModel model = null; NonValidatingDataBinding binding = this.context.getBinding(); try { model = (StateModel)binding.unmarshal(state); } catch (JAXBException e) { throw new StateException(e); } finally { if (binding != null) this.context.returnBinding(binding); } try { this.rootUUID = java.util.UUID.fromString(model.getRoot().getValue()); } catch (NullPointerException npe) { throw new RuntimeException("fixme"); } for (URI uri : model.getURIS()) { this.uriMap.put(uri.getUri(), uri); for (TypeEntry type : uri.getTypes()) { type.setUri(uri.getUri()); // remove before marshalling QName qname = new QName(uri.getUri(), type.getName()); this.typeNameMap.put(qname, type); this.typeIdMap.put(type.getId(), type); } } for (UUID uuid : model.getUUIDS()) { this.uuidMap.put(uuid.getValue(), uuid); TypeEntry type = this.typeIdMap.get(uuid.getTypeId()); QName qname = new QName(type.getUri(), type.getName()); Map<Integer, UUID> uuids = this.uuidTypeMap.get(qname); if (uuids == null) { uuids = new HashMap<Integer, UUID>(); this.uuidTypeMap.put(qname, uuids); } uuids.put(uuid.getId(), uuid); } if (model.getRowKeies() != null && model.getRowKeies().size() > 0) { this.rowKeyMap = new HashMap<String, RowKey>(); for (RowKey entry : model.getRowKeies()) this.rowKeyMap.put(entry.getUuid(), entry); } if (log.isDebugEnabled()) validate(); } public java.util.UUID getRootUUID() { return rootUUID; } private void validate() { // Ensure every row key has a UUID present /* if (rowKeyMap != null) { Iterator<String> iter = rowKeyMap.keySet().iterator(); while (iter.hasNext()) { String uuidStr = iter.next(); UUID uuid = uuidMap.get(uuidStr); if (uuid == null) throw new IllegalStateException("no UUID found for row-key '" + uuidStr + "'"); } } */ // Ensure every sequence num is <= number of UUIDs mapped to the type // Ensure the UUID sequence is found in the type mapping and the UUID // matches Iterator<String> iter = uuidMap.keySet().iterator(); while (iter.hasNext()) { String uuidStr = iter.next(); UUID uuid = uuidMap.get(uuidStr); int sequence = uuid.getId(); TypeEntry type = typeIdMap.get(uuid.getTypeId()); QName qname = new QName(type.getUri(), type.getName()); Map<Integer, UUID> uuids = this.uuidTypeMap.get(qname); log.debug("validating: " + type.getName() + " instances ("+uuids.size()+") sequence " + sequence); if (sequence > uuids.size()) { //log.error(this.marshal()); throw new IllegalStateException("invalid sequence("+sequence+") found for '" + uuid.getValue() + "' for type "+qname+" - expected " + uuids.size()); } UUID mappedUUID = uuids.get(sequence); if (mappedUUID == null) throw new IllegalStateException("no sequence("+sequence+") found for type "+qname+""); if (!mappedUUID.getValue().equals(uuid.getValue())) throw new IllegalStateException("no uuid '" + uuid.getValue() + "' mapped to sequence("+sequence+") found for type "+qname+""); } } public void close() { } /** * Creates and adds a sequence number mapped to the UUID within the * given data object. * @param dataObject the data object * @return the new sequence number * @throws IllegalArgumentException if the data object is already mapped */ public Integer addSequence(DataObject dataObject) { PlasmaType type = (PlasmaType)dataObject.getType(); if (log.isDebugEnabled()) { if ("Histogram".equals(dataObject.getType().getName())) { int foo = 0; foo++; } } TypeEntry typeEntry = this.typeNameMap.get(type.getQualifiedName()); if (typeEntry == null) { // type not mapped typeEntry = new TypeEntry(); typeEntry.setName(type.getName()); typeEntry.setId(this.typeNameMap.size() + 1); typeEntry.setHashCode(type.getQualifiedNameHashCode()); if (log.isDebugEnabled()) log.debug("adding type " + type.getQualifiedName() + " seq: " + typeEntry.getId() + " hash: " + typeEntry.getHashCode()); this.typeNameMap.put(type.getQualifiedName(), typeEntry); if (this.typeIdMap.get(typeEntry.getId()) == null) { this.typeIdMap.put(typeEntry.getId(), typeEntry); } else { TypeEntry dupEntry = this.typeIdMap.get(typeEntry.getId()); throw new StateException("duplicate type sequence found (type: " + dupEntry.getName() + " seq: " + dupEntry.getId() + ") when adding type " + type.getQualifiedName() + " seq: " + typeEntry.getId() + " hash: " + typeEntry.getHashCode()); } URI uri = this.uriMap.get(type.getURI()); if (uri == null) { // uri not mapped uri = new URI(); uri.setUri(type.getURI()); this.uriMap.put(type.getURI(), uri); } typeEntry.setUri(uri.getUri()); uri.getTypes().add(typeEntry); } Map<Integer, UUID> uuids = this.uuidTypeMap.get(type.getQualifiedName()); if (uuids == null) { uuids = new HashMap<Integer, UUID>(); this.uuidTypeMap.put(type.getQualifiedName(), uuids); } int result = -1; String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); UUID uid = this.uuidMap.get(uuid); if (uid == null) { uid = new UUID(); uid.setValue(uuid); uid.setTypeId(typeEntry.getId()); uid.setId(uuids.size() + 1); this.uuidMap.put(uuid, uid); Entry<Integer, UUID> existingEntry = findSequenceEntry(uuid, uuids); if (existingEntry == null) { uuids.put(uid.getId(), uid); } else { // first remove old type/sequence mapping for UUID // then re-add with the new sequence uuids.remove(existingEntry.getKey()); uuids.put(uid.getId(), uid); } result = uid.getId(); if (log.isDebugEnabled()) { log.debug("added sequence " + uid.getId() + " for data object " + dataObject); } } else throw new IllegalArgumentException("found existing mapping for UUID " + uuid); if (log.isDebugEnabled()) validate(); return result; } //FIXME: map these private Entry<Integer, UUID> findSequenceEntry(String uuid, Map<Integer, UUID> uuids) { for (Entry<Integer, UUID> entry : uuids.entrySet()) { if (entry.getValue().getValue().equals(uuid)) return entry; } return null; } /** * Removes the sequence number mapped to the UUID within the * given data object. If no existing sequence exists * for the given data object, a warning is logged and * null is returned. * @param dataObject the data object * @return the removed sequence if exists */ public Integer removeSequence(DataObject dataObject) { Integer result = null; PlasmaType type = (PlasmaType)dataObject.getType(); String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); UUID uid = this.uuidMap.get(uuid); if (log.isDebugEnabled()) { log.debug("removing sequence " + uid.getId() + " for data object " + dataObject); } if (uid == null) { log.warn("could not remove UUID - no existing mapping found for UUID " + uuid + " - ignoring"); return result; } else { this.uuidMap.remove(uuid); result = uid.getId(); } // NOTE: we are in general NOT removing this UUID from its type mapping because this could leave a // sequence gap. If there are e.g. 3 UUID's mapped for a given type, and we // remove sequence 2 with this method, then remove it from the type mapping, there would then // be 2 UUID's of that type. UUID with sequence 3 is already set into data cells with the sequence // 3. If we then add another data-object/sequence of the same type, if we remove the // type mapping we generate another sequence 3 (existing 2 sequences plus 1). Map<Integer, UUID> uuids = this.uuidTypeMap.get(type.getQualifiedName()); if (uuids.size() == 1) // last sequence for type { this.uuidTypeMap.remove(type.getQualifiedName()); } else { //see if this uuid is mapped to the largest sequence, can remove it // since no gap. Entry<Integer, UUID> existingEntry = findSequenceEntry(uuid, uuids); if (existingEntry.getKey().intValue() == uuids.size()) { uuids.remove(existingEntry.getKey()); } } if (log.isDebugEnabled()) validate(); return result; } /** * Returns an existing sequence number for the given * data object, or null if none exists. * Each sequence number generated is * a simply the number of existing elements for a particular * SDO type (plus 1). * @return the existing sequence or null if not exists for the given * data object */ public Integer findSequence(DataObject dataObject) { String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); UUID uid = this.uuidMap.get(uuid); if (uid != null) { return uid.getId(); } else return null; } /** * Returns an existing sequence number for the given * data object, or null if none exists. * Each sequence number generated is * a simply the number of existing elements for a particular * SDO type (plus 1). * @return the existing sequence or null if not exists for the given * data object * @throws IllegalArgumentException if the given data object UUID is not * already mapped */ public Integer getSequence(DataObject dataObject) { Integer result = findSequence(dataObject); if (result != null) { return result; } else { String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); throw new IllegalArgumentException("no sequence mapped to type " + dataObject.getType().getURI() + "#" + dataObject.getType().getName() + " for the given UUID, " + String.valueOf(uuid)); } } /** * Returns an existing UUID for the given * sequence number, or null if none exists. * @return the existing UUID or null if not exists for the given * sequence. */ public String findUUID(Type type, Integer sequence) { PlasmaType plasmaType = (PlasmaType)type; Map<Integer, UUID> uuids = this.uuidTypeMap.get(plasmaType.getQualifiedName()); if (uuids != null) { UUID uuid = uuids.get(sequence); if (uuid != null) return uuid.getValue(); } return null; } /** * Returns an existing UUID for the given * sequence number, or null if none exists. * @return the existing UUID or null if not exists for the given * sequence. * @param type the SDO type * @param sequence the sequence number * @return the UUID mapped to the given sequence * @throws IllegalArgumentException if the given sequence is not * already mapped */ public String getUUID(Type type, Integer sequence) { String result = findUUID(type, sequence); if (result != null) return result; throw new IllegalArgumentException("no UUID mapped for the given sequence (" + String.valueOf(sequence) + ") for given type, " + type.getURI() + "#" + type.getName()); } /** * Returns an existing mapped row key for the given data object, * or null if none exists. * @param dataObject the data object * @return an existing mapped row key for the given data object, * or null if none exists. */ public byte[] findRowKey(DataObject dataObject) { String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); return findRowKey(uuid); } /** * Returns an existing mapped row key for the given data object uuid, * or null if none exists. * @param uuid the data object uuid * @return an existing mapped row key for the given data object uuid, * or null if none exists. */ public byte[] findRowKey(String uuid) { if (this.rowKeyMap != null) { RowKey key = this.rowKeyMap.get(uuid); if (key != null) return key.getValue().getBytes(this.charset); } return null; } /** * Returns the table name for an existing mapped row key * for the given data object uuid. * @param uuid the data object uuid * @return an existing mapped row key for the given data object, * or null if none exists. * @throws IllegalArgumentException if no row key is mapped for the given data object uuid */ public String getRowKeyTable(String uuid) { if (this.rowKeyMap != null) { RowKey key = this.rowKeyMap.get(uuid); if (key != null) return key.getTable(); } throw new IllegalArgumentException("no row key mapped for the given UUID (" + String.valueOf(uuid) + ")"); } /** * Returns an existing mapped row key for the given data object. * @param dataObject the data object * @return an existing mapped row key for the given data object. * @throws IllegalArgumentException if no row key is mapped for the given data object */ public byte[] getRowKey(DataObject dataObject) { byte[] result = findRowKey(dataObject); if (result != null) return result; String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); throw new IllegalArgumentException("no row key mapped for the given UUID (" + String.valueOf(uuid) + ") for given type, " + dataObject.getType().getURI() + "#" + dataObject.getType().getName()); } /** * Returns an existing mapped row key for the given data object UUID. * @param uuid the data object uuid * @return an existing mapped row key for the given data object uuid. * @throws IllegalArgumentException if no row key is mapped for the given data object UUID, the UUID is null or the incorrect length */ public byte[] getRowKey(String uuid) { byte[] result = findRowKey(uuid); if (result != null) { if (log.isDebugEnabled()) log.debug("returning row-key: " + uuid + "->" + new String(result, this.charset)); return result; } throw new IllegalArgumentException("no row key mapped for the given UUID (" + String.valueOf(uuid) + ")"); } /** * Creates a new mapping for the given row key and * data object. * @param dataObject the data object * @param key the row key; */ public void addRowKey(DataObject dataObject, TableConfig table, byte[] key) { String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); if (uuid == null || uuid.length() == 0) throw new IllegalArgumentException("found null or zero length UUID from data object"); if (uuid.length() != 36) throw new IllegalArgumentException("found "+uuid.length()+" rather than 38 char length UUID from data object"); if (log.isDebugEnabled()) log.debug("adding "+table.getName()+" row-key: " + uuid + "->" + new String(key, this.charset)); if (this.rowKeyMap == null) { this.rowKeyMap = new HashMap<String, RowKey>(); } RowKey rowKey = this.rowKeyMap.get(uuid); if (rowKey == null) { rowKey = new RowKey(); rowKey.setUuid(uuid); rowKey.setTable(table.getName()); rowKey.setValue(new String(key, this.charset)); this.rowKeyMap.put(uuid, rowKey); } if (log.isDebugEnabled()) validate(); } /** * Removes an existing mapping for the given data object. * @param dataObject the data object * @param key the row key * @throws IllegalArgumentException if no row key is mapped for the UUID associated with the given * data object. */ public void removeRowKey(DataObject dataObject) { String uuid = ((PlasmaDataObject)dataObject).getUUIDAsString(); if (uuid == null || uuid.length() == 0) throw new IllegalArgumentException("found null or zero length UUID from data object"); if (uuid.length() != 36) throw new IllegalArgumentException("found "+uuid.length()+" rather than 38 char length UUID from data object"); RowKey rowKey = this.rowKeyMap.remove(uuid); if (rowKey == null) { throw new IllegalArgumentException("could not remove key - no row key mapped to UUID, " + uuid); } else { if (log.isDebugEnabled()) log.debug("removed "+rowKey.getTable()+" row-key: " + uuid + "->" + rowKey.getValue()); } if (log.isDebugEnabled()) validate(); } /** * Returns a count of the current row keys * @return a count of the current row keys */ public int getRowKeyCount() { if (this.rowKeyMap != null) return this.rowKeyMap.size(); return 0; } public void addEdges(PlasmaNode dataNode, List<PlasmaEdge> edges) { if (edges != null) { for (PlasmaEdge edge : edges) { PlasmaDataObject opposite = edge.getOpposite(dataNode).getDataObject(); if (findSequence(opposite) == null) addSequence(opposite); } } } /** * Returns a formatted string representation for the graph * edge(s) found linked from the given data object. * @param dataNode the source data node * @param edges the edges * @return a formatted string representation for the graph * edge(s) found linked from the given data object. */ public String marshalEdges(PlasmaNode dataNode, List<PlasmaEdge> edges) { String[] result = new String[0]; if (edges != null) { result = new String[edges.size()]; int i = 0; for (PlasmaEdge edge : edges) { PlasmaDataObject opposite = edge.getOpposite(dataNode).getDataObject(); result[i] = marshalEdge(edge, opposite); i++; } } //NOTE: returns '[]' for zero length array // use Arrays formatting return Arrays.toString(result); } public String marshalEdges(Edge[] edges) { String[] result = new String[0]; if (edges != null) { result = new String[edges.length]; int i = 0; for (Edge edge : edges) { result[i] = marshalEdge(edge); i++; } } //NOTE: returns '[]' for zero length array // use Arrays formatting return Arrays.toString(result); } public Edge[] unmarshalEdges(byte[] data) { String edges = new String(data, this.charset); return unmarshalEdges(edges); } public Edge[] unmarshalEdges(String data) { // replace Arrays formatting and whitespace, then split String[] array = data.replaceAll("[\\[\\]\\s]", "").split(","); if (array.length == 1 && array[0].length() == 0) return new Edge[0]; Edge[] result = new Edge[array.length]; for (int i = 0; i < result.length; i++) { result[i] = new Edge(array[i]); } return result; } private String marshalEdge(PlasmaEdge edge, DataObject opposite) { PlasmaType type = (PlasmaType)opposite.getType(); TypeEntry typeEntry = this.typeNameMap.get(type.getQualifiedName()); Integer typeSeq = typeEntry.getId(); Integer seq = this.getSequence(opposite); //String dir = formatDirection(edge.getDirection()); this.buf.setLength(0); //this.buf.append(dir); //this.buf.append(EDGE_DELIM); this.buf.append(String.valueOf(typeSeq)); this.buf.append(EDGE_DELIM); this.buf.append(String.valueOf(seq)); return this.buf.toString(); } private String marshalEdge(Edge edge) { this.buf.setLength(0); this.buf.append(String.valueOf(edge.getTypeId())); this.buf.append(EDGE_DELIM); this.buf.append(String.valueOf(edge.getId())); return this.buf.toString(); } /** * Returns edge structures for the given data edges * @param dataNode the source data node * @param edges the edges * @return edge structures for the given data edges */ public Edge[] createEdges(PlasmaNode dataNode, List<PlasmaEdge> edges) { Edge[] result = new Edge[0]; if (edges != null) { result = new Edge[edges.size()]; int i = 0; for (PlasmaEdge edge : edges) { PlasmaDataObject opposite = edge.getOpposite(dataNode).getDataObject(); result[i] = createEdge(edge, opposite); i++; } } return result; } /** * Returns edge structures for the given data objects. A read-only opeartion * which does not modify the * underlying graph state. * @param edges the edges * @return edge structures for the given data edges */ public Edge[] createEdges(List<DataObject> dataObjects) { Edge[] result = new Edge[0]; if (dataObjects != null) { result = new Edge[dataObjects.size()]; int i = 0; for (DataObject dataObject : dataObjects) { result[i] = createEdge((PlasmaDataObject)dataObject); i++; } } return result; } private Edge createEdge(PlasmaEdge edge, DataObject opposite) { PlasmaType type = (PlasmaType)opposite.getType(); TypeEntry typeEntry = this.typeNameMap.get(type.getQualifiedName()); if (typeEntry == null) throw new StateException("no type mapped for, " + type.getQualifiedName() + " (" + String.valueOf(type.getQualifiedName().hashCode())+ ")"); Integer typeSeq = typeEntry.getId(); String uuid = ((PlasmaDataObject)opposite).getUUIDAsString(); Integer seq = this.getSequence(opposite); Edge result = new Edge(type, typeSeq, uuid, seq); return result; } private Edge createEdge(DataObject opposite) { PlasmaType type = (PlasmaType)opposite.getType(); TypeEntry typeEntry = this.typeNameMap.get(type.getQualifiedName()); if (typeEntry == null) throw new StateException("no type mapped for, " + type.getQualifiedName() + " (" + String.valueOf(type.getQualifiedName().hashCode())+ ")"); Integer typeSeq = typeEntry.getId(); String uuid = ((PlasmaDataObject)opposite).getUUIDAsString(); Integer seq = this.getSequence(opposite); Edge result = new Edge(type, typeSeq, uuid, seq); return result; } public String marshal() { if (log.isDebugEnabled()) validate(); return marshal(false); } public String marshal(boolean formatted) { NonValidatingDataBinding binding = null; String xml = ""; try { StateModel model = new StateModel(); Root root = new Root(); root.setValue(this.rootUUID.toString()); model.setRoot(root); if (this.rowKeyMap != null) { for (Entry<String, RowKey> entry : this.rowKeyMap.entrySet()) model.getRowKeies().add(entry.getValue()); } for (UUID uuid : this.uuidMap.values()) { model.getUUIDS().add(uuid); } for (URI uri : this.uriMap.values()) { model.getURIS().add(uri); } // null out URI on individual types as uri // found on URI container for (TypeEntry entry : this.typeNameMap.values()) entry.setUri(null); binding = this.context.getBinding(); xml = binding.marshal(model); // no formatting } catch (JAXBException e1) { throw new StateException(e1); } finally { if (binding != null) this.context.returnBinding(binding); } return xml; } /** * Simple immutable structure to contain and * manage the parse result for a single edge. */ public class Edge { private PlasmaType type; private Integer typeId; private String uuid; private Integer id; private int hashCode; @SuppressWarnings("unused") private Edge() {} public Edge(PlasmaType type, Integer typeId, String uuid, Integer id) { super(); this.type = type; this.typeId = typeId; this.uuid = uuid; this.id = id; } public String toString() { StringBuilder buf = new StringBuilder(); buf.append("["); buf.append(this.type.toString()); buf.append("("); buf.append(this.typeId); buf.append(")"); buf.append(","); buf.append(uuid); buf.append("("); buf.append(this.id); buf.append(")"); buf.append("]"); return buf.toString(); } /** * COnstructor which parses the given marshalled raw edge data into * an edge structure. * @param data */ public Edge(String data) { this.type = (PlasmaType)type; // FIXME WTF? String[] tokens = data.split(EDGE_DELIM); this.typeId = Integer.valueOf(tokens[0]); TypeEntry typeEntry = typeIdMap.get(this.typeId); this.type = (PlasmaType)PlasmaTypeHelper.INSTANCE.getType(typeEntry.getUri(), typeEntry.getName()); this.id = Integer.valueOf(tokens[1]); this.uuid = getUUID(this.type, this.id); } @Override public boolean equals(Object other) { return this.hashCode() == other.hashCode(); } public int hashCode() { if (this.hashCode == 0) this.hashCode = this.typeId.intValue() ^ this.id.intValue(); return this.hashCode; } public PlasmaType getType() { return type; } public Integer getTypeId() { return typeId; } /** * Returns the UUID string for the target data object * for this edge. * @return the UUID string for the target data object * for this edge. */ public String getUuid() { return uuid; } /** * Returns the sequence id for the target data object * for this edge. * @return the sequence id for the target data object * for this edge. */ public Integer getId() { return id; } } }