/************************************************************************
* Copyright (c) 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.value.NodeValue;
import org.neo4j.driver.internal.value.RelationshipValue;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
import org.neo4j.driver.v1.types.Entity;
import org.neo4j.driver.v1.types.Node;
import org.neo4j.driver.v1.types.Path;
import org.neo4j.driver.v1.types.Relationship;
import org.neo4j.driver.v1.types.Type;
import org.neo4j.driver.v1.util.Pair;
import iot.jcypher.database.IDBAccess;
import iot.jcypher.graph.GrAccess;
import iot.jcypher.graph.GrLabel;
import iot.jcypher.graph.SyncState;
import iot.jcypher.query.InternalQAccess;
import iot.jcypher.query.JcQuery;
import iot.jcypher.query.JcQueryResult;
import iot.jcypher.query.api.IClause;
import iot.jcypher.query.factories.clause.RETURN;
import iot.jcypher.query.factories.clause.START;
import iot.jcypher.query.result.util.ResultHandler.AContentHandler;
import iot.jcypher.query.result.util.ResultHandler.ElemType;
import iot.jcypher.query.result.util.ResultHandler.ElementInfo;
import iot.jcypher.query.result.util.ResultHandler.PathInfo;
import iot.jcypher.query.result.util.ResultHandler.RelationInfo;
import iot.jcypher.query.values.JcNode;
import iot.jcypher.query.values.JcRelation;
import iot.jcypher.util.ResultSettings;
import iot.jcypher.util.Util;
public class BoltContentHandler extends AContentHandler {
private List<Record> records;
private Reloaded reloaded;
public BoltContentHandler(StatementResult statementResult, ResultHandler rh) {
if (statementResult != null)
this.records = statementResult.list(); // don't use streaming mode
else
this.records = new ArrayList<Record>();
this.reloaded = new Reloaded(rh.getDbAccess());
}
@Override
public List<String> getColumns() {
List<String> columns = new ArrayList<String>();
if (this.records.size() > 0) {
Record rec = this.records.get(0);
columns.addAll(rec.keys());
}
return columns;
}
@Override
public int getColumnIndex(String colKey) {
if (this.records.size() > 0) {
Record rec = this.records.get(0);
int idx;
try {
idx = rec.index(colKey);
} catch (NoSuchElementException e) {
idx = -1;
}
return idx;
}
return -1;
}
@Override
public Iterator<RowOrRecord> getDataIterator() {
return new RecordIterator();
}
@Override
public Object convertContentValue(Object value) {
if (value instanceof Value) {
Value val = (Value) value;
Object ret = null;
Type typ = val.type();
InternalTypeSystem ts = InternalTypeSystem.TYPE_SYSTEM;
if (typ == ts.NUMBER())
ret = new BigDecimal(val.asNumber().toString());
else if (typ == ts.INTEGER())
ret = new BigDecimal(val.asNumber().toString());
else if (typ == ts.FLOAT())
ret = new BigDecimal(val.asNumber().toString());
else if (typ == ts.STRING())
ret = val.asString();
else if (typ == ts.BOOLEAN())
ret = Boolean.valueOf(val.asBoolean());
else if (typ == ts.LIST()) {
List<Object> orgVals = val.asList();
List<Object> vals = new ArrayList<Object>();
int sz = orgVals.size();
for (int i = 0; i < sz; i++) {
Object v = orgVals.get(i);
vals.add(convertContentValue(v));
}
ret = vals;
} else if (typ == ts.ANY()) {
ret = val.asObject();
}
return ret;
}
return value;
}
@Override
public Iterator<PropEntry> getPropertiesIterator(long id, int rowIndex, ElemType typ) {
Entity propertiesObject = getPropertiesObject(id, rowIndex, typ);
Iterator<Entry<String, Object>> esIt = propertiesObject.asMap().entrySet().iterator();
return new PropertiesIterator(esIt);
}
@Override
public String getRelationType(long relationId, int rowIndex) {
if (rowIndex >= 0) {
Relationship rel = (Relationship) getPropertiesObject(relationId, rowIndex, ElemType.RELATION);
return rel.type();
}
return null;
}
@Override
public List<GrLabel> getNodeLabels(long nodeId, int rowIndex) {
List<GrLabel> labels = new ArrayList<GrLabel>();
if (rowIndex >= 0) {
Node nd = (Node) getPropertiesObject(nodeId, rowIndex, ElemType.NODE);
for (String lab : nd.labels()) {
GrLabel label = GrAccess.createLabel(lab);
GrAccess.setState(label, SyncState.SYNC);
labels.add(label);
}
}
return labels;
}
private Entity getPropertiesObject(long id, int rowIndex, ElemType typ) {
Record rec = this.records.get(rowIndex);
List<Pair<String, Value>> flds = rec.fields();
for (Pair<String, Value> pair : flds) {
if (typ == ElemType.NODE && pair.value() instanceof NodeValue) {
Node nd = pair.value().asNode();
if (nd.id() == id)
return nd;
} else if (typ == ElemType.RELATION && pair.value() instanceof RelationshipValue) {
Relationship rel = pair.value().asRelationship();
if (rel.id() == id)
return rel;
}
}
// element with id may not have been loaded
return this.reloaded.getEntity(id, typ);
}
/************************************/
public class RecordIterator implements Iterator<RowOrRecord> {
private Iterator<Record> recordIterator;
public RecordIterator() {
super();
this.recordIterator = records.iterator();
}
@Override
public boolean hasNext() {
return this.recordIterator.hasNext();
}
@Override
public RowOrRecord next() {
Record nextVal = this.recordIterator.next();
return new Rec(nextVal);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/*****************************/
public class Rec extends RowOrRecord {
private Record record;
public Rec(Record record) {
super();
this.record = record;
}
@Override
public ElementInfo getElementInfo(String colKey) {
Value val;
try {
val = this.record.get(colKey);
} catch (NoSuchRecordException e) {
throw new RuntimeException("no result column: " + colKey);
}
return ElementInfo.fromRecordValue(val);
}
@Override
public RelationInfo getRelationInfo(String colKey) {
Value val = this.record.get(colKey);
return RelationInfo.fromRecordValue(val);
}
@Override
public PathInfo getPathInfo(String colKey) {
PathInfo pathInfo = null;
Value val;
try {
val = this.record.get(colKey);
} catch (NoSuchRecordException e) {
throw new RuntimeException("no result column: " + colKey);
}
String typName = val.type().name();
if ("PATH".equals(typName)) {
Path p = val.asPath();
long startId = p.start().id();
long endId = p.end().id();
List<Long> relIds = new ArrayList<Long>();
Iterator<Relationship> it = p.relationships().iterator();
while(it.hasNext()) {
Relationship rel = it.next();
relIds.add(Long.valueOf(rel.id()));
}
pathInfo = new PathInfo(startId, endId, relIds, p);
}
return pathInfo;
}
@SuppressWarnings("unchecked")
@Override
public long gePathtNodeIdAt(PathInfo pathInfo, int index) {
Object obj = pathInfo.getContentObject();
List<Node> nodes = null;
if (obj instanceof List<?>)
nodes = (List<Node>) obj;
else if (obj instanceof Path) {
nodes = new ArrayList<Node>();
Iterator<Node> it = ((Path)obj).nodes().iterator();
while(it.hasNext())
nodes.add(it.next());
pathInfo.setContentObject(nodes);
}
return nodes.get(index).id();
}
@SuppressWarnings("unchecked")
@Override
public <T> void addValue(String colKey, List<T> vals) {
Value val;
try {
val = this.record.get(colKey);
} catch (NoSuchRecordException e) {
throw new RuntimeException("no result column: " + colKey);
}
Object v = convertContentValue(val);
if (v != null)
vals.add((T) v);
else {
if (ResultHandler.includeNullValues.get().booleanValue()
|| ResultSettings.includeNullValuesAndDuplicates.get().booleanValue())
vals.add((T) v);
}
}
}
}
/************************************/
public class PropertiesIterator implements Iterator<PropEntry> {
private Iterator<Entry<String, Object>> iterator;
public PropertiesIterator(Iterator<Entry<String, Object>> iterator) {
super();
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public PropEntry next() {
Entry<String, Object> next = this.iterator.next();
return new PropEntry(next.getKey(), next.getValue());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/************************************/
private class Reloaded {
private Map<Long, Node> nodesById;
private Map<Long, Relationship> relationsById;
private IDBAccess dbAccess;
private Reloaded(IDBAccess dba) {
super();
this.dbAccess = dba;
}
private Entity getEntity(long id, ElemType typ) {
if (typ == ElemType.NODE)
return this.getNode(id);
else
return this.getRelation(id);
}
private Node getNode(long id) {
if (this.nodesById == null)
this.nodesById = new HashMap<Long, Node>();
Node ret = this.nodesById.get(id);
if (ret == null) {
ret = (Node) this.loadPropertiesObject(id, ElemType.NODE);
this.nodesById.put(id, ret);
}
return ret;
}
private Relationship getRelation(long id) {
if (this.relationsById == null)
this.relationsById = new HashMap<Long, Relationship>();
Relationship ret = this.relationsById.get(id);
if (ret == null) {
ret = (Relationship) this.loadPropertiesObject(id, ElemType.RELATION);
this.relationsById.put(id, ret);
}
return ret;
}
private Entity loadPropertiesObject(long id, ElemType typ) {
IClause[] clauses;
JcNode n;
JcRelation r;
if (typ == ElemType.NODE) {
n = new JcNode("n");
clauses = new IClause[] {
START.node(n).byId(id),
RETURN.value(n)
};
} else {
r = new JcRelation("r");
clauses = new IClause[] {
START.relation(r).byId(id),
RETURN.value(r)
};
}
JcQuery q = new JcQuery();
q.setClauses(clauses);
JcQueryResult result = this.dbAccess.execute(q);
if (result.hasErrors()) {
StringBuilder sb = new StringBuilder();
Util.appendErrorList(Util.collectErrors(result), sb);
throw new RuntimeException(sb.toString());
}
BoltContentHandler ch = (BoltContentHandler) InternalQAccess.getResultHandler(result).getContentHandler();
return ch.getPropertiesObject(id, 0, typ);
}
}
}