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

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

There is a newer version: 1.2.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 java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.redhat.lightblue.crud.CrudConstants;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.ObjectArrayElement;
import com.redhat.lightblue.metadata.ObjectField;
import com.redhat.lightblue.metadata.SimpleArrayElement;
import com.redhat.lightblue.metadata.SimpleField;
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.metadata.types.Arith;
import com.redhat.lightblue.query.FieldAndRValue;
import com.redhat.lightblue.query.RValueExpression;
import com.redhat.lightblue.query.SetExpression;
import com.redhat.lightblue.query.UpdateOperator;
import com.redhat.lightblue.util.JsonDoc;
import com.redhat.lightblue.util.Path;

/**
 * Sets a field value
 */
public class SetExpressionEvaluator extends Updater {

    private static final Logger LOGGER = LoggerFactory.getLogger(SetExpressionEvaluator.class);

    private final List setValues = new ArrayList<>();
    private final UpdateOperator op;
    private final JsonNodeFactory factory;

    private static final class FieldData {
        /**
         * Relative path to the field to set
         */
        private final Path field;
        /**
         * Absolute path to the field to set. The absolute path may contain '*'
         */
        private final Path absField;

        /**
         * Type of the field to set
         */
        private final Type fieldType;

        /**
         * If the field is to be set from another field, the referenced relative
         * path to the source field
         */
        private final Path refPath;

        /**
         * If the field is to be set from another field, the type of the source
         * field
         */
        private final Type refType;

        /**
         * If the field is set to a value, the value
         */
        private final RValueExpression value;

        public FieldData(Path field, Type t, Path refPath, Type refType, RValueExpression value, Path absField) {
            this.field = field;
            this.fieldType = t;
            this.refPath = refPath;
            this.refType = refType;
            this.value = value;
            this.absField = absField;
        }
    }

    /**
     * Creates a set expression evaluator
     *
     * @param factory Node factory
     * @param context The context from which the expression will be evaluated
     * @param expr The set expression
     *
     * This ctor resolves the field references in expr and stores them to be
     * applied later.
     */
    public SetExpressionEvaluator(JsonNodeFactory factory, FieldTreeNode context, SetExpression expr) {
        this.factory = factory;
        op = expr.getOp();
        for (FieldAndRValue fld : expr.getFields()) {
            Path field = fld.getField();
            LOGGER.debug("Parsing setter for {}", field);
            RValueExpression rvalue = fld.getRValue();
            Path refPath = null;
            FieldTreeNode refMdNode = null;
            FieldData data = null;
            if (rvalue.getType() == RValueExpression.RValueType._dereference) {
                refPath = rvalue.getPath();
                refMdNode = context.resolve(refPath);
                if (refMdNode == null) {
                    throw new EvaluationError(CrudConstants.ERR_CANT_ACCESS + refPath);
                }
                LOGGER.debug("Refpath {}", refPath);
            }
            FieldTreeNode mdNode = context.resolve(field);
            if (mdNode == null) {
                throw new EvaluationError(CrudConstants.ERR_CANT_ACCESS + field);
            }

            if (mdNode instanceof SimpleField || mdNode instanceof SimpleArrayElement) {
                data = initializeSimple(rvalue, refMdNode, mdNode, field, refPath);
            } else if (mdNode instanceof ObjectField || mdNode instanceof ObjectArrayElement) {
                // Only a dereference or empty object is acceptable here
                data = initializeObject(rvalue, refMdNode, mdNode, field, refPath);
            } else if (mdNode instanceof ArrayField) {
                // Unacceptable
                throw new EvaluationError(CrudConstants.ERR_ASSIGNMENT + field);
            }
            setValues.add(data);
        }
    }

    private FieldData initializeSimple(RValueExpression rvalue, FieldTreeNode refMdNode, FieldTreeNode mdNode, Path field, Path refPath) {
        if (rvalue.getType() == RValueExpression.RValueType._dereference) {
            if (!mdNode.getType().equals(refMdNode.getType())) {
                throw new EvaluationError(CrudConstants.ERR_INCOMPATIBLE_DEREFERENCE + field + " <- " + refPath);
            }
        } else if (rvalue.getType() == RValueExpression.RValueType._emptyObject) {
            throw new EvaluationError(CrudConstants.ERR_INCOMPATIBLE_ASSIGNMENT + field + " <- {}");
        }

        return new FieldData(field, mdNode.getType(), refPath, refMdNode == null ? null : refMdNode.getType(), rvalue, mdNode.getFullPath());
    }

    private FieldData initializeObject(RValueExpression rvalue, FieldTreeNode refMdNode, FieldTreeNode mdNode, Path field, Path refPath) {
        if (rvalue.getType() == RValueExpression.RValueType._dereference) {
            if (!(refMdNode instanceof ObjectField)) {
                throw new EvaluationError(CrudConstants.ERR_INCOMPATIBLE_ASSIGNMENT + field + " <- " + refPath);
            }
        } else if (rvalue.getType() == RValueExpression.RValueType._value) {
            throw new EvaluationError(CrudConstants.ERR_INCOMPATIBLE_ASSIGNMENT + field + " <- " + rvalue.getValue());
        }
        return new FieldData(field, mdNode.getType(), refPath, refMdNode == null ? null : refMdNode.getType(), rvalue, mdNode.getFullPath());
    }

    @Override
    public void getUpdateFields(Set fields) {
        for (FieldData df : setValues) {
            fields.add(df.absField);
        }
    }

    @Override
    public boolean update(JsonDoc doc, FieldTreeNode contextMd, Path contextPath) {
        boolean ret = false;
        LOGGER.debug("Starting");
        for (FieldData df : setValues) {
            LOGGER.debug("Set field {} in ctx: {}", df.field, contextPath);
            JsonNode oldValueNode = null;
            JsonNode newValueNode = null;
            Object newValue = null;
            Type newValueType = null;
            switch (df.value.getType()) {
                case _null:
                    newValueNode = factory.nullNode();
                    break;
                case _emptyObject:
                    newValueNode = factory.objectNode();
                    break;
                case _dereference:
                    JsonNode refNode = doc.get(new Path(contextPath, df.refPath));
                    if (refNode != null) {
                        newValueNode = refNode.deepCopy();
                        newValue = df.refType.fromJson(newValueNode);
                        newValueType = df.refType;
                    }
                    break;
                case _value:
                    newValue = df.value.getValue().getValue();
                    newValueNode = df.fieldType.toJson(factory, newValue);
                    newValueType = df.fieldType;
                    break;
            }
            oldValueNode = setOrAdd(doc, contextPath, df, newValueNode, newValue, newValueType);
            if (!ret) {
                ret = oldAndNewAreDifferent(oldValueNode, newValueNode);
            }
        }
        LOGGER.debug("Completed");
        return ret;
    }

    private JsonNode setOrAdd(JsonDoc doc, Path contextPath, FieldData df, JsonNode newValueNode, Object newValue, Type newValueType) {
        JsonNode oldValueNode = null;
        Path fieldPath = new Path(contextPath, df.field);
        if (op == UpdateOperator._set) {
            oldValueNode = doc.modify(fieldPath, newValueNode, true);
        } else if (op == UpdateOperator._add) {
            oldValueNode = doc.get(fieldPath);
            if (newValueNode != null && oldValueNode != null) {
                newValueNode = df.fieldType.toJson(factory, Arith.add(df.fieldType.fromJson(oldValueNode), newValue, Arith.promote(df.fieldType, newValueType)));
                doc.modify(fieldPath, newValueNode, false);
            }
        }
        return oldValueNode;
    }

    private boolean oldAndNewAreDifferent(JsonNode oldValueNode, JsonNode newValueNode) {
        if (oldValueNode == null && newValueNode != null) {
            return true;
        } else if (oldValueNode != null && newValueNode == null) {
            return true;
        } else if (oldValueNode != null && newValueNode != null) {
            return !oldValueNode.equals(newValueNode);
        } else {
            return false;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy