481 lines
14 KiB
Java
481 lines
14 KiB
Java
package org.gcube.informationsystem.resourceregistry.queries.json.base;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import javax.ws.rs.InternalServerErrorException;
|
|
|
|
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
|
|
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
|
|
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import org.gcube.informationsystem.base.reference.AccessType;
|
|
import org.gcube.informationsystem.base.reference.Direction;
|
|
import org.gcube.informationsystem.base.reference.Element;
|
|
import org.gcube.informationsystem.model.reference.ModelElement;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.queries.InvalidQueryException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaNotFoundException;
|
|
import org.gcube.informationsystem.resourceregistry.queries.operators.ComparisonOperator;
|
|
import org.gcube.informationsystem.resourceregistry.queries.operators.LogicalOperator;
|
|
import org.gcube.informationsystem.resourceregistry.queries.operators.MatemathicsOperator;
|
|
import org.gcube.informationsystem.resourceregistry.queries.operators.ProjectionOperator;
|
|
import org.gcube.informationsystem.resourceregistry.types.TypesCache;
|
|
import org.gcube.informationsystem.utils.TypeUtility;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
public abstract class JsonQueryERElement {
|
|
|
|
protected Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
|
|
public static void validateType(String type, AccessType requiredAccessType) throws SchemaException, ResourceRegistryException {
|
|
AccessType accessType = TypesCache.getInstance().getCachedType(type).getAccessType();
|
|
if(!accessType.equals(requiredAccessType)) {
|
|
throw new InvalidQueryException(type + "is not an expected " + requiredAccessType.getName() + " type");
|
|
}
|
|
}
|
|
|
|
protected final ObjectMapper objectMapper;
|
|
protected final String type;
|
|
protected final JsonNode jsonNode;
|
|
protected final AccessType accessType;
|
|
|
|
protected final Set<String> fieldNamesToRemove;
|
|
|
|
protected Direction direction;
|
|
protected boolean entryPoint;
|
|
|
|
/* Start of variables used to create in MATCH queries */
|
|
/**
|
|
* Instruct the JSON query analyzer if it is a projection
|
|
*/
|
|
protected boolean projection;
|
|
|
|
/**
|
|
* The chain of callers of this instance analyzer.
|
|
* breadcrumb.get(breadcrumb.size-1) == caller
|
|
* breadcrumb is empty if this instance is an entry point
|
|
*/
|
|
protected List<JsonQueryERElement> breadcrumb;
|
|
/**
|
|
* The breadcrumb.size() provide the level of nesting.
|
|
* The position which element number at the same level.
|
|
* E.g. the index of a ConsistsOf when the caller is a Resource.
|
|
*
|
|
* It is used in conjunction with breadcrumb.size() to attach a number to the alias.
|
|
* This allows to generate a predictive not clashing alias instead of using random string/number.
|
|
* This is useful for testing purposes.
|
|
*/
|
|
protected int position;
|
|
|
|
/**
|
|
* Contains the alias if needed by the class
|
|
*/
|
|
protected String alias;
|
|
|
|
/**
|
|
* This field is used by entry point only.
|
|
* Any class at any level can retrieve the entry point using
|
|
* breadcrumb.get(0);
|
|
*/
|
|
protected List<String> fieldsToEmit;
|
|
/* Start of variables used to create in MATCH queries */
|
|
|
|
|
|
|
|
|
|
/**
|
|
* it indicates the number of properties in this.jsonNode
|
|
* This number is manipulated while analyzing the jsonNode
|
|
* to properly create the query.
|
|
*/
|
|
protected int size;
|
|
|
|
protected boolean traverseBack;
|
|
|
|
public JsonQueryERElement(JsonNode jsonQuery, AccessType accessType) throws SchemaException, ResourceRegistryException {
|
|
this.objectMapper = new ObjectMapper();
|
|
this.type = TypeUtility.getTypeName(jsonQuery);
|
|
this.jsonNode = jsonQuery;
|
|
this.size = jsonNode.size();
|
|
this.accessType = accessType;
|
|
this.entryPoint = false;
|
|
this.traverseBack = true;
|
|
|
|
this.projection = false;
|
|
this.breadcrumb = new ArrayList<>();
|
|
this.position = 0;
|
|
this.alias = null;
|
|
this.fieldsToEmit = new ArrayList<>();
|
|
|
|
|
|
this.fieldNamesToRemove = new HashSet<>();
|
|
this.fieldNamesToRemove.add(Element.TYPE_PROPERTY);
|
|
this.fieldNamesToRemove.add(ModelElement.SUPERTYPES_PROPERTY);
|
|
this.fieldNamesToRemove.add(ModelElement.EXPECTED_TYPE_PROPERTY);
|
|
|
|
validateType(this.type, this.accessType);
|
|
}
|
|
|
|
public String getType() {
|
|
return type;
|
|
}
|
|
|
|
public Direction getDirection() {
|
|
return direction;
|
|
}
|
|
|
|
public void setDirection(Direction direction) {
|
|
this.direction = direction;
|
|
}
|
|
|
|
public boolean isEntryPoint() {
|
|
return entryPoint;
|
|
}
|
|
|
|
public void setEntryPoint(boolean entryPoint) {
|
|
this.entryPoint = entryPoint;
|
|
this.traverseBack = !entryPoint;
|
|
}
|
|
|
|
public boolean isProjection() {
|
|
return projection;
|
|
}
|
|
|
|
public void setProjection(boolean projection) {
|
|
if(!projection) {
|
|
throw new InternalServerErrorException("Projection can only be set to true from code. This is a server side bug. Please contact the administrator.");
|
|
}
|
|
this.projection = projection;
|
|
if(!entryPoint) {
|
|
// Set the projection in the parent
|
|
breadcrumb.get(breadcrumb.size()-2).setProjection(projection);
|
|
}
|
|
}
|
|
|
|
public List<JsonQueryERElement> getBreadcrumb() {
|
|
return breadcrumb;
|
|
}
|
|
|
|
public void setBreadcrumb(List<JsonQueryERElement> breadcrumb) {
|
|
this.breadcrumb = breadcrumb;
|
|
}
|
|
|
|
public int getPosition() {
|
|
return position;
|
|
}
|
|
|
|
public void setPosition(int position) {
|
|
this.position = position;
|
|
}
|
|
|
|
public String getAlias() {
|
|
return alias;
|
|
}
|
|
|
|
public String getAlias(boolean generateifNull) {
|
|
if(alias==null && generateifNull) {
|
|
alias = generateAlias().toString();
|
|
}
|
|
return alias;
|
|
}
|
|
|
|
protected StringBuffer generateAlias() {
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.append(type.toLowerCase());
|
|
for(JsonQueryERElement elem : breadcrumb) {
|
|
sb.append(elem.getPosition());
|
|
}
|
|
sb.append(this.position);
|
|
return sb;
|
|
}
|
|
|
|
/**
|
|
* Add a field to emit only if this instance is the entry point
|
|
* @param fieldToEmit
|
|
*/
|
|
protected void addFieldToEmit(String fieldToEmit) {
|
|
fieldsToEmit.add(fieldToEmit);
|
|
logger.trace("The field to emit ({}) has been added to {} with alias {}", fieldToEmit, this.type, this.alias);
|
|
if(!entryPoint) {
|
|
logger.trace("The field to emit ({}) will be added to the parent too", fieldToEmit);
|
|
breadcrumb.get(breadcrumb.size()-2).addFieldToEmit(fieldToEmit);
|
|
}
|
|
}
|
|
|
|
public boolean isTraverseBack() {
|
|
return traverseBack;
|
|
}
|
|
|
|
public void setTraverseBack(boolean traverseBack) {
|
|
this.traverseBack = traverseBack;
|
|
}
|
|
|
|
public StringBuffer createQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException {
|
|
return createMatchQuery(stringBuffer);
|
|
}
|
|
|
|
public abstract StringBuffer createTraversalQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException;
|
|
|
|
protected StringBuffer addConstraints(JsonNode jsonNode, LogicalOperator queryLogicalOperator, String fieldNamePrefix) throws InvalidQueryException {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
|
|
if(queryLogicalOperator==null) {
|
|
queryLogicalOperator = LogicalOperator.AND;
|
|
}
|
|
|
|
JsonNode copiedJsonNode = jsonNode.deepCopy();
|
|
|
|
if(jsonNode.isObject()) {
|
|
ObjectNode objectNode = (ObjectNode) copiedJsonNode;
|
|
objectNode.remove(fieldNamesToRemove);
|
|
|
|
Iterator<String> iterator = objectNode.fieldNames();
|
|
|
|
boolean first = true;
|
|
|
|
while(iterator.hasNext()) {
|
|
String fieldName = iterator.next();
|
|
JsonNode node = objectNode.get(fieldName);
|
|
StringBuffer evBuffer = evaluateNode(node, fieldName, fieldNamePrefix);
|
|
|
|
if(evBuffer!=null && evBuffer.length()>0) {
|
|
if(first) {
|
|
first = false;
|
|
}else {
|
|
stringBuffer.append(queryLogicalOperator.getDbOperator());
|
|
}
|
|
stringBuffer.append(evBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(jsonNode.isArray()) {
|
|
ArrayNode arrayNode = (ArrayNode) copiedJsonNode;
|
|
Iterator<JsonNode> iterator = arrayNode.iterator();
|
|
boolean first = true;
|
|
while(iterator.hasNext()) {
|
|
JsonNode node = iterator.next();
|
|
StringBuffer evBuffer = evaluateNode(node, null, fieldNamePrefix);
|
|
|
|
if(!first) {
|
|
stringBuffer.append(queryLogicalOperator.getDbOperator());
|
|
}
|
|
|
|
if(evBuffer!=null && evBuffer.length()>0) {
|
|
if(first) {
|
|
first = false;
|
|
}
|
|
stringBuffer.append(evBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
return stringBuffer;
|
|
}
|
|
|
|
protected StringBuffer evaluateNode(JsonNode jsonNode, String fieldName, String fieldNamePrefix) throws InvalidQueryException {
|
|
|
|
if(ProjectionOperator.getOperators().contains(fieldName)) {
|
|
--size;
|
|
setProjection(true);
|
|
Iterator<String> iterator = jsonNode.fieldNames();
|
|
while(iterator.hasNext()) {
|
|
String fieldNameToEmit = iterator.next();
|
|
String nameOfFieldToEmit = jsonNode.get(fieldNameToEmit).asText();
|
|
StringBuffer b = new StringBuffer();
|
|
b.append(getAlias(true));
|
|
b.append(".");
|
|
if(fieldNamePrefix !=null) {
|
|
b.append(fieldNamePrefix);
|
|
b.append(".");
|
|
}
|
|
b.append(fieldNameToEmit);
|
|
b.append(" AS `");
|
|
b.append(nameOfFieldToEmit);
|
|
b.append("`");
|
|
addFieldToEmit(b.toString());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if(MatemathicsOperator.getOperators().contains(fieldName)) {
|
|
--size;
|
|
setProjection(true);
|
|
MatemathicsOperator mo = MatemathicsOperator.getOperator(fieldName);
|
|
String fieldToEmit = mo.generateFieldToEmit(jsonNode, getAlias(true));
|
|
addFieldToEmit(fieldToEmit);
|
|
return null;
|
|
}
|
|
|
|
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
|
|
if(LogicalOperator.getOperators().contains(fieldName)) {
|
|
LogicalOperator queryLogicalOperator = LogicalOperator.getOperator(fieldName);
|
|
stringBuffer.append("(");
|
|
stringBuffer.append(addConstraints(jsonNode, queryLogicalOperator, fieldNamePrefix));
|
|
stringBuffer.append(")");
|
|
return stringBuffer;
|
|
}
|
|
|
|
if(ComparisonOperator.getOperators().contains(fieldName)) {
|
|
ComparisonOperator comparisonOperator = ComparisonOperator.getOperator(fieldName);
|
|
|
|
String key = getKey(null, fieldNamePrefix);
|
|
String value = getValue(jsonNode);
|
|
stringBuffer.append(comparisonOperator.addCondition(key, value));
|
|
return stringBuffer;
|
|
}
|
|
|
|
if(jsonNode.isObject()) {
|
|
StringBuffer newPrefix = new StringBuffer();
|
|
if(fieldNamePrefix!=null && fieldNamePrefix.compareTo("")!=0) {
|
|
newPrefix.append(fieldNamePrefix);
|
|
if(fieldName!=null && fieldName.compareTo("")!=0) {
|
|
newPrefix.append(".");
|
|
}
|
|
}
|
|
|
|
if(fieldName!=null && fieldName.compareTo("")!=0) {
|
|
newPrefix.append(fieldName);
|
|
}
|
|
|
|
stringBuffer.append(addConstraints(jsonNode, null, newPrefix.length()>0 ? newPrefix.toString() : null));
|
|
return stringBuffer;
|
|
}
|
|
|
|
if(jsonNode.isTextual() || jsonNode.isNumber()) {
|
|
String key = getKey(fieldName, fieldNamePrefix);
|
|
String value = getValue(jsonNode);
|
|
stringBuffer.append(ComparisonOperator.EQ.addCondition(key, value));
|
|
}
|
|
|
|
if(jsonNode.isNull()) {
|
|
String key = getKey(fieldName, null);
|
|
stringBuffer.append(ComparisonOperator.IS.addCondition(key, null));
|
|
}
|
|
|
|
return stringBuffer;
|
|
}
|
|
|
|
protected String getKey(String fieldName, String fieldNamePrefix) {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
if(fieldNamePrefix!=null) {
|
|
stringBuffer.append(fieldNamePrefix.trim());
|
|
if(fieldName!=null && fieldName.trim().length()!=0) {
|
|
stringBuffer.append(".");
|
|
}
|
|
}
|
|
if(fieldName!=null) {
|
|
stringBuffer.append(fieldName.trim());
|
|
}
|
|
return stringBuffer.toString();
|
|
}
|
|
|
|
protected String getValue(JsonNode jsonNode) {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
|
|
String value = jsonNode.asText();
|
|
if(jsonNode.isNumber()) {
|
|
stringBuffer.append(value);
|
|
} else {
|
|
stringBuffer.append("\"");
|
|
stringBuffer.append(value);
|
|
stringBuffer.append("\"");
|
|
}
|
|
return stringBuffer.toString();
|
|
}
|
|
|
|
protected List<JsonQueryERElement> getChildrenBreadcrumb() {
|
|
List<JsonQueryERElement> childrenBreadcrumb = new ArrayList<>(this.breadcrumb);
|
|
childrenBreadcrumb.add(this);
|
|
return childrenBreadcrumb;
|
|
}
|
|
|
|
protected StringBuffer wrapMatchQuery(StringBuffer buffer) throws InvalidQueryException {
|
|
if(entryPoint) {
|
|
alias = getAlias(true);
|
|
|
|
StringBuffer sb = null;
|
|
if(size > 1) {
|
|
sb = addConstraints(jsonNode, null, null);
|
|
}
|
|
|
|
StringBuffer entryBuffer = new StringBuffer();
|
|
entryBuffer.append("MATCH\n");
|
|
entryBuffer.append("\t{class: "); // The { has to be closed
|
|
entryBuffer.append(type);
|
|
entryBuffer.append(", as: ");
|
|
entryBuffer.append(alias);
|
|
entryBuffer.append(", where: ");
|
|
if(sb!=null && sb.length()>0) {
|
|
entryBuffer.append("(");
|
|
}
|
|
entryBuffer.append("($currentMatch['@class'] INSTANCEOF '");
|
|
entryBuffer.append(type);
|
|
entryBuffer.append("')"); // close the second (
|
|
|
|
if(sb!=null && sb.length()>0) {
|
|
entryBuffer.append(" AND (");
|
|
entryBuffer.append(sb);
|
|
entryBuffer.append(")");
|
|
entryBuffer.append(")");
|
|
}
|
|
|
|
entryBuffer.append("}\n");
|
|
entryBuffer.append(buffer);
|
|
|
|
entryBuffer.append("\nRETURN\n");
|
|
|
|
if(!projection) {
|
|
entryBuffer.append("\tDISTINCT(");
|
|
entryBuffer.append(alias);
|
|
entryBuffer.append(") as ret");
|
|
|
|
StringBuffer wrap = new StringBuffer();
|
|
wrap.append("SELECT EXPAND(ret) FROM (\n");
|
|
wrap.append(entryBuffer);
|
|
wrap.append("\n)");
|
|
|
|
entryBuffer = wrap;
|
|
}else {
|
|
int size = fieldsToEmit.size();
|
|
for(int i=0; i<size; i++) {
|
|
entryBuffer.append("\t");
|
|
entryBuffer.append(fieldsToEmit.get(i));
|
|
if(i<(size-1)) {
|
|
entryBuffer.append(",\n");
|
|
}
|
|
}
|
|
}
|
|
return entryBuffer;
|
|
}else {
|
|
return buffer;
|
|
}
|
|
|
|
}
|
|
|
|
protected abstract StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb) throws SchemaException, ResourceRegistryException;
|
|
|
|
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
|
|
List<JsonQueryERElement> childrenBreadcrumb = getChildrenBreadcrumb();
|
|
|
|
if(entryPoint) {
|
|
getAlias(true);
|
|
}
|
|
|
|
StringBuffer buffer = getSpecificMatchQuery(childrenBreadcrumb);
|
|
buffer = wrapMatchQuery(buffer);
|
|
|
|
stringBuffer.append(buffer);
|
|
|
|
return stringBuffer;
|
|
}
|
|
}
|