/************************************************************************
* Copyright (c) 2014-2016 IoT-Solutions e.U.
*
* 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 iot.jcypher.query.result.util;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.json.JsonObject;
import org.neo4j.driver.internal.value.ListValue;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Value;
import iot.jcypher.concurrency.Locking;
import iot.jcypher.database.IDBAccess;
import iot.jcypher.database.remote.BoltDBAccess;
import iot.jcypher.domain.internal.DomainAccess.DomainAccessHandler.DBAccessWrapper;
import iot.jcypher.graph.GrAccess;
import iot.jcypher.graph.GrLabel;
import iot.jcypher.graph.GrNode;
import iot.jcypher.graph.GrPath;
import iot.jcypher.graph.GrProperty;
import iot.jcypher.graph.GrPropertyContainer;
import iot.jcypher.graph.GrRelation;
import iot.jcypher.graph.Graph;
import iot.jcypher.graph.PersistableItem;
import iot.jcypher.graph.SyncState;
import iot.jcypher.graph.internal.ChangeListener;
import iot.jcypher.graph.internal.GrId;
import iot.jcypher.graph.internal.LocalId;
import iot.jcypher.graph.internal.LockUtil;
import iot.jcypher.query.JcQuery;
import iot.jcypher.query.JcQueryResult;
import iot.jcypher.query.api.IClause;
import iot.jcypher.query.api.pattern.Node;
import iot.jcypher.query.api.pattern.Relation;
import iot.jcypher.query.api.start.StartPoint;
import iot.jcypher.query.factories.clause.CASE;
import iot.jcypher.query.factories.clause.CREATE;
import iot.jcypher.query.factories.clause.DO;
import iot.jcypher.query.factories.clause.ELSE;
import iot.jcypher.query.factories.clause.END;
import iot.jcypher.query.factories.clause.FOR_EACH;
import iot.jcypher.query.factories.clause.NATIVE;
import iot.jcypher.query.factories.clause.OPTIONAL_MATCH;
import iot.jcypher.query.factories.clause.RETURN;
import iot.jcypher.query.factories.clause.START;
import iot.jcypher.query.factories.clause.WHEN;
import iot.jcypher.query.factories.clause.WHERE;
import iot.jcypher.query.factories.clause.WITH;
import iot.jcypher.query.factories.xpression.C;
import iot.jcypher.query.result.JcError;
import iot.jcypher.query.result.util.ResultHandler.AContentHandler.PropEntry;
import iot.jcypher.query.result.util.ResultHandler.AContentHandler.RowOrRecord;
import iot.jcypher.query.values.JcBoolean;
import iot.jcypher.query.values.JcCollection;
import iot.jcypher.query.values.JcElement;
import iot.jcypher.query.values.JcNode;
import iot.jcypher.query.values.JcNumber;
import iot.jcypher.query.values.JcPath;
import iot.jcypher.query.values.JcProperty;
import iot.jcypher.query.values.JcRelation;
import iot.jcypher.query.values.JcString;
import iot.jcypher.query.values.JcValue;
import iot.jcypher.query.values.ValueAccess;
import iot.jcypher.query.values.ValueWriter;
import iot.jcypher.query.writer.Format;
import iot.jcypher.query.writer.WriterContext;
import iot.jcypher.transaction.ITransaction;
import iot.jcypher.util.QueriesPrintObserver.QueryToObserve;
import iot.jcypher.util.ResultSettings;
import iot.jcypher.util.Util;
public class ResultHandler {
public static final String lockVersionProperty = "_c_version_";
public static ThreadLocal<Boolean> includeNullValues = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
private AContentHandler contentHandler;
private IDBAccess dbAccess;
// allow to switch off writing version property for testing purposes
public static boolean writeVersion = true;
private Graph graph;
private Locking lockingStrategy;
private LocalElements localElements;
private NodeRelationListener nodeRelationListener;
private Map<Long, GrNode> nodesById;
// contains changed and removed (deleted) nodes
private Map<Long, GrNode> changedNodesById;
private Map<Long, GrRelation> relationsById;
// contains changed and removed (deleted) relations
private Map<Long, GrRelation> changedRelationsById;
// contains element versions if Locking is activated
private ElementVersions elementVersions;
private Map<String, List<GrNode>> nodeColumns;
private Map<String, List<GrRelation>> relationColumns;
private Map<String, List<GrPath>> pathColumns;
@SuppressWarnings("rawtypes")
private Map<String, List> valueColumns;
// only needed to lookup nodes and relations
private List<String> unResolvedColumns;
/**
* construct a ResultHandler initialized with a jsonResult
* @param jsonResult
* @param queryIndex
* @param dbAccess
*/
public ResultHandler(JsonObject jsonResult, int queryIndex, IDBAccess dbAccess) {
super();
init(dbAccess);
this.contentHandler = new JSONContentHandler(jsonResult, queryIndex);
}
/**
* construct a ResultHandler initialized with a statementResult
* @param statementResult
* @param dbAccess
*/
public ResultHandler(StatementResult statementResult, IDBAccess dbAccess) {
super();
init(dbAccess);
this.contentHandler = new BoltContentHandler(statementResult, this);
}
/**
* construct a ResultHandler initialized with a statementResult
* @param dbAccess
*/
public ResultHandler(IDBAccess dbAccess) {
super();
init(dbAccess);
this.contentHandler = this.calcContentHandler(dbAccess);
}
private AContentHandler calcContentHandler(IDBAccess dba) {
if (dba instanceof BoltDBAccess)
return new BoltContentHandler(null, this);
else if (dba instanceof DBAccessWrapper)
return this.calcContentHandler(((DBAccessWrapper)dba).getDelegate());
else
return new JSONContentHandler(null, -1);
}
private void init(IDBAccess dbAccess) {
this.dbAccess = dbAccess;
this.lockingStrategy = Locking.NONE;
this.localElements = new LocalElements();
this.graph = GrAccess.createGraph(this);
GrAccess.setGraphState(this.graph, SyncState.SYNC);
}
IDBAccess getDbAccess() {
return dbAccess;
}
AContentHandler getContentHandler() {
return contentHandler;
}
public void setLockingStrategy(Locking lockingStrategy) {
this.lockingStrategy = lockingStrategy;
}
public ITransaction createLockingTxIfNeeded() {
if (this.lockingStrategy == Locking.OPTIMISTIC) {
ITransaction tx = this.dbAccess.getTX();
if (tx == null)
return this.dbAccess.beginTX();
}
return null;
}
public LocalElements getLocalElements() {
return localElements;
}
public Graph getGraph() {
return this.graph;
}
public List<GrNode> getNodes(JcNode node) {
String colKey = ValueAccess.getName(node);
List<GrNode> nds = getNodes(colKey);
nds = filterRemovedAndNullItems(nds);
return Collections.unmodifiableList(nds);
}
private <T extends PersistableItem> List<T> filterRemovedAndNullItems(List<T> items) {
ArrayList<T> rItems = new ArrayList<T>();
for (T item : items) {
if (item == null) {
if (includeNullValues.get() || ResultSettings.includeNullValuesAndDuplicates.get().booleanValue())
rItems.add(item);
} else if (GrAccess.getState(item) != SyncState.REMOVED)
rItems.add(item);
}
return rItems;
}
private List<GrNode> getNodes(String colKey) {
List<GrNode> rNodes = getNodeColumns().get(colKey);
if (rNodes == null) {
rNodes = new ArrayList<GrNode>();
Iterator<RowOrRecord> it = this.contentHandler.getDataIterator();
int rowIdx = -1;
while(it.hasNext()) { // iterate over rows
rowIdx++;
RowOrRecord roc = it.next();
ElementInfo ei = roc.getElementInfo(colKey);
GrNode rNode = null;
if (!ei.isNull) {
rNode = getNodesById().get(ei.id);
if (rNode == null) {
rNode = GrAccess.createNode(this, new GrId(ei.id), rowIdx);
GrAccess.setState(rNode, SyncState.SYNC);
GrAccess.addChangeListener(getNodeRelationListener(), rNode);
getNodesById().put(ei.id, rNode);
}
}
if (ResultSettings.includeNullValuesAndDuplicates.get().booleanValue() || !rNodes.contains(rNode))
rNodes.add(rNode);
}
getNodeColumns().put(colKey, rNodes);
getUnresolvedColumns().remove(colKey);
}
return rNodes;
}
public List<GrRelation> getRelations(JcRelation relation) {
String colKey = ValueAccess.getName(relation);
List<GrRelation> rels = getRelations(colKey);
rels = filterRemovedAndNullItems(rels);
return Collections.unmodifiableList(rels);
}
private List<GrRelation> getRelations(String colKey) {
List<GrRelation> rRelations = getRelationColumns().get(colKey);
if (rRelations == null) {
rRelations = new ArrayList<GrRelation>();
Iterator<RowOrRecord> it = this.contentHandler.getDataIterator();
int rowIdx = -1;
while(it.hasNext()) { // iterate over rows
rowIdx++;
RowOrRecord roc = it.next();
GrRelation rRelation = null;
ElementInfo ei = roc.getElementInfo(colKey);
if (!ei.isNull) {
RelationInfo ri = roc.getRelationInfo(colKey);
rRelation = getRelationsById().get(ei.id);
if (rRelation == null) {
rRelation = GrAccess.createRelation(this, new GrId(ei.id),
new GrId(ri.startNodeId), new GrId(ri.endNodeId), rowIdx);
GrAccess.setState(rRelation, SyncState.SYNC);
GrAccess.addChangeListener(getNodeRelationListener(), rRelation);
getRelationsById().put(ei.id, rRelation);
}
}
if (ResultSettings.includeNullValuesAndDuplicates.get().booleanValue() || !rRelations.contains(rRelation))
rRelations.add(rRelation);
}
getRelationColumns().put(colKey, rRelations);
getUnresolvedColumns().remove(colKey);
}
return rRelations;
}
public List<GrPath> getPaths(JcPath path) {
String colKey = ValueAccess.getName(path);
List<GrPath> rPaths = getPathColumns().get(colKey);
if (rPaths == null) {
rPaths = new ArrayList<GrPath>();
Iterator<RowOrRecord> it = this.contentHandler.getDataIterator();
int rowIdx = -1;
while(it.hasNext()) { // iterate over rows
rowIdx++;
RowOrRecord roc = it.next();
PathInfo pinfo = roc.getPathInfo(colKey);
if (pinfo != null) {
int sz = pinfo.relationIds.size();
List<GrId> relIds = new ArrayList<GrId>(sz);
long sid;
long eid = pinfo.startNodeId;
for (int i = 0; i < sz; i++) {
long rid = pinfo.relationIds.get(i);
GrRelation rRelation = getRelationsById().get(rid);
if (rRelation == null) {
sid = eid;
eid = roc.gePathtNodeIdAt(pinfo, i + 1);
rRelation = GrAccess.createRelation(this, new GrId(rid),
new GrId(sid), new GrId(eid), rowIdx);
GrAccess.setState(rRelation, SyncState.SYNC);
GrAccess.addChangeListener(getNodeRelationListener(), rRelation);
getRelationsById().put(rid, rRelation);
}
relIds.add(new GrId(rid));
}
GrPath rPath = GrAccess.createPath(this, new GrId(pinfo.startNodeId),
new GrId(pinfo.endNodeId), relIds, rowIdx);
rPaths.add(rPath);
} else if (ResultSettings.includeNullValuesAndDuplicates.get().booleanValue()) {
rPaths.add(null);
}
}
getPathColumns().put(colKey, rPaths);
}
return Collections.unmodifiableList(rPaths);
}
public List<BigDecimal> getNumbers(JcNumber number) {
return this.getValues(number);
}
public List<String> getStrings(JcString string) {
return this.getValues(string);
}
public List<Boolean> getBooleans(JcBoolean bool) {
return this.getValues(bool);
}
public List<List<?>> getCollections(JcCollection collection) {
return this.getValues(collection);
}
public List<?> getObjects(JcValue val) {
return this.getValues(val);
}
public GrNode getNode(GrId id, int rowIdx) {
if (id instanceof LocalId)
return getLocalNode(id.getId());
else
return getNode(id.getId(), rowIdx);
}
private GrNode getLocalNode(long id) {
return this.localElements.getNode(id);
}
private GrNode getNode(long id, int rowIdx) {
GrNode rNode = getNodesById().get(id);
if (rNode == null) {
// first resolve unresolved columns
if (getUnresolvedColumns().size() > 0) {
List<String> ucols = new ArrayList<String>();
ucols.addAll(getUnresolvedColumns());
for (int i = 0; i < ucols.size(); i++) {
String colKey = ucols.get(i);
Iterator<RowOrRecord> it = this.contentHandler.getDataIterator();
boolean isNodeColumn = false;
while(it.hasNext()) { // iterate over just one row
RowOrRecord roc = it.next();
ElementInfo ei = roc.getElementInfo(colKey);
if (ei != null && !ei.isNull) { // relation or node
if (ElemType.NODE == ei.type) {
isNodeColumn = true;
}
} else
getUnresolvedColumns().remove(colKey);
break;
}
if (isNodeColumn) {
// resolve nodes of column
getNodes(colKey);
// test if node has been resolved
rNode = getNodesById().get(id);
if (rNode != null)
return rNode;
}
}
}
}
if (rNode == null) {
rNode = GrAccess.createNode(this, new GrId(id), rowIdx);
GrAccess.setState(rNode, SyncState.SYNC);
GrAccess.addChangeListener(getNodeRelationListener(), rNode);
getNodesById().put(id, rNode);
}
return rNode;
}
public GrRelation getRelation(GrId id) {
if (id instanceof LocalId) {
return getLocalRelation(id.getId());
} else
return getRelationsById().get(id.getId());
}
private GrRelation getLocalRelation(long id) {
return this.localElements.getRelation(id);
}
public String getRelationType(long relationId, int rowIndex) {
return this.contentHandler.getRelationType(relationId, rowIndex);
}
public List<GrProperty> getNodeProperties(GrId nodeId, int rowIndex) {
if (nodeId instanceof LocalId)
return new ArrayList<GrProperty>();
else
return getNodeProperties(nodeId.getId(), rowIndex);
}
private List<GrProperty> getNodeProperties(long nodeId, int rowIndex) {
List<GrProperty> props = new ArrayList<GrProperty>();
Iterator<PropEntry> esIt = this.contentHandler.getPropertiesIterator(nodeId, rowIndex, ElemType.NODE);
while (esIt.hasNext()) {
PropEntry entry = esIt.next();
GrProperty prop = GrAccess.createProperty(entry.getPropName());
prop.setValue(this.contentHandler.convertContentValue(entry.getPropValue()));
GrAccess.setState(prop, SyncState.SYNC);
props.add(prop);
}
return props;
}
public List<GrLabel> getNodeLabels(long nodeId, int rowIndex) {
return this.contentHandler.getNodeLabels(nodeId, rowIndex);
}
public List<GrProperty> getRelationProperties(GrId relationId, int rowIndex) {
if (relationId instanceof LocalId)
return new ArrayList<GrProperty>();
else
return getRelationProperties(relationId.getId(), rowIndex);
}
private List<GrProperty> getRelationProperties(long relationId, int rowIndex) {
List<GrProperty> props = new ArrayList<GrProperty>();
Iterator<PropEntry> esIt = this.contentHandler.getPropertiesIterator(relationId, rowIndex, ElemType.RELATION);
while (esIt.hasNext()) {
PropEntry entry = esIt.next();
GrProperty prop = GrAccess.createProperty(entry.getPropName());
prop.setValue(this.contentHandler.convertContentValue(entry.getPropValue()));
GrAccess.setState(prop, SyncState.SYNC);
props.add(prop);
}
return props;
}
@SuppressWarnings("unchecked")
private <T> List<T> getValues(JcValue jcValue) {
String colKey;
if (jcValue instanceof JcProperty) {
WriterContext ctxt = new WriterContext();
ValueWriter.toValueExpression(jcValue, ctxt, ctxt.buffer);
colKey = ctxt.buffer.toString();
} else
colKey = ValueAccess.getName(jcValue);
List<T> vals = getValueColumns().get(colKey);
if (vals == null) {
vals = new ArrayList<T>();
Iterator<RowOrRecord> it = this.contentHandler.getDataIterator();
while(it.hasNext()) { // iterate over rows
RowOrRecord roc = it.next();
roc.addValue(colKey, vals);
}
getValueColumns().put(colKey, vals);
getUnresolvedColumns().remove(colKey);
}
return vals;
}
private Map<String, List<GrNode>> getNodeColumns() {
if (this.nodeColumns == null)
this.nodeColumns = new HashMap<String, List<GrNode>>();
return this.nodeColumns;
}
private Map<String, List<GrRelation>> getRelationColumns() {
if (this.relationColumns == null)
this.relationColumns = new HashMap<String, List<GrRelation>>();
return this.relationColumns;
}
private Map<String, List<GrPath>> getPathColumns() {
if (this.pathColumns == null)
this.pathColumns = new HashMap<String, List<GrPath>>();
return this.pathColumns;
}
@SuppressWarnings("rawtypes")
private Map<String, List> getValueColumns() {
if (this.valueColumns == null)
this.valueColumns = new HashMap<String, List>();
return this.valueColumns;
}
private Map<Long, GrNode> getNodesById() {
if (this.nodesById == null)
this.nodesById = new HashMap<Long, GrNode>();
return this.nodesById;
}
private Map<Long, GrRelation> getRelationsById() {
if (this.relationsById == null)
this.relationsById = new HashMap<Long, GrRelation>();
return this.relationsById;
}
private List<String> getUnresolvedColumns() {
if (this.unResolvedColumns == null) {
this.unResolvedColumns = new ArrayList<String>();
this.unResolvedColumns.addAll(this.contentHandler.getColumns());
}
return this.unResolvedColumns;
}
private NodeRelationListener getNodeRelationListener() {
if (this.nodeRelationListener == null)
this.nodeRelationListener = new NodeRelationListener();
return this.nodeRelationListener;
}
/**
* Update the underlying database with changes made on the graph
* @return a list of errors, which is empty if no errors occurred
*/
public List<JcError> store(Map<Long, Integer> elementVersionsMap) {
Map<GrNode, JcNumber> createdNodeToIdMap = new HashMap<GrNode, JcNumber>();
Map<GrRelation, JcNumber> createdRelationToIdMap = new HashMap<GrRelation, JcNumber>();
List<ElemId2Query> elemIds2Query = createUpdateQueries(createdNodeToIdMap, createdRelationToIdMap, elementVersionsMap);
Map<Long, Integer> elemId2ResultIndex = new HashMap<Long, Integer>();
List<JcQuery> queries = collectQueries(elemIds2Query, elemId2ResultIndex);
Util.printQueries(queries, QueryToObserve.UPDATE_QUERY, Format.PRETTY_1);
List<JcError> errors = new ArrayList<JcError>();
if (queries.size() > 0) {
List<JcQueryResult> results = dbAccess.execute(queries);
errors.addAll(Util.collectErrors(results));
if (errors.isEmpty()) { // success
JcError error;
if ((error = checkLockingError(results, !createdNodeToIdMap.isEmpty() || !createdRelationToIdMap.isEmpty(),
elemIds2Query)) != null) {
errors.add(error);
ITransaction tx = this.dbAccess.getTX();
if (tx != null)
tx.failure();
}
this.handleSyncState(results, createdNodeToIdMap,
createdRelationToIdMap, error != null, elemId2ResultIndex);
}
}
return errors;
}
private List<JcQuery> collectQueries(List<ElemId2Query> elemIds2Query, Map<Long, Integer> elemId2ResultIndex) {
List<JcQuery> ret = new ArrayList<JcQuery>(elemIds2Query.size());
for (ElemId2Query e2q : elemIds2Query) {
ret.add(e2q.query);
if (e2q.elemId >= 0)
elemId2ResultIndex.put(e2q.elemId, e2q.queryIndex);
}
return ret;
}
private List<ElemId2Query> createUpdateQueries(Map<GrNode, JcNumber> createdNodeToIdMap,
Map<GrRelation, JcNumber> createdRelationToIdMap, Map<Long, Integer> elementVersionsMap) {
QueryBuilder queryBuilder = new QueryBuilder();
List<ElemId2Query> elemIds2Query = queryBuilder.buildUpdateAndRemoveQueries(elementVersionsMap);
JcQuery createQuery = queryBuilder.buildCreateQuery(createdNodeToIdMap,
createdRelationToIdMap);
if (createQuery != null)
elemIds2Query.add(new ElemId2Query(-1, elemIds2Query.size(), createQuery));
return elemIds2Query;
}
/**
* in case of error return the element's id, or if not available return -2 in case of change or -3 in case of delete,
* in case of ok return -1
* @param results
* @param hasCreateQuery
* @param elemIds2Query
* @return
*/
private JcError checkLockingError(List<JcQueryResult> results, boolean hasCreateQuery, List<ElemId2Query> elemIds2Query) {
JcError error = null;
if (this.lockingStrategy == Locking.OPTIMISTIC) {
JcNumber lockV = new JcNumber("lockV");
JcNumber nSum = new JcNumber("sum");
int to = hasCreateQuery ? results.size() - 1 : results.size(); // don't check the result of the create query
for (int i = 0; i < to; i++) {
ElemId2Query elemId2Query = elemIds2Query.get(i);
if (elemId2Query.versionSum >= 0 && elemId2Query.elemId < 0) { // delete query
List<BigDecimal> ires = results.get(i).resultOf(nSum);
if (ires.size() > 0) {
if (((Number)ires.get(0)).intValue() != elemId2Query.versionSum) {
error = new JcError("JCypher.Locking", "Optimistic locking failed (an element was changed by another client)",
null);
break;
}
} else { // an element has been deleted
error = new JcError("JCypher.Locking", "Optimistic locking failed (an element was deleted by another client)",
null);
break;
}
} else { // change query
List<BigDecimal> ires = results.get(i).resultOf(lockV);
if (ires.size() > 0) {
int res = ires.get(0).intValue();
if (res == -2) {
error = new JcError("JCypher.Locking", "Optimistic locking failed (an element was changed by another client)",
"element id: " + elemId2Query.elemId);
break;
}
} else { // an element has been deleted
error = new JcError("JCypher.Locking", "Optimistic locking failed (an element was deleted by another client)",
"element id: " + elemId2Query.elemId);
break;
}
}
}
}
return error;
}
private void handleSyncState(List<JcQueryResult> results,
Map<GrNode, JcNumber> createdNodeToIdMap,
Map<GrRelation, JcNumber> createdRelationToIdMap,
boolean hasErrors, Map<Long, Integer> elemId2ResultIndex) {
List<Long> toRemove = new ArrayList<Long>();
Iterator<Entry<Long, GrNode>> nbyId = this.getNodesById().entrySet().iterator();
while(nbyId.hasNext()) {
Entry<Long, GrNode> entry = nbyId.next();
Integer idx = elemId2ResultIndex.get(entry.getKey());
checkRemovedSetSynchronized(toRemove, entry.getValue(), entry.getKey(),
hasErrors, idx != null ? results.get(idx) : null);
}
for (Long id : toRemove) {
this.getNodesById().remove(id);
}
this.changedNodesById = null;
toRemove.clear();
Iterator<Entry<Long, GrRelation>> rbyId = this.getRelationsById().entrySet().iterator();
while(rbyId.hasNext()) {
Entry<Long, GrRelation> entry = rbyId.next();
Integer idx = elemId2ResultIndex.get(entry.getKey());
checkRemovedSetSynchronized(toRemove, entry.getValue(), entry.getKey(),
hasErrors, idx != null ? results.get(idx) : null);
}
for (Long id : toRemove) {
this.getRelationsById().remove(id);
}
this.elementVersions = null;
this.changedRelationsById = null;
JcQueryResult createResult = results.get(results.size() - 1);
Iterator<Entry<GrNode, JcNumber>> nit = createdNodeToIdMap.entrySet().iterator();
while(nit.hasNext()) {
Entry<GrNode, JcNumber> entry = nit.next();
long id = exchangeGrId(entry.getKey(), entry.getValue(), createResult);
GrAccess.setToSynchronized(entry.getKey());
this.getNodesById().put(Long.valueOf(id), entry.getKey());
}
Iterator<Entry<GrRelation, JcNumber>> rit = createdRelationToIdMap.entrySet().iterator();
while (rit.hasNext()) {
Entry<GrRelation, JcNumber> entry = rit.next();
long id = exchangeGrId(entry.getKey(), entry.getValue(), createResult);
GrAccess.setToSynchronized(entry.getKey());
this.getRelationsById().put(Long.valueOf(id), entry.getKey());
}
this.localElements.clear();
GrAccess.setGraphState(getGraph(), SyncState.SYNC);
}
private void checkRemovedSetSynchronized(List<Long> toRemove,
PersistableItem item, Long id, boolean hasErrors,
JcQueryResult qResult) {
if (hasErrors) { // reset version property (can only be with optimistic locking)
Integer v;
if (this.elementVersions != null) { // there is at least one changed element
if (item instanceof GrNode) // elements to be removed are not added to elementVersions,
v = this.elementVersions.nodeVersions.get(item); // their version property is not changed
else
v = this.elementVersions.relationVersions.get(item);
if (v != null) { // for changed elements only
GrProperty vprop = ((GrPropertyContainer)item).getProperty(lockVersionProperty);
if (v == -1) // remove version property
vprop.remove();
else { // reset version property
vprop.setValue(v);
GrAccess.setToSynchronized(vprop);
}
}
}
} else {
if (writeVersion && lockingStrategy != Locking.OPTIMISTIC) {
// version in db may be different from element version, so sync element version
if (qResult != null) { // only for changed elements
JcNumber lockV = new JcNumber("lockV");
GrProperty vprop = ((GrPropertyContainer)item).getProperty(lockVersionProperty);
List<BigDecimal> lvr = qResult.resultOf(lockV);
if (lvr.size() > 0) {
int v = ((Number)lvr.get(0)).intValue();
int ov = ((Number)vprop.getValue()).intValue();
if (v != ov) {
vprop.setValue(v);
GrAccess.setToSynchronized(vprop);
}
} else { // has been deleted on db, reset to previous value
vprop.setValue(((Number)vprop.getValue()).intValue() - 1);
GrAccess.setToSynchronized(vprop);
}
}
}
if (GrAccess.getState(item) == SyncState.REMOVED)
toRemove.add(id);
else if (GrAccess.getState(item) != SyncState.SYNC)
GrAccess.setToSynchronized(item);
}
}
/**
* @param pc
* @param jcId
* @param createResult
* @return the id
*/
private long exchangeGrId(GrPropertyContainer pc, JcNumber jcId,
JcQueryResult createResult) {
List<BigDecimal> bdIds = createResult.resultOf(jcId);
long id = bdIds.get(0).longValue();
GrId grId = new GrId(id);
GrAccess.setGrId(grId, pc);
return id;
}
public void setDbAccess(IDBAccess dbAccess) {
this.dbAccess = dbAccess;
}
/**************************************/
public enum ElemType {
NODE, RELATION
}
/**************************************/
public static class ElementInfo {
private long id;
private ElemType type;
private boolean isNull;
public static ElementInfo parse(String selfString) {
ElementInfo ret = new ElementInfo();
ret.isNull = false;
int lidx = selfString.lastIndexOf('/');
ret.id = Long.parseLong(selfString.substring(lidx + 1));
String preString = selfString.substring(0, lidx);
lidx = preString.lastIndexOf('/');
String typeString;
if (lidx != -1)
typeString = preString.substring(lidx + 1);
else
typeString = preString;
if ("node".equals(typeString))
ret.type = ElemType.NODE;
else if ("relationship".equals(typeString))
ret.type = ElemType.RELATION;
return ret;
}
public static ElementInfo fromRecordValue(Value val) {
if (val instanceof ListValue)
return ElementInfo.fromRecordValue(((ListValue)val).get(0));
ElementInfo ret = null;
if (val != null) {
String typName = val.type().name(); // NODE, RELATIONSHIP, NULL
if ("NODE".equals(typName)) {
ret = new ElementInfo();
ret.isNull = false;
ret.id = val.asNode().id();
ret.type = ElemType.NODE;
} else if ("RELATIONSHIP".equals(typName)) {
ret = new ElementInfo();
ret.isNull = false;
ret.id = val.asRelationship().id();
ret.type = ElemType.RELATION;
} else if ("NULL".equals(typName))
ret = ElementInfo.nullElement();
}
return ret;
}
public static ElementInfo nullElement() {
ElementInfo ret = new ElementInfo();
ret.isNull = true;
return ret;
}
}
/**************************************/
public static class RelationInfo {
private long startNodeId;
private long endNodeId;
public static RelationInfo parse(String startString, String endString) {
RelationInfo ret = new RelationInfo();
ret.startNodeId = ret.parseId(startString);
ret.endNodeId = ret.parseId(endString);
return ret;
}
public static RelationInfo fromRecordValue(Value val) {
if (val instanceof ListValue)
return RelationInfo.fromRecordValue(((ListValue)val).get(0));
RelationInfo ret = null;
if (val != null) {
String typName = val.type().name(); // must be: RELATIONSHIP
if ("RELATIONSHIP".equals(typName)) {
ret = new RelationInfo();
ret.startNodeId = val.asRelationship().startNodeId();
ret.endNodeId = val.asRelationship().endNodeId();
}
}
return ret;
}
private long parseId(String str) {
int lidx = str.lastIndexOf('/');
return Long.parseLong(str.substring(lidx + 1));
}
}
/**************************************/
public static class PathInfo {
private long startNodeId;
private long endNodeId;
private List<Long> relationIds;
private Object contentObject;
public PathInfo(long startNodeId, long endNodeId, List<Long> relationIds,
Object userObject) {
super();
this.startNodeId = startNodeId;
this.endNodeId = endNodeId;
this.relationIds = relationIds;
this.contentObject = userObject;
}
public Object getContentObject() {
return contentObject;
}
public void setContentObject(Object userObject) {
this.contentObject = userObject;
}
}
/**************************************/
private class NodeRelationListener implements ChangeListener {
@Override
public void changed(Object theChanged, SyncState oldState,
SyncState newState) {
boolean possiblyReturnedToSync = false;
if (newState == SyncState.CHANGED || newState == SyncState.REMOVED) {
if (theChanged instanceof GrNode) {
if (changedNodesById == null)
changedNodesById = new HashMap<Long, GrNode>();
changedNodesById.put(((GrNode)theChanged).getId(), (GrNode)theChanged);
} else if (theChanged instanceof GrRelation) {
if (changedRelationsById == null)
changedRelationsById = new HashMap<Long, GrRelation>();
changedRelationsById.put(((GrRelation)theChanged).getId(), (GrRelation)theChanged);
}
if (GrAccess.getGraphState(getGraph()) == SyncState.SYNC)
GrAccess.setGraphState(getGraph(), SyncState.CHANGED);
} else if (newState == SyncState.SYNC) {
if (theChanged instanceof GrNode) {
if (changedNodesById != null)
changedNodesById.remove(((GrNode)theChanged).getId());
} else if (theChanged instanceof GrRelation) {
if (changedRelationsById != null)
changedRelationsById.remove(((GrRelation)theChanged).getId());
}
} else if (newState == SyncState.NEW) {
if (GrAccess.getGraphState(getGraph()) == SyncState.SYNC)
GrAccess.setGraphState(getGraph(), SyncState.CHANGED);
} else if (newState == SyncState.NEW_REMOVED) {
if (theChanged instanceof GrNode) {
localElements.removeNode(((GrNode)theChanged).getId());
} else if (theChanged instanceof GrRelation) {
localElements.removeRelation(((GrRelation)theChanged).getId());
}
}
if (newState == SyncState.SYNC || newState == SyncState.NEW_REMOVED) {
if ((changedNodesById == null || changedNodesById.size() == 0) &&
(changedRelationsById == null || changedRelationsById.size() == 0) &&
localElements.isEmpty())
possiblyReturnedToSync = true;
}
if (possiblyReturnedToSync && GrAccess.getGraphState(getGraph()) == SyncState.CHANGED) {
GrAccess.setGraphState(getGraph(), SyncState.SYNC);
}
}
}
/**************************************/
private class QueryBuilder {
/**
* @param createdNodeIds
* @return a Query to create elements
*/
JcQuery buildCreateQuery(Map<GrNode, JcNumber> createdNodeToIdMap,
Map<GrRelation, JcNumber> createdRelationToIdMap) {
List<GrNode2JcNode> nodesToCreate = new ArrayList<GrNode2JcNode>();
List<IClause> createNodeClauses = new ArrayList<IClause>();
Map<GrNode, JcNode> localNodeMap = new HashMap<GrNode, JcNode>();
for (GrNode node : localElements.getLocalNodes()) {
addCreateNodeClause(node, createNodeClauses,
localNodeMap, nodesToCreate);
}
List<GrRelation2JcRelation> relationsToCreate = new ArrayList<GrRelation2JcRelation>();
List<IClause> startNodeClauses = new ArrayList<IClause>();
List<IClause> createRelationClauses = new ArrayList<IClause>();
Map<GrNode, JcNode> dbNodeMap = new HashMap<GrNode, JcNode>();
for (GrRelation relation : localElements.getLocalRelations()) {
addCreateRelationClause(relation, createRelationClauses,
localNodeMap, dbNodeMap, startNodeClauses, relationsToCreate);
}
List<IClause> clauses = startNodeClauses;
clauses.addAll(createNodeClauses);
clauses.addAll(createRelationClauses);
for (GrNode2JcNode grn2jcn : nodesToCreate) {
JcNumber nid = new JcNumber("NID_".concat(ValueAccess.getName(grn2jcn.jcNode)));
createdNodeToIdMap.put(grn2jcn.grNode, nid);
clauses.add(RETURN.value(grn2jcn.jcNode.id()).AS(nid));
}
for (GrRelation2JcRelation grr2jcr : relationsToCreate) {
JcNumber rid = new JcNumber("RID_".concat(ValueAccess.getName(grr2jcr.jcRelation)));
createdRelationToIdMap.put(grr2jcr.grRelation, rid);
clauses.add(RETURN.value(grr2jcr.jcRelation.id()).AS(rid));
}
JcQuery ret = null;
if (clauses.size() > 0) {
IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]);
ret = new JcQuery();
ret.setClauses(clausesArray);
}
return ret;
}
List<ElemId2Query> buildUpdateAndRemoveQueries(Map<Long, Integer> elementVersionsMap) {
List<ElemId2Query> ret = new ArrayList<ElemId2Query>();
List<GrPropertyContainer> removedNodes = new ArrayList<GrPropertyContainer>();
if (changedNodesById != null) {
Iterator<GrNode> nit = changedNodesById.values().iterator();
while (nit.hasNext()) {
GrNode node = nit.next();
SyncState state = GrAccess.getState(node);
if (state == SyncState.CHANGED) {
ret.add(
new ElemId2Query(node.getId(), ret.size(),
buildChangedNodeOrRelationQuery(node, elementVersionsMap)));
} else if (state == SyncState.REMOVED) {
removedNodes.add(node);
}
}
}
List<GrPropertyContainer> removedRelations = new ArrayList<GrPropertyContainer>();
if (changedRelationsById != null) {
Iterator<GrRelation> rit = changedRelationsById.values().iterator();
while (rit.hasNext()) {
GrRelation relation = rit.next();
SyncState state = GrAccess.getState(relation);
if (state == SyncState.CHANGED) {
ret.add(
new ElemId2Query(relation.getId(), ret.size(),
buildChangedNodeOrRelationQuery(relation, elementVersionsMap)));
} else if (state == SyncState.REMOVED) {
removedRelations.add(relation);
}
}
}
if (removedRelations.size() > 0) {
ElemId2Query elemId2Query = new ElemId2Query(-1, ret.size(), null);
buildRemovedNodeOrRelationQuery(removedRelations, elemId2Query);
ret.add(elemId2Query);
}
if (removedNodes.size() > 0) {
ElemId2Query elemId2Query = new ElemId2Query(-1, ret.size(), null);
buildRemovedNodeOrRelationQuery(removedNodes, elemId2Query);
ret.add(elemId2Query);
}
return ret;
}
private void addCreateNodeClause(GrNode node,
List<IClause> clauses, Map<GrNode, JcNode> localNodeMap,
List<GrNode2JcNode> nodesToCreate) {
String nm = "ln_".concat(String.valueOf(clauses.size()));
JcNode n = new JcNode(nm);
nodesToCreate.add(new GrNode2JcNode(node, n));
Node create = CREATE.node(n);
for (GrLabel label : node.getLabels()) {
create = create.label(label.getName());
}
for (GrProperty prop : node.getProperties()) {
create = create.property(prop.getName()).value(prop.getValue());
}
if (writeVersion || lockingStrategy == Locking.OPTIMISTIC)
create = create.property(ResultHandler.lockVersionProperty).value(0);
clauses.add(create);
localNodeMap.put(node, n);
}
private void addCreateRelationClause(GrRelation relation,
List<IClause> createRelationClauses,
Map<GrNode, JcNode> localNodeMap, Map<GrNode, JcNode> dbNodeMap,
List<IClause> startNodeClauses, List<GrRelation2JcRelation> relationsToCreate) {
String nm = "lr_".concat(String.valueOf(createRelationClauses.size()));
JcRelation r = new JcRelation(nm);
relationsToCreate.add(new GrRelation2JcRelation(relation, r));
GrNode sNode = relation.getStartNode();
GrNode eNode = relation.getEndNode();
JcNode sn = getNode(sNode, localNodeMap, dbNodeMap, startNodeClauses);
JcNode en = getNode(eNode, localNodeMap, dbNodeMap, startNodeClauses);
Relation create = CREATE.node(sn).relation(r).out();
if (relation.getType() != null)
create = create.type(relation.getType());
for (GrProperty prop : relation.getProperties()) {
create = create.property(prop.getName()).value(prop.getValue());
}
if (writeVersion || lockingStrategy == Locking.OPTIMISTIC)
create = create.property(ResultHandler.lockVersionProperty).value(0);
createRelationClauses.add(create.node(en));
}
private JcNode getNode(GrNode grNode, Map<GrNode, JcNode> localNodeMap,
Map<GrNode, JcNode> dbNodeMap, List<IClause> startNodeClauses) {
GrId grId = GrAccess.getGrId(grNode);
JcNode n;
if (grId instanceof LocalId) {
n = localNodeMap.get(grNode);
} else {
n = dbNodeMap.get(grNode);
if (n == null) {
String nm = "rn_".concat(String.valueOf(startNodeClauses.size()));
n = new JcNode(nm);
StartPoint start = START.node(n).byId(grId.getId());
startNodeClauses.add(start);
dbNodeMap.put(grNode, n);
}
}
return n;
}
private void buildRemovedNodeOrRelationQuery(List<GrPropertyContainer> elements, ElemId2Query elemId2Query) {
List<IClause> clauses = new ArrayList<IClause>();
List<IClause> removeClauses = new ArrayList<IClause>();
List<String> elemNames = new ArrayList<String>(elements.size());
for (int i = 0; i < elements.size(); i++) {
String nm = "elem_".concat(String.valueOf(i));
elemNames.add(nm);
if (elements.get(0) instanceof GrNode) {
JcNode elem = new JcNode(nm);
//clauses.add(START.node(elem).byId(elements.get(i).getId()));
// use OPTIONAL_MATCH to be tolerant for removed elements
clauses.add(OPTIONAL_MATCH.node(elem));
clauses.add(WHERE.valueOf(elem.id()).EQUALS(elements.get(i).getId()));
} else if (elements.get(0) instanceof GrRelation) {
JcRelation elem = new JcRelation(nm);
//clauses.add(START.relation(elem).byId(elements.get(i).getId()));
// use OPTIONAL_MATCH to be tolerant for removed elements
clauses.add(OPTIONAL_MATCH.node().relation(elem).out().node());
clauses.add(WHERE.valueOf(elem.id()).EQUALS(elements.get(i).getId()));
}
}
LockUtil.Removes removes = new LockUtil.Removes();
int idx = 0;
for (String nm : elemNames) {
JcElement elem = null;
if (elements.get(0) instanceof GrNode)
elem = new JcNode(nm);
else if (elements.get(0) instanceof GrRelation)
elem = new JcRelation(nm);
int nodeVersion = -1;
GrProperty prop = elements.get(idx).getProperty(lockVersionProperty);
if (prop != null)
nodeVersion = ((Number)prop.getValue()).intValue();
if (lockingStrategy == Locking.OPTIMISTIC) {
LockUtil.calcRemoves(removes, elem, nodeVersion);
}
removeClauses.add(DO.DELETE(elem));
idx++;
}
if (removes.getWithClauses() != null) {
JcNumber nSum = new JcNumber("sum");
removes.getWithClauses().add(WITH.value(removes.getSum()).AS(nSum));
clauses.addAll(removes.getWithClauses());
JcValue x = new JcValue("x");
// conditional remove in case of Locking.OPTIONAL
IClause clause = FOR_EACH.element(x).IN(C.CREATE(new IClause[]{
CASE.result(),
WHEN.valueOf(nSum).EQUALS(removes.getVersionSum()),
NATIVE.cypher("[1]"),
ELSE.perform(),
NATIVE.cypher("[]"),
END.caseXpr()
})).DO(removeClauses.toArray(new IClause[removeClauses.size()]));
clauses.add(clause);
clauses.add(RETURN.value(nSum));
} else
clauses.addAll(removeClauses);
IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]);
JcQuery query = new JcQuery();
//query.setExtractParams(false);
query.setClauses(clausesArray);
elemId2Query.versionSum = removes.getVersionSum();
elemId2Query.query = query;
}
private JcQuery buildChangedNodeOrRelationQuery(GrPropertyContainer element,
Map<Long, Integer> elementVersionsMap) {
int nodeVersion = -1;
if (elementVersionsMap != null) { // only when coming from DomainAccess
// with Locking.OTIMISTIC
Integer v = elementVersionsMap.get(element.getId());
if (v != null)
nodeVersion = v;
} else {
GrProperty prop = element.getProperty(lockVersionProperty);
if (prop != null)
nodeVersion = ((Number)prop.getValue()).intValue();
}
handleVersionProperty(element, nodeVersion);
List<IClause> clauses = new ArrayList<IClause>();
JcElement elem = null;
if (element instanceof GrNode)
elem = new JcNode("elem");
else if (element instanceof GrRelation)
elem = new JcRelation("elem");
List<IClause> startClause = buildStartClause(element);
clauses.addAll(buildChangedPropertiesClauses(element));
if (writeVersion || lockingStrategy == Locking.OPTIMISTIC)
clauses.add(
DO.SET(elem.property(lockVersionProperty)).byExpression(elem.numberProperty(lockVersionProperty).plus(1)));
clauses.addAll(buildChangedLabelsClauses(element));
IClause[] clausesArray;
JcNumber lockV = new JcNumber("lockV");
if (lockingStrategy == Locking.OPTIMISTIC && nodeVersion >= 0) {
clausesArray = clauses.toArray(new IClause[clauses.size()]);
clauses.clear();
clauses.addAll(startClause);
JcValue x = new JcValue("x");
IClause clause = FOR_EACH.element(x).IN(C.CREATE(new IClause[]{
CASE.result(),
WHEN.valueOf(elem.property(lockVersionProperty)).NOT_EQUALS(nodeVersion),
NATIVE.cypher("[1]"),
ELSE.perform(),
NATIVE.cypher("[]"),
END.caseXpr()
})).DO().SET(elem.property(lockVersionProperty)).to(-2);
clauses.add(clause);
clause = FOR_EACH.element(x).IN(C.CREATE(new IClause[]{
CASE.result(),
WHEN.valueOf(elem.property(lockVersionProperty)).EQUALS(nodeVersion),
NATIVE.cypher("[1]"),
ELSE.perform(),
NATIVE.cypher("[]"),
END.caseXpr()
})).DO(clausesArray);
clauses.add(clause);
} else
clauses.addAll(0, startClause);
if (writeVersion || lockingStrategy == Locking.OPTIMISTIC) {
JcNumber elemId = new JcNumber("elemId");
clauses.add(RETURN.value(elem.property(lockVersionProperty)).AS(lockV));
clauses.add(RETURN.value(elem.id()).AS(elemId));
}
clausesArray = clauses.toArray(new IClause[clauses.size()]);
JcQuery query = new JcQuery();
query.setClauses(clausesArray);
return query;
}
private Collection<? extends IClause> buildChangedLabelsClauses(
GrPropertyContainer element) {
List<IClause> ret = new ArrayList<IClause>();
if (element instanceof GrNode) {
GrNode node = (GrNode)element;
List<GrLabel> modified = GrAccess.getModifiedLabels(node);
Iterator<GrLabel> lit = modified.iterator();
while (lit.hasNext()) {
GrLabel lab = lit.next();
SyncState state = GrAccess.getState(lab);
JcNode elem = new JcNode("elem");
IClause c = null;
// a label can only be created and added or it can be removed
// but a label can never be changed
if (state == SyncState.NEW) {
c = DO.SET(elem.label(lab.getName()));
} else if (state == SyncState.REMOVED) {
c = DO.REMOVE(elem.label(lab.getName()));
}
ret.add(c);
}
}
return ret;
}
private Collection<? extends IClause> buildChangedPropertiesClauses(
GrPropertyContainer element) {
List<IClause> ret = new ArrayList<IClause>();
List<GrProperty> modified = GrAccess.getModifiedProperties(element);
Iterator<GrProperty> pit = modified.iterator();
while(pit.hasNext()) {
GrProperty prop = pit.next();
if (!prop.getName().equals(lockVersionProperty)) { // version property is handled differently
SyncState state = GrAccess.getState(prop);
JcElement elem = null;
if (element instanceof GrNode)
elem = new JcNode("elem");
else
elem = new JcRelation("elem");
IClause c = null;
if (state == SyncState.CHANGED || state == SyncState.NEW) {
Object propValue = prop.getValue();
if (propValue != null)
c = DO.SET(elem.property(prop.getName())).to(prop.getValue());
else
c = DO.SET(elem.property(prop.getName())).toNull();
} else if (state == SyncState.REMOVED) {
c = DO.REMOVE(elem.property(prop.getName()));
}
ret.add(c);
}
}
return ret;
}
private List<IClause> buildStartClause(GrPropertyContainer element) {
List<IClause> ret = new ArrayList<IClause>();
JcElement elem;
if (element instanceof GrNode) {
elem = new JcNode("elem");
//ret = START.node(elem).byId(id);
ret.add(OPTIONAL_MATCH.node((JcNode) elem));
} else {
elem = new JcRelation("elem");
//ret = START.relation(elem).byId(id);
ret.add(OPTIONAL_MATCH.node().relation((JcRelation) elem).out().node());
}
ret.add(WHERE.valueOf(elem.id()).EQUALS(element.getId()));
return ret;
}
private void handleVersionProperty(GrPropertyContainer element, int curVersion) {
if (writeVersion || lockingStrategy == Locking.OPTIMISTIC) {
if (elementVersions == null)
elementVersions = new ElementVersions();
int oldVersion = curVersion;
GrProperty prop = element.getProperty(ResultHandler.lockVersionProperty);
if (prop != null)
prop.setValue(oldVersion + 1);
else
prop = element.addProperty(ResultHandler.lockVersionProperty, oldVersion + 1);
if (element instanceof GrNode)
elementVersions.nodeVersions.put(element, oldVersion);
else if (element instanceof GrRelation)
elementVersions.relationVersions.put(element, oldVersion);
}
}
/*************************************/
private class GrNode2JcNode {
private GrNode grNode;
private JcNode jcNode;
private GrNode2JcNode(GrNode grNode, JcNode jcNode) {
super();
this.grNode = grNode;
this.jcNode = jcNode;
}
}
/*************************************/
private class GrRelation2JcRelation {
private GrRelation grRelation;
private JcRelation jcRelation;
private GrRelation2JcRelation(GrRelation grRelation, JcRelation jcRelation) {
super();
this.grRelation = grRelation;
this.jcRelation = jcRelation;
}
}
}
/**************************************/
private class ElementVersions {
private ElementVersions() {
super();
this.nodeVersions = new HashMap<GrPropertyContainer, Integer>();
this.relationVersions = new HashMap<GrPropertyContainer, Integer>();
}
private Map<GrPropertyContainer, Integer> nodeVersions;
private Map<GrPropertyContainer, Integer> relationVersions;
}
/**************************************/
private class ElemId2Query {
private long elemId;
private int queryIndex;
private int versionSum;
private JcQuery query;
private ElemId2Query(long elemId, int queryIndex, JcQuery query) {
super();
this.elemId = elemId;
this.queryIndex = queryIndex;
this.query = query;
this.versionSum = -1;
}
}
/**************************************/
public class LocalElements {
private LocalIdBuilder nodeIdBuilder;
private LocalIdBuilder relationIdBuilder;
private Map<Long, GrNode> localNodesById;
private Map<Long, GrRelation> localRelationsById;
public GrNode createNode() {
if (this.nodeIdBuilder == null)
this.nodeIdBuilder = new LocalIdBuilder();
LocalId lid =new LocalId(this.nodeIdBuilder.getId());
GrNode node = GrAccess.createNode(ResultHandler.this, lid, -1);
if (this.localNodesById == null)
this.localNodesById = new HashMap<Long, GrNode>();
GrAccess.addChangeListener(getNodeRelationListener(), node);
this.localNodesById.put(lid.getId(), node);
GrAccess.notifyState(node);
return node;
}
public GrRelation createRelation(String type, GrNode startNode, GrNode endNode) {
if (this.relationIdBuilder == null)
this.relationIdBuilder = new LocalIdBuilder();
LocalId lid =new LocalId(this.relationIdBuilder.getId());
GrRelation relation = GrAccess.createRelation(ResultHandler.this, lid,
GrAccess.getGrId(startNode), GrAccess.getGrId(endNode), type);
if (this.localRelationsById == null)
this.localRelationsById = new HashMap<Long, GrRelation>();
GrAccess.addChangeListener(getNodeRelationListener(), relation);
this.localRelationsById.put(lid.getId(), relation);
GrAccess.notifyState(relation);
return relation;
}
private GrNode getNode(long id) {
if (this.localNodesById != null)
return this.localNodesById.get(id);
return null;
}
private GrRelation getRelation(long id) {
if (this.localRelationsById != null)
return this.localRelationsById.get(id);
return null;
}
private void removeNode(long id) {
if (this.localNodesById != null)
this.localNodesById.remove(id);
}
private void removeRelation(long id) {
if (this.localRelationsById != null)
this.localRelationsById.remove(id);
}
private List<GrNode> getLocalNodes() {
List<GrNode> ret = new ArrayList<GrNode>();
if (this.localNodesById != null) {
for (GrNode node : this.localNodesById.values()) {
ret.add(node);
}
}
return ret;
}
private List<GrRelation> getLocalRelations() {
List<GrRelation> ret = new ArrayList<GrRelation>();
if (this.localRelationsById != null) {
for (GrRelation relation : this.localRelationsById.values()) {
ret.add(relation);
}
}
return ret;
}
private void clear() {
this.nodeIdBuilder = null;
this.localNodesById = null;
this.relationIdBuilder = null;
this.localRelationsById = null;
}
public boolean isEmpty() {
return (this.localNodesById == null || this.localNodesById.size() == 0) &&
(this.localRelationsById == null || this.localRelationsById.size() == 0);
}
}
/**************************************/
public static abstract class AContentHandler {
public abstract List<String> getColumns();
public abstract int getColumnIndex(String colKey);
public abstract Iterator<RowOrRecord> getDataIterator();
public abstract Object convertContentValue(Object val);
public abstract Iterator<PropEntry> getPropertiesIterator(long id, int rowIndex, ElemType typ);
public abstract String getRelationType(long relationId, int rowIndex);
public abstract List<GrLabel> getNodeLabels(long nodeId, int rowIndex);
/*********************************************/
public static class PropEntry {
private String propName;
private Object propValue;
public PropEntry(String propName, Object propValue) {
super();
this.propName = propName;
this.propValue = propValue;
}
public String getPropName() {
return propName;
}
public Object getPropValue() {
return propValue;
}
}
/*********************************************/
public abstract class RowOrRecord {
public abstract ElementInfo getElementInfo(String colKey);
public abstract RelationInfo getRelationInfo(String colKey);
public abstract PathInfo getPathInfo(String colKey);
public abstract long gePathtNodeIdAt(PathInfo pathInfo, int index);
public abstract <T> void addValue(String colKey, List<T> vals);
}
}
}