All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.eval.Projector Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 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=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 - 2025 Weber Informatics LLC | Privacy Policy