com.redhat.lightblue.eval.Projector.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.lightblue.eval.Projector.java

Source

/*
 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 <http://www.gnu.org/licenses/>.
 */
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
 * <code>true</code>, the field is included, and projection continues
 * to the subtree under that field. If the projection evaluates to
 * <code>false</code>, 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*
     * <code>project</code> 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 Boolean 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, rootMdNode, cursor,
                new QueryEvaluationContext(doc.getRoot()), false);
        if (root == null)
            root = factory.objectNode();
        return new JsonDoc(root);
    }

    private JsonNode project(JsonNodeFactory factory, Path contextPath, FieldTreeNode contextNode,
            JsonNodeCursor cursor, QueryEvaluationContext ctx, boolean processingArray) {
        JsonNode parentNode = null;
        do {
            Path fieldPath = cursor.getCurrentPath();
            Path contextRelativePath = contextPath.isEmpty() ? fieldPath
                    : fieldPath.suffix(-contextPath.numSegments());
            JsonNode fieldNode = cursor.getCurrentNode();
            LOGGER.debug("project context={} fieldPath={} contextRelativePath={} isArray={}", contextPath,
                    fieldPath, contextRelativePath, processingArray);
            FieldTreeNode fieldMd = contextNode.resolve(contextRelativePath);
            if (fieldMd != null) {
                Boolean result = project(fieldPath, ctx);
                LOGGER.debug("Projecting '{}' in context '{}': {}", contextRelativePath, contextPath, result);
                if (result == null) {
                    // Projection is undecisive. Recurse into array/object/reference nodes and see if anything is projected there
                    if (!(fieldNode instanceof NullNode)) {
                        if (fieldMd instanceof ObjectField || fieldMd instanceof ArrayField
                                || fieldMd instanceof ObjectArrayElement
                                || fieldMd instanceof ResolvedReferenceField) {
                            JsonNode newNode;
                            if (cursor.firstChild()) {
                                newNode = (getNestedProjector() == null ? this : getNestedProjector()).project(
                                        factory, contextPath, contextNode, cursor, ctx,
                                        !(fieldMd instanceof ObjectField || fieldMd instanceof ObjectArrayElement));
                                cursor.parent();
                            } else {
                                if (fieldMd instanceof ObjectField)
                                    newNode = factory.objectNode();
                                else
                                    newNode = factory.arrayNode();
                            }
                            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) {
                    // 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 (fieldMd instanceof ObjectField || fieldMd instanceof ObjectArrayElement) {
                            if (cursor.firstChild()) {
                                newNode = (getNestedProjector() == null ? this : getNestedProjector())
                                        .project(factory, contextPath, contextNode, cursor, ctx, false);
                                cursor.parent();
                                if (newNode == null)
                                    newNode = factory.objectNode();
                            }
                        } else if (fieldMd instanceof ArrayField || fieldMd instanceof ResolvedReferenceField) {
                            if (cursor.firstChild()) {
                                newNode = (getNestedProjector() == null ? this : getNestedProjector())
                                        .project(factory, contextPath, contextNode, cursor, ctx, true);
                                cursor.parent();
                                if (newNode == null)
                                    newNode = factory.arrayNode();
                            }
                        } else if (fieldMd instanceof SimpleField) {
                            newNode = fieldNode;
                        } else if (fieldMd instanceof SimpleArrayElement) {
                            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);
                        }
                    }
                }
            } else {
                LOGGER.warn("Unknown field:{}", fieldPath);
            }
        } 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<Projector> items = ((ListProjector) p).getItems();
            ListIterator<Projector> 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;
    }
}