/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
*
* 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 org.apache.streams.neo4j;
import org.apache.streams.graph.QueryGraphHelper;
import org.apache.streams.jackson.StreamsJacksonMapper;
import org.apache.streams.pojo.json.Activity;
import org.apache.streams.pojo.json.ActivityObject;
import org.apache.streams.util.PropertyUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.StringEscapeUtils;
import org.javatuples.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stringtemplate.v4.ST;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Supporting class for interacting with neo4j via rest API
*/
public class CypherQueryGraphHelper implements QueryGraphHelper, Serializable {
private static final ObjectMapper MAPPER = StreamsJacksonMapper.getInstance();
private static final Logger LOGGER = LoggerFactory.getLogger(CypherQueryGraphHelper.class);
public static final String getVertexLongIdStatementTemplate = "MATCH (v) WHERE ID(v) = <id> RETURN v";
public static final String getVertexStringIdStatementTemplate = "MATCH (v {id: '<id>'} ) RETURN v";
public static final String getVerticesLabelIdStatementTemplate = "MATCH (v:<type>) RETURN v";
public final static String createVertexStatementTemplate = "MATCH (x {id: '<id>'}) "+
"CREATE UNIQUE (v:`<type>` { props }) "+
"ON CREATE SET v <labels> "+
"RETURN v";
public final static String mergeVertexStatementTemplate = "MERGE (v:`<type>` {id: '<id>'}) "+
"ON CREATE SET v <labels>, v = { props }, v.`@timestamp` = timestamp() "+
"ON MATCH SET v <labels>, v = { props }, v.`@timestamp` = timestamp() "+
"RETURN v";
public final static String createEdgeStatementTemplate = "MATCH (s:`<s_type>` {id: '<s_id>'}),(d:`<d_type>` {id: '<d_id>'}) "+
"CREATE UNIQUE (s)-[r:`<r_type>` <r_props>]->(d) "+
"RETURN r";
public Pair<String, Map<String, Object>> getVertexRequest(String streamsId) {
ST getVertex = new ST(getVertexStringIdStatementTemplate);
getVertex.add("id", streamsId);
Pair<String, Map<String, Object>> queryPlusParameters = new Pair(getVertex.render(), null);
LOGGER.debug("getVertexRequest", queryPlusParameters.toString());
return queryPlusParameters;
}
/**
* getVertexRequest.
* @param vertexId numericId
* @return pair (streamsId, parameterMap)
*/
public Pair<String, Map<String, Object>> getVertexRequest(Long vertexId) {
ST getVertex = new ST(getVertexLongIdStatementTemplate);
getVertex.add("id", vertexId);
Pair<String, Map<String, Object>> queryPlusParameters = new Pair(getVertex.render(), null);
LOGGER.debug("getVertexRequest", queryPlusParameters.toString());
return queryPlusParameters;
}
/**
* createVertexRequest.
* @param activityObject activityObject
* @return pair (query, parameterMap)
*/
public Pair<String, Map<String, Object>> createVertexRequest(ActivityObject activityObject) {
Objects.requireNonNull(activityObject.getObjectType());
List<String> labels = getLabels(activityObject);
ST createVertex = new ST(createVertexStatementTemplate);
createVertex.add("id", activityObject.getId());
createVertex.add("type", activityObject.getObjectType());
if ( labels.size() > 0 ) {
createVertex.add("labels", String.join(" ", labels));
}
String query = createVertex.render();
ObjectNode object = MAPPER.convertValue(activityObject, ObjectNode.class);
Map<String, Object> props = PropertyUtil.flattenToMap(object, '.');
Pair<String, Map<String, Object>> queryPlusParameters = new Pair(createVertex.render(), props);
LOGGER.debug("createVertexRequest: ({},{})", query, props);
return queryPlusParameters;
}
/**
* getVerticesRequest gets all vertices with a label.
* @param labelId labelId
* @return pair (query, parameterMap)
*/
public Pair<String, Map<String, Object>> getVerticesRequest(String labelId) {
ST getVertex = new ST(getVerticesLabelIdStatementTemplate);
getVertex.add("type", labelId);
Pair<String, Map<String, Object>> queryPlusParameters = new Pair(getVertex.render(), null);
LOGGER.debug("getVertexRequest", queryPlusParameters.toString());
return queryPlusParameters;
}
/**
* mergeVertexRequest.
* @param activityObject activityObject
* @return pair (query, parameterMap)
*/
public Pair<String, Map<String, Object>> mergeVertexRequest(ActivityObject activityObject) {
Objects.requireNonNull(activityObject.getObjectType());
Pair queryPlusParameters = new Pair(null, new HashMap<>());
List<String> labels = getLabels(activityObject);
ST mergeVertex = new ST(mergeVertexStatementTemplate);
mergeVertex.add("id", activityObject.getId());
mergeVertex.add("type", activityObject.getObjectType());
if ( labels.size() > 0 ) {
mergeVertex.add("labels", String.join(" ", labels));
}
String query = mergeVertex.render();
ObjectNode object = MAPPER.convertValue(activityObject, ObjectNode.class);
Map<String, Object> props = PropertyUtil.flattenToMap(object, '.');
LOGGER.debug("mergeVertexRequest: ({},{})", query, props);
queryPlusParameters = queryPlusParameters.setAt0(query);
queryPlusParameters = queryPlusParameters.setAt1(props);
return queryPlusParameters;
}
/**
* createActorObjectEdge.
* @param activity activity
* @return pair (query, parameterMap)
*/
public Pair<String, Map<String, Object>> createActorObjectEdge(Activity activity) {
Pair queryPlusParameters = new Pair(null, new HashMap<>());
ObjectNode object = MAPPER.convertValue(activity, ObjectNode.class);
Map<String, Object> props = PropertyUtil.flattenToMap(object, '.');
ST mergeEdge = new ST(createEdgeStatementTemplate);
mergeEdge.add("s_id", activity.getActor().getId());
mergeEdge.add("s_type", activity.getActor().getObjectType());
mergeEdge.add("d_id", activity.getObject().getId());
mergeEdge.add("d_type", activity.getObject().getObjectType());
mergeEdge.add("r_id", activity.getId());
mergeEdge.add("r_type", activity.getVerb());
mergeEdge.add("r_props", getActorObjectEdgePropertyCreater(props));
String statement = mergeEdge.render();
queryPlusParameters = queryPlusParameters.setAt0(statement);
queryPlusParameters = queryPlusParameters.setAt1(props);
LOGGER.debug("createActorObjectEdge: ({},{})", statement, props);
return queryPlusParameters;
}
/**
* createActorTargetEdge.
* @param activity activity
* @return pair (query, parameterMap)
*/
public Pair<String, Map<String, Object>> createActorTargetEdge(Activity activity) {
Pair queryPlusParameters = new Pair(null, new HashMap<>());
ObjectNode object = MAPPER.convertValue(activity, ObjectNode.class);
Map<String, Object> props = PropertyUtil.flattenToMap(object, '.');
ST mergeEdge = new ST(createEdgeStatementTemplate);
mergeEdge.add("s_id", activity.getActor().getId());
mergeEdge.add("s_type", activity.getActor().getObjectType());
mergeEdge.add("d_id", activity.getTarget().getId());
mergeEdge.add("d_type", activity.getTarget().getObjectType());
mergeEdge.add("r_id", activity.getId());
mergeEdge.add("r_type", activity.getVerb());
mergeEdge.add("r_props", getActorTargetEdgePropertyCreater(props));
String statement = mergeEdge.render();
queryPlusParameters = queryPlusParameters.setAt0(statement);
queryPlusParameters = queryPlusParameters.setAt1(props);
LOGGER.debug("createActorObjectEdge: ({},{})", statement, props);
return queryPlusParameters;
}
/**
* getPropertyValueSetter.
* @param map paramMap
* @return PropertyValueSetter string
*/
public static String getPropertyValueSetter(Map<String, Object> map, String symbol) {
StringBuilder builder = new StringBuilder();
for( Map.Entry<String, Object> entry : map.entrySet()) {
if( entry.getValue() instanceof String ) {
String propVal = (String)(entry.getValue());
builder.append("," + symbol + ".`" + entry.getKey() + "` = '" + StringEscapeUtils.escapeJava(propVal) + "'");
}
}
return builder.toString();
}
/**
* getPropertyParamSetter.
* @param map paramMap
* @return PropertyParamSetter string
*/
public static String getPropertyParamSetter(Map<String, Object> map, String symbol) {
StringBuilder builder = new StringBuilder();
for( Map.Entry<String, Object> entry : map.entrySet()) {
if( entry.getValue() instanceof String ) {
String propVal = (String)(entry.getValue());
builder.append("," + symbol + ".`" + entry.getKey() + "` = '" + StringEscapeUtils.escapeJava(propVal) + "'");
}
}
return builder.toString();
}
/**
* getPropertyCreater.
* @param map paramMap
* @return PropertyCreater string
*/
public static String getPropertyCreater(Map<String, Object> map) {
StringBuilder builder = new StringBuilder();
builder.append("{ ");
List<String> parts = new ArrayList<>();
for( Map.Entry<String, Object> entry : map.entrySet()) {
if( entry.getValue() instanceof String ) {
String propVal = (String) (entry.getValue());
parts.add("`"+entry.getKey() + "`:'" + StringEscapeUtils.escapeJava(propVal) + "'");
}
}
builder.append(String.join(" ", parts));
builder.append(" }");
return builder.toString();
}
private String getActorObjectEdgePropertyCreater(Map<String, Object> map) {
StringBuilder builder = new StringBuilder();
builder.append("{ ");
List<String> parts = new ArrayList<>();
for( Map.Entry<String, Object> entry : map.entrySet()) {
if( entry.getValue() instanceof String ) {
String propVal = (String) (entry.getValue());
if( !entry.getKey().contains(".")) {
parts.add("`"+entry.getKey() + "`: '" + StringEscapeUtils.escapeJava(propVal) + "'");
}
}
}
builder.append(String.join(", ", parts));
builder.append(" }");
return builder.toString();
}
private String getActorTargetEdgePropertyCreater(Map<String, Object> map) {
StringBuilder builder = new StringBuilder();
builder.append("{ ");
List<String> parts = new ArrayList<>();
for( Map.Entry<String, Object> entry : map.entrySet()) {
if( entry.getValue() instanceof String ) {
String propVal = (String) (entry.getValue());
if( !entry.getKey().contains(".")) {
parts.add("`"+entry.getKey() + "`: '" + StringEscapeUtils.escapeJava(propVal) + "'");
} else if( entry.getKey().startsWith("object.") && !entry.getKey().contains(".id")) {
parts.add("`"+entry.getKey().substring("object.".length()) + "`: '" + StringEscapeUtils.escapeJava(propVal) + "'");
}
}
}
builder.append(String.join(", ", parts));
builder.append(" }");
return builder.toString();
}
/**
* getLabels.
* @param activityObject activityObject
* @return PropertyCreater string
*/
public static List<String> getLabels(ActivityObject activityObject) {
List<String> labels = Collections.singletonList(":streams");
if ( activityObject.getAdditionalProperties().containsKey("labels") ) {
List<String> extraLabels = (List<String>)activityObject.getAdditionalProperties().get("labels");
for ( String extraLabel : extraLabels ) {
labels.add(":" + extraLabel);
}
}
return labels;
}
}