com.redhat.lightblue.eval.Projector Maven / Gradle / Ivy
/*
Copyright 2013 Red Hat, Inc. and/or its affiliates.
This file is part of lightblue.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package com.redhat.lightblue.eval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.ListIterator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.redhat.lightblue.metadata.ArrayElement;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.metadata.EntityMetadata;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.ObjectField;
import com.redhat.lightblue.metadata.SimpleArrayElement;
import com.redhat.lightblue.metadata.ObjectArrayElement;
import com.redhat.lightblue.metadata.SimpleField;
import com.redhat.lightblue.metadata.ResolvedReferenceField;
import com.redhat.lightblue.query.ArrayQueryMatchProjection;
import com.redhat.lightblue.query.ArrayRangeProjection;
import com.redhat.lightblue.query.FieldProjection;
import com.redhat.lightblue.query.Projection;
import com.redhat.lightblue.query.ProjectionList;
import com.redhat.lightblue.util.JsonDoc;
import com.redhat.lightblue.util.JsonNodeCursor;
import com.redhat.lightblue.util.Path;
/**
* This class evaluates a Projection.
*
* This is a stateful class. It retains state from the last execution that gets
* overwritten every time project() is called.
*
* This is how a document is projected: all the elements in the document is
* traversed in a depth first manner. For each field, the projection is
* evaluated. If the projection evaluated to true
, the field is
* included, and projection continues to the subtree under that field. If the
* projection evaluates to false
, that subtree is excluded. If the
* projection for that field cannot be decided, the a warning is logged, and
* field is excluded. Array fields can have nested projections to project their
* array elements.
*
* Recursive inclusion projections don't cross entity boundaries (i.e.
* references) unless there is an explicit inclusion projection for the
* referenced entity, or a field under that entity.
*/
public abstract class Projector {
private static final Logger LOGGER = LoggerFactory.getLogger(Projector.class);
private final FieldTreeNode rootMdNode;
private final Path rootMdPath;
protected Projector(Path ctxPath, FieldTreeNode ctx) {
this.rootMdNode = ctx;
this.rootMdPath = ctxPath;
}
/**
* Returns the nested projector for this path *only if* project
* returns true. Nested projector is used to project array elements. When a
* nested projector exists, projection operation should use the nested
* projector to project array elements. May return null, which means to
* continue using existing projector (this).
*/
public abstract Projector getNestedProjector();
/**
* Returns true, false, or null if the result cannot be determined.
*
* @param p The absolute field path
* @param ctx Query evaluation context
*/
public abstract Projection.Inclusion project(Path p, QueryEvaluationContext ctx);
/**
* Builds a projector using the given projection and entity metadata
*/
public static Projector getInstance(Projection projection, EntityMetadata md) {
return getInstance(projection, Path.EMPTY, md.getFieldTreeRoot());
}
/**
* Builds a (potentially nested) projection based on the given projection,
* and the location in the metadata field tree.
*/
public static Projector getInstance(Projection projection, Path ctxPath, FieldTreeNode ctx) {
if (projection instanceof FieldProjection) {
return new FieldProjector((FieldProjection) projection, ctxPath, ctx);
} else if (projection instanceof ProjectionList) {
return new ListProjector((ProjectionList) projection, ctxPath, ctx);
} else if (projection instanceof ArrayRangeProjection) {
return new ArrayRangeProjector((ArrayRangeProjection) projection, ctxPath, ctx);
} else {
return new ArrayQueryProjector((ArrayQueryMatchProjection) projection, ctxPath, ctx);
}
}
/**
* Projects a document
*/
public JsonDoc project(JsonDoc doc,
JsonNodeFactory factory) {
JsonNodeCursor cursor = doc.cursor();
cursor.firstChild();
ObjectNode root = (ObjectNode) project(factory,
rootMdPath,
cursor,
new QueryEvaluationContext(doc.getRoot()),
false);
if (root == null) {
root = factory.objectNode();
}
return new JsonDoc(root);
}
private JsonNode project(JsonNodeFactory factory,
Path contextPath,
JsonNodeCursor cursor,
QueryEvaluationContext ctx,
boolean processingArray) {
JsonNode parentNode = null;
do {
Path fieldPath = cursor.getCurrentPath();
JsonNode fieldNode = cursor.getCurrentNode();
LOGGER.debug("project context={} fieldPath={} isArray={}", contextPath, fieldPath, processingArray);
Projection.Inclusion result = project(fieldPath, ctx);
LOGGER.debug("Projecting '{}' in context '{}': {}", fieldPath, contextPath, result);
if (result == Projection.Inclusion.undecided) {
// Projection is undecisive. Recurse into array/object/reference nodes and see if anything is projected there
if (fieldNode instanceof ObjectNode
|| fieldNode instanceof ArrayNode) {
JsonNode newNode;
if (cursor.firstChild()) {
newNode = (getNestedProjector() == null ? this : getNestedProjector()).
project(factory, contextPath, cursor, ctx, fieldNode instanceof ArrayNode);
cursor.parent();
} else {
newNode = null;
}
if (newNode != null) {
if (newNode instanceof ArrayNode) {
newNode = sort(factory, this, (ArrayNode) newNode, fieldPath);
}
if (parentNode == null) {
parentNode = processingArray ? factory.arrayNode() : factory.objectNode();
}
if (parentNode instanceof ArrayNode) {
((ArrayNode) parentNode).add(newNode);
} else {
((ObjectNode) parentNode).set(fieldPath.tail(0), newNode);
}
}
}
} else if (result == Projection.Inclusion.implicit_inclusion
|| result == Projection.Inclusion.explicit_inclusion) {
// Field is included
if (fieldNode instanceof NullNode) {
if (parentNode == null) {
parentNode = processingArray ? factory.arrayNode() : factory.objectNode();
}
if (parentNode instanceof ArrayNode) {
((ArrayNode) parentNode).add(fieldNode);
} else {
((ObjectNode) parentNode).set(fieldPath.tail(0), fieldNode);
}
} else {
JsonNode newNode = null;
if (fieldNode instanceof ObjectNode) {
LOGGER.debug("Projecting object field {}", cursor.getCurrentPath());
if (cursor.firstChild()) {
newNode = (getNestedProjector() == null ? this : getNestedProjector()).
project(factory, contextPath, cursor, ctx, false);
cursor.parent();
LOGGER.debug("Child object:{}", newNode);
}
} else if (fieldNode instanceof ArrayNode) {
LOGGER.debug("Projecting array field {}", cursor.getCurrentPath());
if (cursor.firstChild()) {
newNode = (getNestedProjector() == null ? this : getNestedProjector()).
project(factory, contextPath, cursor, ctx, true);
cursor.parent();
LOGGER.debug("Child object:{}", newNode);
} else {
newNode=factory.arrayNode();
}
} else {
newNode = fieldNode;
}
if (newNode != null) {
if (newNode instanceof ArrayNode) {
newNode = sort(factory, this, (ArrayNode) newNode, fieldPath);
}
if (parentNode == null) {
parentNode = processingArray ? factory.arrayNode() : factory.objectNode();
}
if (parentNode instanceof ArrayNode) {
((ArrayNode) parentNode).add(newNode);
} else {
((ObjectNode) parentNode).set(fieldPath.tail(0), newNode);
}
}
}
}
} while (cursor.nextSibling());
return parentNode;
}
private static ArrayNode sort(JsonNodeFactory factory, Projector projector, ArrayNode node, Path nodePath) {
ArrayProjector p = findArrayProjectorForField(projector, nodePath);
if (p != null && p.getSort() != null) {
LOGGER.debug("Sorting array elements using {}", p.getSort());
return p.sortArray(node, factory);
}
return node;
}
private static ArrayProjector findArrayProjectorForField(Projector p, Path field) {
if (p instanceof ListProjector) {
List items = ((ListProjector) p).getItems();
ListIterator itemsItr = items.listIterator(items.size());
while (itemsItr.hasPrevious()) {
Projector projector = itemsItr.previous();
ArrayProjector x = findArrayProjectorForField(projector, field);
if (x != null) {
return x;
}
}
} else if (p instanceof ArrayProjector) {
if (field.matches(((ArrayProjector) p).getArrayFieldPattern())) {
return (ArrayProjector) p;
}
return findArrayProjectorForField(p.getNestedProjector(), field);
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy