diff --git a/src/main/java/com/microsoft/spring/data/gremlin/common/Constants.java b/src/main/java/com/microsoft/spring/data/gremlin/common/Constants.java index 418c1997..433a7df1 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/common/Constants.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/common/Constants.java @@ -15,9 +15,11 @@ public class Constants { public static final String PROPERTY_LABEL = "label"; public static final String PROPERTY_TYPE = "type"; public static final String PROPERTY_VALUE = "value"; + public static final String PROPERTY_VALUE_WITH_AT = "@value"; public static final String PROPERTY_PROPERTIES = "properties"; public static final String PROPERTY_INV = "inV"; public static final String PROPERTY_OUTV = "outV"; + public static final String PROPERTY_RELATION_ID = "relationId"; public static final String RESULT_TYPE_VERTEX = "vertex"; public static final String RESULT_TYPE_EDGE = "edge"; diff --git a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/AbstractGremlinResultReader.java b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/AbstractGremlinResultReader.java index 1b7881b1..d01ff8f8 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/AbstractGremlinResultReader.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/AbstractGremlinResultReader.java @@ -15,10 +15,40 @@ import java.util.LinkedHashMap; import java.util.Map; +import static com.microsoft.spring.data.gremlin.common.Constants.PROPERTY_VALUE_WITH_AT; + @NoArgsConstructor -// TODO: seems only for Vertex. public abstract class AbstractGremlinResultReader { + /** + * Vertex's properties returned from gremlin-driver has a complicated data structure + * This function helps to renovate it to a simple Map + * @return Map of list properties + */ + protected Map getProperties (@NonNull Map map) { + Map propertyMap = map; + while ((propertyMap instanceof LinkedHashMap) && propertyMap.containsKey(PROPERTY_VALUE_WITH_AT)) { + final Object value = propertyMap.get(PROPERTY_VALUE_WITH_AT); + if (value instanceof ArrayList && ((ArrayList) value).size() > 0) { + propertyMap = (Map) ((ArrayList) value).get(0); + } else { + propertyMap = (Map) value; + } + } + + return propertyMap; + } + + protected Object getPropertyValue (@NonNull Map map, @NonNull String propertyKey) { + Object value = map.get(propertyKey); + + while ((value instanceof LinkedHashMap) && ((LinkedHashMap) value).containsKey(PROPERTY_VALUE_WITH_AT)) { + value = ((LinkedHashMap) value).get(PROPERTY_VALUE_WITH_AT); + } + + return value; + } + /** * properties's organization is a little complicated. *

@@ -28,14 +58,18 @@ public abstract class AbstractGremlinResultReader { * T is LinkedHashMap */ private Object readProperty(@NonNull Object value) { - Assert.isInstanceOf(ArrayList.class, value, "should be instance of ArrayList"); + if (value instanceof ArrayList) { + @SuppressWarnings("unchecked") final ArrayList> mapList + = (ArrayList>) value; + + Assert.isTrue(mapList.size() == 1, "should be only 1 element in ArrayList"); - @SuppressWarnings("unchecked") final ArrayList> mapList - = (ArrayList>) value; + value = mapList.get(0); + } - Assert.isTrue(mapList.size() == 1, "should be only 1 element in ArrayList"); + final Map renovatedMap = getProperties((Map) value); - return mapList.get(0).get(Constants.PROPERTY_VALUE); + return renovatedMap.get(Constants.PROPERTY_VALUE); } protected void readResultProperties(@NonNull Map properties, @NonNull GremlinSource source) { diff --git a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultEdgeReader.java b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultEdgeReader.java index a0d3341d..382ef50e 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultEdgeReader.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultEdgeReader.java @@ -10,11 +10,11 @@ import com.microsoft.spring.data.gremlin.conversion.source.GremlinSourceEdge; import com.microsoft.spring.data.gremlin.exception.GremlinUnexpectedSourceTypeException; import lombok.NoArgsConstructor; -import lombok.NonNull; import org.apache.tinkerpop.gremlin.driver.Result; -import org.springframework.lang.Nullable; +import org.springframework.lang.NonNull; import org.springframework.util.Assert; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -23,31 +23,38 @@ @NoArgsConstructor public class GremlinResultEdgeReader extends AbstractGremlinResultReader implements GremlinResultsReader { - private void readProperties(@NonNull GremlinSource source, @Nullable Map map) { - if (map != null) { - @SuppressWarnings("unchecked") final Map properties = (Map) map; - - properties.forEach(source::setProperty); - } - } - private void validate(List results, GremlinSource source) { Assert.notNull(results, "Results should not be null."); Assert.notNull(source, "GremlinSource should not be null."); Assert.isTrue(results.size() == 1, "Edge should contain only one result."); + } - final Result result = results.get(0); - + private Map getEdgeProperties (@NonNull Result result) { Assert.isInstanceOf(Map.class, result.getObject(), "should be one instance of Map"); - @SuppressWarnings("unchecked") final Map map = (Map) result.getObject(); + Map map = (Map) result.getObject(); + + map = getProperties(map); Assert.isTrue(map.containsKey(PROPERTY_ID), "should contain id property"); Assert.isTrue(map.containsKey(PROPERTY_LABEL), "should contain label property"); - Assert.isTrue(map.containsKey(PROPERTY_TYPE), "should contain type property"); +// Assert.isTrue(map.containsKey(PROPERTY_TYPE), "should contain type property"); Assert.isTrue(map.containsKey(PROPERTY_INV), "should contain inV property"); Assert.isTrue(map.containsKey(PROPERTY_OUTV), "should contain outV property"); - Assert.isTrue(map.get(PROPERTY_TYPE).equals(RESULT_TYPE_EDGE), "must be vertex type"); +// Assert.isTrue(map.get(PROPERTY_TYPE).equals(RESULT_TYPE_EDGE), "must be vertex type"); + + return map; + } + + @Override + protected Object getPropertyValue (@NonNull Map map, @NonNull String propertyKey) { + Object value = super.getPropertyValue(map, propertyKey); + + if (value instanceof LinkedHashMap && ((LinkedHashMap) value).containsKey(PROPERTY_RELATION_ID)) { + value = ((LinkedHashMap) value).get(PROPERTY_RELATION_ID); + } + + return value; } @Override @@ -60,16 +67,16 @@ public void read(@NonNull List results, @NonNull GremlinSource source) { validate(results, source); final GremlinSourceEdge sourceEdge = (GremlinSourceEdge) source; - final Map map = (Map) results.get(0).getObject(); + final Map map = getEdgeProperties(results.get(0)); - this.readProperties(source, (Map) map.get(PROPERTY_PROPERTIES)); + super.readResultProperties((Map) map.get(PROPERTY_PROPERTIES), source); final String className = source.getProperties().get(GREMLIN_PROPERTY_CLASSNAME).toString(); sourceEdge.setIdField(GremlinUtils.getIdField(GremlinUtils.toEntityClass(className))); - sourceEdge.setId(map.get(PROPERTY_ID)); - sourceEdge.setLabel(map.get(PROPERTY_LABEL).toString()); - sourceEdge.setVertexIdFrom(map.get(PROPERTY_OUTV)); - sourceEdge.setVertexIdTo(map.get(PROPERTY_INV)); + sourceEdge.setId(getPropertyValue(map, PROPERTY_ID)); + sourceEdge.setLabel(getPropertyValue(map, PROPERTY_LABEL).toString()); + sourceEdge.setVertexIdFrom(getPropertyValue(map, PROPERTY_OUTV)); + sourceEdge.setVertexIdTo(getPropertyValue(map, PROPERTY_INV)); } } diff --git a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultVertexReader.java b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultVertexReader.java index 10e8795e..ae642877 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultVertexReader.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/conversion/result/GremlinResultVertexReader.java @@ -26,20 +26,23 @@ private void validate(List results, GremlinSource source) { Assert.notNull(results, "Results should not be null."); Assert.notNull(source, "GremlinSource should not be null."); Assert.isTrue(results.size() == 1, "Vertex should contain only one result."); + } - final Result result = results.get(0); - + private Map getVertexProperties (@NonNull Result result) { Assert.isInstanceOf(Map.class, result.getObject(), "should be one instance of Map"); - @SuppressWarnings("unchecked") final Map map = (Map) result.getObject(); + Map map = (Map) result.getObject(); + + map = getProperties(map); Assert.isTrue(map.containsKey(PROPERTY_ID), "should contain id property"); Assert.isTrue(map.containsKey(PROPERTY_LABEL), "should contain label property"); - Assert.isTrue(map.containsKey(PROPERTY_TYPE), "should contain type property"); +// Assert.isTrue(map.containsKey(PROPERTY_TYPE), "should contain type property"); Assert.isTrue(map.containsKey(PROPERTY_PROPERTIES), "should contain properties property"); - Assert.isTrue(map.get(PROPERTY_TYPE).equals(RESULT_TYPE_VERTEX), "must be vertex type"); - +// Assert.isTrue(map.get(PROPERTY_TYPE).equals(RESULT_TYPE_VERTEX), "must be vertex type"); Assert.isInstanceOf(Map.class, map.get(PROPERTY_PROPERTIES), "should be one instance of Map"); + + return map; } @Override @@ -51,7 +54,7 @@ public void read(@NonNull List results, @NonNull GremlinSource source) { validate(results, source); - final Map map = (Map) results.get(0).getObject(); + final Map map = getVertexProperties(results.get(0)); final Map properties = (Map) map.get(PROPERTY_PROPERTIES); super.readResultProperties(properties, source); @@ -59,7 +62,7 @@ public void read(@NonNull List results, @NonNull GremlinSource source) { final String className = source.getProperties().get(GREMLIN_PROPERTY_CLASSNAME).toString(); source.setIdField(GremlinUtils.getIdField(GremlinUtils.toEntityClass(className))); - source.setId(map.get(PROPERTY_ID)); - source.setLabel(map.get(PROPERTY_LABEL).toString()); + source.setId(getPropertyValue(map, PROPERTY_ID)); + source.setLabel(getPropertyValue(map, PROPERTY_LABEL).toString()); } } diff --git a/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinOperations.java b/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinOperations.java index d92e0c5e..f583dc87 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinOperations.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinOperations.java @@ -49,5 +49,7 @@ public interface GremlinOperations { List find(GremlinQuery query, GremlinSource source); + List findByQuery(List queries, GremlinSource source); + MappingGremlinConverter getMappingConverter(); } diff --git a/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinTemplate.java b/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinTemplate.java index f84a554b..8bfe86b9 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinTemplate.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/query/GremlinTemplate.java @@ -8,6 +8,7 @@ import com.microsoft.spring.data.gremlin.annotation.EdgeFrom; import com.microsoft.spring.data.gremlin.annotation.EdgeTo; import com.microsoft.spring.data.gremlin.annotation.GeneratedValue; +import com.microsoft.spring.data.gremlin.common.Constants; import com.microsoft.spring.data.gremlin.common.GremlinEntityType; import com.microsoft.spring.data.gremlin.common.GremlinFactory; import com.microsoft.spring.data.gremlin.common.GremlinUtils; @@ -41,9 +42,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ExecutionException; import static java.util.stream.Collectors.toList; @@ -94,13 +93,31 @@ private List executeQuery(@NonNull List queries) { private List executeQueryParallel(@NonNull List queries) { return queries.parallelStream() .map(q -> getGremlinClient().submit(q).all()) - .collect(toList()).parallelStream().flatMap(f -> { + .collect(toList()) + .parallelStream() + .flatMap(f -> { try { return f.get().stream(); } catch (InterruptedException | ExecutionException e) { throw new GremlinQueryException("unable to complete query from gremlin", e); } }) + .flatMap(r -> { + final List results = new ArrayList<>(); + Object object = r.getObject(); + while (object instanceof LinkedHashMap + && ((LinkedHashMap) object).containsKey(Constants.PROPERTY_VALUE_WITH_AT)) { + object = ((LinkedHashMap) object).get(Constants.PROPERTY_VALUE_WITH_AT); + } + + if (object instanceof ArrayList) { + ((ArrayList) object).forEach(o -> results.add(new Result(o))); + } else { + results.add(new Result(object)); + } + + return results.stream(); + }) .collect(toList()); } @@ -349,7 +366,10 @@ private T recoverDomain(@NonNull GremlinSource source, @NonNull List List recoverDomainList(@NonNull GremlinSource source, @NonNull List results) { - return results.stream().map(r -> recoverDomain(source, Collections.singletonList(r))).collect(toList()); + return results + .stream() + .map(r -> recoverDomain(source, Collections.singletonList(r))) + .collect(toList()); } private T recoverGraphDomain(@NonNull GremlinSourceGraph source, @NonNull List results) { @@ -382,5 +402,16 @@ public List find(@NonNull GremlinQuery query, @NonNull GremlinSource s return this.recoverDomainList(source, results); } + + @Override + public List findByQuery(@NonNull List queryList, @NonNull GremlinSource source) { + final List results = this.executeQuery(queryList); + + if (results.isEmpty()) { + return Collections.emptyList(); + } + + return this.recoverDomainList(source, results); + } } diff --git a/src/main/java/com/microsoft/spring/data/gremlin/repository/GremlinRepository.java b/src/main/java/com/microsoft/spring/data/gremlin/repository/GremlinRepository.java index a47ed614..df46331c 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/repository/GremlinRepository.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/repository/GremlinRepository.java @@ -10,12 +10,15 @@ import org.springframework.data.repository.NoRepositoryBean; import java.io.Serializable; +import java.util.List; @NoRepositoryBean public interface GremlinRepository extends CrudRepository { Iterable findAll(Class domainClass); + Iterable findAllByQuery(List queryList); + void deleteAll(GremlinEntityType type); void deleteAll(Class domainClass); diff --git a/src/main/java/com/microsoft/spring/data/gremlin/repository/support/SimpleGremlinRepository.java b/src/main/java/com/microsoft/spring/data/gremlin/repository/support/SimpleGremlinRepository.java index d284cb3e..541d059f 100644 --- a/src/main/java/com/microsoft/spring/data/gremlin/repository/support/SimpleGremlinRepository.java +++ b/src/main/java/com/microsoft/spring/data/gremlin/repository/support/SimpleGremlinRepository.java @@ -133,5 +133,9 @@ public void deleteAll(@NonNull Class domainClass) { public boolean existsById(@NonNull ID id) { return this.operations.existsById(id, this.information.createGremlinSource()); } + + public Iterable findAllByQuery(@NonNull List queries) { + return this.operations.findByQuery(queries, this.information.createGremlinSource()); + } }