resource-registry/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/JsonQueryERElement.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;
}
}