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 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 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 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 getBreadcrumb() { return breadcrumb; } public void setBreadcrumb(List 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 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 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 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 getChildrenBreadcrumb() { List 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 childrenBreadcrumb) throws SchemaException, ResourceRegistryException; public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException { List childrenBreadcrumb = getChildrenBreadcrumb(); if(entryPoint) { getAlias(true); } StringBuffer buffer = getSpecificMatchQuery(childrenBreadcrumb); buffer = wrapMatchQuery(buffer); stringBuffer.append(buffer); return stringBuffer; } }