com.redhat.lightblue.crud.mongo.Translator.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.lightblue.crud.mongo.Translator.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.crud.mongo;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.redhat.lightblue.crud.MetadataResolver;
import com.redhat.lightblue.metadata.ArrayElement;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.metadata.EntityMetadata;
import com.redhat.lightblue.metadata.FieldCursor;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.ObjectArrayElement;
import com.redhat.lightblue.metadata.ObjectField;
import com.redhat.lightblue.metadata.ReferenceField;
import com.redhat.lightblue.metadata.SimpleArrayElement;
import com.redhat.lightblue.metadata.SimpleField;
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.query.ArrayContainsExpression;
import com.redhat.lightblue.query.ArrayMatchExpression;
import com.redhat.lightblue.query.ArrayUpdateExpression;
import com.redhat.lightblue.query.BinaryComparisonOperator;
import com.redhat.lightblue.query.CompositeSortKey;
import com.redhat.lightblue.query.FieldAndRValue;
import com.redhat.lightblue.query.FieldComparisonExpression;
import com.redhat.lightblue.query.NaryLogicalExpression;
import com.redhat.lightblue.query.NaryLogicalOperator;
import com.redhat.lightblue.query.NaryRelationalExpression;
import com.redhat.lightblue.query.NaryRelationalOperator;
import com.redhat.lightblue.query.PartialUpdateExpression;
import com.redhat.lightblue.query.PrimitiveUpdateExpression;
import com.redhat.lightblue.query.QueryExpression;
import com.redhat.lightblue.query.RValueExpression;
import com.redhat.lightblue.query.RegexMatchExpression;
import com.redhat.lightblue.query.SetExpression;
import com.redhat.lightblue.query.Sort;
import com.redhat.lightblue.query.SortKey;
import com.redhat.lightblue.query.UnaryLogicalExpression;
import com.redhat.lightblue.query.UnaryLogicalOperator;
import com.redhat.lightblue.query.UnsetExpression;
import com.redhat.lightblue.query.UpdateExpression;
import com.redhat.lightblue.query.UpdateExpressionList;
import com.redhat.lightblue.query.Value;
import com.redhat.lightblue.query.ValueComparisonExpression;
import com.redhat.lightblue.util.Error;
import com.redhat.lightblue.util.JsonDoc;
import com.redhat.lightblue.util.JsonNodeCursor;
import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.Util;

/**
 * Translations between BSON and JSON. This class is thread-safe, and can be shared between threads
 */
public class Translator {

    public static final String OBJECT_TYPE_STR = "object_type";
    public static final Path OBJECT_TYPE = new Path(OBJECT_TYPE_STR);

    public static final Path ID_PATH = new Path("_id");

    public static final String ERR_NO_OBJECT_TYPE = "NO_OBJECT_TYPE";
    public static final String ERR_INVALID_OBJECTTYPE = "INVALID_OBJECTTYPE";
    public static final String ERR_INVALID_FIELD = "INVALID_FIELD";
    public static final String ERR_INVALID_COMPARISON = "INVALID_COMPARISON";

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

    private final MetadataResolver mdResolver;
    private final JsonNodeFactory factory;

    private static final Map<BinaryComparisonOperator, String> BINARY_COMPARISON_OPERATOR_JS_MAP;
    private static final Map<BinaryComparisonOperator, String> BINARY_COMPARISON_OPERATOR_MAP;
    private static final Map<NaryLogicalOperator, String> NARY_LOGICAL_OPERATOR_MAP;
    private static final Map<UnaryLogicalOperator, String> UNARY_LOGICAL_OPERATOR_MAP;
    private static final Map<NaryRelationalOperator, String> NARY_RELATIONAL_OPERATOR_MAP;

    private static final String LITERAL_THIS_DOT = "this.";

    static {
        BINARY_COMPARISON_OPERATOR_JS_MAP = new HashMap<>();
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._eq, "==");
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._neq, "!=");
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._lt, "<");
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._gt, ">");
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._lte, "<=");
        BINARY_COMPARISON_OPERATOR_JS_MAP.put(BinaryComparisonOperator._gte, ">=");

        BINARY_COMPARISON_OPERATOR_MAP = new HashMap<>();
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._eq, "$eq");
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._neq, "$ne");
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._lt, "$lt");
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._gt, "$gt");
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._lte, "$lte");
        BINARY_COMPARISON_OPERATOR_MAP.put(BinaryComparisonOperator._gte, "$gte");

        NARY_LOGICAL_OPERATOR_MAP = new HashMap<>();
        NARY_LOGICAL_OPERATOR_MAP.put(NaryLogicalOperator._and, "$and");
        NARY_LOGICAL_OPERATOR_MAP.put(NaryLogicalOperator._or, "$or");

        UNARY_LOGICAL_OPERATOR_MAP = new HashMap<>();
        UNARY_LOGICAL_OPERATOR_MAP.put(UnaryLogicalOperator._not, "$not");

        NARY_RELATIONAL_OPERATOR_MAP = new HashMap<>();
        NARY_RELATIONAL_OPERATOR_MAP.put(NaryRelationalOperator._in, "$in");
        NARY_RELATIONAL_OPERATOR_MAP.put(NaryRelationalOperator._not_in, "$nin");
    }

    /**
     * Constructs a translator using the given metadata resolver and factory
     */
    public Translator(MetadataResolver mdResolver, JsonNodeFactory factory) {
        this.mdResolver = mdResolver;
        this.factory = factory;
    }

    /**
     * Translate a path to a mongo path
     *
     * Any * in the path is removed. Array indexes remain intact.
     */
    public static String translatePath(Path p) {
        StringBuilder str = new StringBuilder();
        int n = p.numSegments();
        for (int i = 0; i < n; i++) {
            String s = p.head(i);
            if (!s.equals(Path.ANY)) {
                if (i > 0) {
                    str.append('.');
                }
                str.append(s);
            }
        }
        return str.toString();
    }

    /**
     * Translate a path to a javascript path
     *
     * Path cannot have *. Indexes are put into brackets
     */
    public static String translateJsPath(Path p) {
        StringBuilder str = new StringBuilder();
        int n = p.numSegments();
        for (int i = 0; i < n; i++) {
            String s = p.head(i);
            if (s.equals(Path.ANY)) {
                throw Error.get(MongoCrudConstants.ERR_TRANSLATION_ERROR, p.toString());
            } else if (p.isIndex(i)) {
                str.append('[').append(s).append(']');
            } else {
                if (i > 0) {
                    str.append('.');
                }
                str.append(s);
            }
        }
        return str.toString();
    }

    /**
     * Translates a list of JSON documents to DBObjects. Translation is metadata driven.
     */
    public DBObject[] toBson(List<? extends JsonDoc> docs) {
        DBObject[] ret = new DBObject[docs.size()];
        int i = 0;
        for (JsonDoc doc : docs) {
            ret[i++] = toBson(doc);
        }
        return ret;
    }

    /**
     * Translates a JSON document to DBObject. Translation is metadata driven.
     */
    public DBObject toBson(JsonDoc doc) {
        LOGGER.debug("toBson() enter");
        JsonNode node = doc.get(OBJECT_TYPE);
        if (node == null) {
            throw Error.get(ERR_NO_OBJECT_TYPE);
        }
        EntityMetadata md = mdResolver.getEntityMetadata(node.asText());
        if (md == null) {
            throw Error.get(ERR_INVALID_OBJECTTYPE, node.asText());
        }
        DBObject ret = toBson(doc, md);
        LOGGER.debug("toBson() return");
        return ret;
    }

    /**
     * Traslates a DBObject document to Json document
     */
    public JsonDoc toJson(DBObject object) {
        LOGGER.debug("toJson() enter");
        Object type = object.get(OBJECT_TYPE_STR);
        if (type == null) {
            throw Error.get(ERR_NO_OBJECT_TYPE);
        }
        EntityMetadata md = mdResolver.getEntityMetadata(type.toString());
        if (md == null) {
            throw Error.get(ERR_INVALID_OBJECTTYPE, type.toString());
        }
        JsonDoc doc = toJson(object, md);
        LOGGER.debug("toJson() return");
        return doc;
    }

    /**
     * Translates DBObjects into Json documents
     */
    public List<JsonDoc> toJson(List<DBObject> objects) {
        List<JsonDoc> list = new ArrayList<>(objects.size());
        for (DBObject object : objects) {
            list.add(toJson(object));
        }
        return list;
    }

    /**
     * Add any fields in the old object that are not in the metadata to the new object
     */
    public void addInvisibleFields(DBObject oldDBObject, DBObject newObject, EntityMetadata md) {
        Merge merge = new Merge(md);
        merge.merge(oldDBObject, newObject);
    }

    public static Object getDBObject(DBObject start, Path p) {
        int n = p.numSegments();
        Object trc = start;
        for (int seg = 0; seg < n; seg++) {
            String segment = p.head(seg);
            if (segment.equals(Path.ANY)) {
                throw Error.get(MongoCrudConstants.ERR_TRANSLATION_ERROR, p.toString());
            } else if (Util.isNumber(segment)) {
                trc = ((List) trc).get(Integer.valueOf(segment));
            } else {
                trc = ((DBObject) trc).get(segment);
            }
            if (trc == null) {
                throw Error.get(MongoCrudConstants.ERR_TRANSLATION_ERROR, p.toString());
            }
        }
        return trc;
    }

    /**
     * Translates a sort expression to Mongo sort expression
     */
    public DBObject translate(Sort sort) {
        LOGGER.debug("translate {}", sort);
        Error.push("translateSort");
        DBObject ret;
        try {
            if (sort instanceof CompositeSortKey) {
                ret = translateCompositeSortKey((CompositeSortKey) sort);
            } else {
                ret = translateSortKey((SortKey) sort);
            }
        } finally {
            Error.pop();
        }
        return ret;
    }

    /**
     * Translates a query to Mongo query
     *
     * @param md Entity metadata
     * @param query The query expression
     */
    public DBObject translate(EntityMetadata md, QueryExpression query) {
        LOGGER.debug("translate {}", query);
        Error.push("translateQuery");
        FieldTreeNode mdRoot = md.getFieldTreeRoot();
        try {
            return translate(mdRoot, query);
        } finally {
            Error.pop();
        }
    }

    /**
     * Tranlates an update expression to Mongo query
     *
     * @param md Entity metedata
     * @param expr Update expression
     *
     * If the update expresssion is something that can be translated into a mongo update expression, translation is
     * performed. Otherwise, CannotTranslateException is thrown, and the update operation must be performed using the
     * Updaters.
     */
    public DBObject translate(EntityMetadata md, UpdateExpression expr) throws CannotTranslateException {
        LOGGER.debug("translate {}", expr);
        Error.push("translateUpdate");
        try {
            BasicDBObject ret = new BasicDBObject();
            translateUpdate(md.getFieldTreeRoot(), expr, ret);
            LOGGER.debug("translated={}", ret);
            return ret;
        } finally {
            Error.pop();
        }
    }

    /**
     * Translate update expression list and primitive updates. Anything else causes an exception.
     */
    private void translateUpdate(FieldTreeNode root, UpdateExpression expr, BasicDBObject dest)
            throws CannotTranslateException {
        if (expr instanceof ArrayUpdateExpression) {
            throw new CannotTranslateException(expr);
        } else if (expr instanceof PrimitiveUpdateExpression) {
            translatePrimitiveUpdate(root, (PrimitiveUpdateExpression) expr, dest);
        } else if (expr instanceof UpdateExpressionList) {
            for (PartialUpdateExpression x : ((UpdateExpressionList) expr).getList()) {
                translateUpdate(root, x, dest);
            }
        }
    }

    /**
     * Attempt to translate a primitive update expression. If the epxression touches any arrays or array elements,
     * translation fails.
     */
    private void translatePrimitiveUpdate(FieldTreeNode root, PrimitiveUpdateExpression expr, BasicDBObject dest)
            throws CannotTranslateException {
        if (expr instanceof SetExpression) {
            translateSet(root, (SetExpression) expr, dest);
        } else if (expr instanceof UnsetExpression) {
            translateUnset(root, (UnsetExpression) expr, dest);
        } else {
            throw new CannotTranslateException(expr);
        }
    }

    private void translateSet(FieldTreeNode root, SetExpression expr, BasicDBObject dest)
            throws CannotTranslateException {
        String op;
        switch (expr.getOp()) {
        case _set:
            op = "$set";
            break;
        case _add:
            op = "$inc";
            break;
        default:
            throw new CannotTranslateException(expr);
        }
        BasicDBObject obj = (BasicDBObject) dest.get(op);
        if (obj == null) {
            obj = new BasicDBObject();
            dest.put(op, obj);
        }
        for (FieldAndRValue frv : expr.getFields()) {
            Path field = frv.getField();
            if (hasArray(root, field)) {
                throw new CannotTranslateException(expr);
            }
            RValueExpression rvalue = frv.getRValue();
            if (rvalue.getType() == RValueExpression.RValueType._value) {
                Value value = rvalue.getValue();
                FieldTreeNode ftn = root.resolve(field);
                if (ftn == null) {
                    throw new CannotTranslateException(expr);
                }
                if (!(ftn instanceof SimpleField)) {
                    throw new CannotTranslateException(expr);
                }
                Object valueObject = ftn.getType().cast(value.getValue());
                if (field.equals(ID_PATH)) {
                    valueObject = new ObjectId(valueObject.toString());
                }
                obj.put(translatePath(field), valueObject);
            } else {
                throw new CannotTranslateException(expr);
            }
        }
    }

    private void translateUnset(FieldTreeNode root, UnsetExpression expr, BasicDBObject dest)
            throws CannotTranslateException {
        BasicDBObject obj = (BasicDBObject) dest.get("$unset");
        if (obj == null) {
            obj = new BasicDBObject();
            dest.put("$unset", obj);
        }
        for (Path field : expr.getFields()) {
            if (hasArray(root, field)) {
                throw new CannotTranslateException(expr);
            }
            obj.put(translatePath(field), "");
        }
    }

    /**
     * Returns true if the field is an array, or points to a field within an array
     */
    private boolean hasArray(FieldTreeNode root, Path field) throws CannotTranslateException {
        FieldTreeNode node = root.resolve(field);
        if (node == null) {
            throw new CannotTranslateException(field);
        }
        do {
            if (node instanceof ArrayField || node instanceof ArrayElement) {
                return true;
            } else {
                node = node.getParent();
            }
        } while (node != null);
        return false;
    }

    private DBObject translateSortKey(SortKey sort) {
        return new BasicDBObject(translatePath(sort.getField()), sort.isDesc() ? -1 : 1);
    }

    private DBObject translateCompositeSortKey(CompositeSortKey sort) {
        DBObject ret = null;
        for (SortKey key : sort.getKeys()) {
            if (ret == null) {
                ret = translateSortKey(key);
            } else {
                ret.put(translatePath(key.getField()), key.isDesc() ? -1 : 1);
            }
        }
        return ret;
    }

    private DBObject translate(FieldTreeNode context, QueryExpression query) {
        DBObject ret;
        if (query instanceof ArrayContainsExpression) {
            ret = translateArrayContains(context, (ArrayContainsExpression) query);
        } else if (query instanceof ArrayMatchExpression) {
            ret = translateArrayElemMatch(context, (ArrayMatchExpression) query);
        } else if (query instanceof FieldComparisonExpression) {
            ret = translateFieldComparison((FieldComparisonExpression) query);
        } else if (query instanceof NaryLogicalExpression) {
            ret = translateNaryLogicalExpression(context, (NaryLogicalExpression) query);
        } else if (query instanceof NaryRelationalExpression) {
            ret = translateNaryRelationalExpression(context, (NaryRelationalExpression) query);
        } else if (query instanceof RegexMatchExpression) {
            ret = translateRegexMatchExpression((RegexMatchExpression) query);
        } else if (query instanceof UnaryLogicalExpression) {
            ret = translateUnaryLogicalExpression(context, (UnaryLogicalExpression) query);
        } else {
            ret = translateValueComparisonExpression(context, (ValueComparisonExpression) query);
        }
        return ret;
    }

    private FieldTreeNode resolve(FieldTreeNode context, Path field) {
        FieldTreeNode node = context.resolve(field);
        if (node == null) {
            throw Error.get(ERR_INVALID_FIELD, field.toString());
        }
        return node;
    }

    /**
     * Converts a value list to a list of values with the proper type
     */
    private List<Object> translateValueList(Type t, List<Value> values) {
        if (values == null || values.isEmpty()) {
            throw new IllegalArgumentException(MongoCrudConstants.ERR_EMPTY_VALUE_LIST);
        }
        List<Object> ret = new ArrayList<>(values.size());
        for (Value v : values) {
            Object value = v == null ? null : v.getValue();
            if (value != null) {
                value = t.cast(value);
            }
            ret.add(value);
        }
        return ret;
    }

    private DBObject translateValueComparisonExpression(FieldTreeNode context, ValueComparisonExpression expr) {
        Type t = resolve(context, expr.getField()).getType();
        if (expr.getOp() == BinaryComparisonOperator._eq || expr.getOp() == BinaryComparisonOperator._neq) {
            if (!t.supportsEq()) {
                throw Error.get(ERR_INVALID_COMPARISON, expr.toString());
            }
        } else {
            if (!t.supportsOrdering()) {
                throw Error.get(ERR_INVALID_COMPARISON, expr.toString());
            }
        }
        Object valueObject = t.cast(expr.getRvalue().getValue());
        if (expr.getField().equals(ID_PATH)) {
            valueObject = new ObjectId(valueObject.toString());
        }
        if (expr.getOp() == BinaryComparisonOperator._eq) {
            return new BasicDBObject(translatePath(expr.getField()), valueObject);
        } else {
            return new BasicDBObject(translatePath(expr.getField()),
                    new BasicDBObject(BINARY_COMPARISON_OPERATOR_MAP.get(expr.getOp()), valueObject));
        }
    }

    private DBObject translateRegexMatchExpression(RegexMatchExpression expr) {
        StringBuilder options = new StringBuilder();
        BasicDBObject regex = new BasicDBObject("$regex", expr.getRegex());
        if (expr.isCaseInsensitive()) {
            options.append('i');
        }
        if (expr.isMultiline()) {
            options.append('m');
        }
        if (expr.isExtended()) {
            options.append('x');
        }
        if (expr.isDotAll()) {
            options.append('s');
        }
        String opStr = options.toString();
        if (opStr.length() > 0) {
            regex.append("$options", opStr);
        }
        return new BasicDBObject(translatePath(expr.getField()), regex);
    }

    private DBObject translateNaryRelationalExpression(FieldTreeNode context, NaryRelationalExpression expr) {
        Type t = resolve(context, expr.getField()).getType();
        if (t.supportsEq()) {
            List<Object> values = translateValueList(t, expr.getValues());
            return new BasicDBObject(translatePath(expr.getField()),
                    new BasicDBObject(NARY_RELATIONAL_OPERATOR_MAP.get(expr.getOp()), values));
        } else {
            throw Error.get(ERR_INVALID_FIELD, expr.toString());
        }
    }

    private DBObject translateUnaryLogicalExpression(FieldTreeNode context, UnaryLogicalExpression expr) {
        return new BasicDBObject(UNARY_LOGICAL_OPERATOR_MAP.get(expr.getOp()), translate(context, expr.getQuery()));
    }

    private DBObject translateNaryLogicalExpression(FieldTreeNode context, NaryLogicalExpression expr) {
        List<QueryExpression> queries = expr.getQueries();
        List<DBObject> list = new ArrayList<>(queries.size());
        for (QueryExpression query : queries) {
            list.add(translate(context, query));
        }
        return new BasicDBObject(NARY_LOGICAL_OPERATOR_MAP.get(expr.getOp()), list);
    }

    private String writeJSForLoop(StringBuilder bld, Path p, String varPrefix) {
        StringBuilder arr = new StringBuilder();
        int n = p.numSegments();
        int j = 0;
        for (int i = 0; i < n; i++) {
            String seg = p.head(i);
            if (Path.ANY.equals(seg)) {
                bld.append(String.format("for(var %s%d=0;%s%d<this.%s.length;%s%d++) {", varPrefix, j, varPrefix, j,
                        arr.toString(), varPrefix, j));
                arr.append('[').append(varPrefix).append(j).append(']');
                j++;
            } else if (p.isIndex(i)) {
                arr.append('[').append(seg).append(']');
            } else {
                if (i > 0) {
                    arr.append('.');
                }
                arr.append(seg);
            }
        }
        return arr.toString();
    }

    private DBObject translateFieldComparison(FieldComparisonExpression expr) {
        StringBuilder str = new StringBuilder(128);
        // We have to deal with array references here
        Path rField = expr.getRfield();
        Path lField = expr.getField();
        int rn = rField.nAnys();
        int ln = lField.nAnys();
        if (rn > 0 && ln > 0) {
            // Write a function with nested for loops
            str.append("function() {");
            String rJSField = writeJSForLoop(str, rField, "r");
            String lJSField = writeJSForLoop(str, lField, "l");
            str.append("if(this.").append(lJSField).append(BINARY_COMPARISON_OPERATOR_JS_MAP.get(expr.getOp()))
                    .append(LITERAL_THIS_DOT).append(rJSField).append(") { return true; }");
            for (int i = 0; i < rn + ln; i++) {
                str.append('}');
            }
            str.append("return false;}");
        } else if (rn > 0 || ln > 0) {
            // Only one of them has ANY, write a single for loop
            str.append("function() {");
            String jsField = writeJSForLoop(str, rn > 0 ? rField : lField, "i");
            str.append("if(this.").append(ln > 0 ? jsField : translateJsPath(lField))
                    .append(BINARY_COMPARISON_OPERATOR_JS_MAP.get(expr.getOp())).append(LITERAL_THIS_DOT)
                    .append(rn > 0 ? jsField : translateJsPath(rField)).append(") {return true;}");
            for (int i = 0; i < rn + ln; i++) {
                str.append('}');
            }
            str.append("return false;}");
        } else {
            // No ANYs, direct comparison
            str.append(LITERAL_THIS_DOT).append(translateJsPath(expr.getField()))
                    .append(BINARY_COMPARISON_OPERATOR_JS_MAP.get(expr.getOp())).append(LITERAL_THIS_DOT)
                    .append(translateJsPath(expr.getRfield()));
        }

        return new BasicDBObject("$where", str.toString());
    }

    private DBObject translateArrayElemMatch(FieldTreeNode context, ArrayMatchExpression expr) {
        FieldTreeNode arrayNode = resolve(context, expr.getArray());
        if (arrayNode instanceof ArrayField) {
            ArrayElement el = ((ArrayField) arrayNode).getElement();
            if (el instanceof ObjectArrayElement) {
                return new BasicDBObject(translatePath(expr.getArray()),
                        new BasicDBObject("$elemMatch", translate(el, expr.getElemMatch())));
            }
        }
        throw Error.get(ERR_INVALID_FIELD, expr.toString());
    }

    private DBObject translateArrayContains(FieldTreeNode context, ArrayContainsExpression expr) {
        DBObject ret = null;
        FieldTreeNode arrayNode = resolve(context, expr.getArray());
        if (arrayNode instanceof ArrayField) {
            Type t = ((ArrayField) arrayNode).getElement().getType();
            switch (expr.getOp()) {
            case _all:
                ret = translateArrayContainsAll(t, expr.getArray(), expr.getValues());
                break;
            case _any:
                ret = translateArrayContainsAny(t, expr.getArray(), expr.getValues());
                break;
            case _none:
                ret = translateArrayContainsNone(t, expr.getArray(), expr.getValues());
                break;
            }
        } else {
            throw Error.get(ERR_INVALID_FIELD, expr.toString());
        }
        return ret;
    }

    /**
     * <pre>
     *   { field : { $all:[values] } }
     * </pre>
     */
    private DBObject translateArrayContainsAll(Type t, Path array, List<Value> values) {
        return new BasicDBObject(translatePath(array), new BasicDBObject("$all", translateValueList(t, values)));
    }

    /**
     * <pre>
     *     { $or : [ {field:value1},{field:value2},...] }
     * </pre>
     */
    private DBObject translateArrayContainsAny(Type t, Path array, List<Value> values) {
        List<BasicDBObject> l = new ArrayList<>(values.size());
        for (Value x : values) {
            l.add(new BasicDBObject(translatePath(array),
                    x == null ? null : x.getValue() == null ? null : t.cast(x.getValue())));
        }
        return new BasicDBObject("$or", l);
    }

    /**
     * <pre>
     * { $not : { $or : [ {field:value1},{field:value2},...]}}
     * </pre>
     */
    private DBObject translateArrayContainsNone(Type t, Path array, List<Value> values) {
        return new BasicDBObject("$not", translateArrayContainsAny(t, array, values));
    }

    private JsonDoc toJson(DBObject object, EntityMetadata md) {
        // Translation is metadata driven. We don't know how to
        // translate something that's not defined in metadata.
        FieldCursor cursor = md.getFieldCursor();
        if (cursor.firstChild()) {
            return new JsonDoc(objectToJson(object, md, cursor));
        } else {
            return null;
        }
    }

    /**
     * Called after firstChild is called on cursor
     */
    private ObjectNode objectToJson(DBObject object, EntityMetadata md, FieldCursor mdCursor) {
        ObjectNode node = factory.objectNode();
        do {
            Path p = mdCursor.getCurrentPath();
            FieldTreeNode field = mdCursor.getCurrentNode();
            String fieldName = field.getName();
            LOGGER.debug("{}", p);
            // Retrieve field value
            Object value = object.get(fieldName);
            if (value != null) {
                if (field instanceof SimpleField) {
                    convertSimpleFieldToJson(node, field, value, fieldName);
                } else if (field instanceof ObjectField) {
                    convertObjectFieldToJson(node, fieldName, md, mdCursor, value, p);
                } else if (field instanceof ArrayField && value instanceof List && mdCursor.firstChild()) {
                    convertArrayFieldToJson(node, fieldName, md, mdCursor, value);
                } else if (field instanceof ReferenceField) {
                    convertReferenceFieldToJson();
                }
            }
        } while (mdCursor.nextSibling());
        return node;
    }

    private void convertSimpleFieldToJson(ObjectNode node, FieldTreeNode field, Object value, String fieldName) {
        JsonNode valueNode = ((SimpleField) field).getType().toJson(factory, value);
        if (valueNode != null) {
            node.set(fieldName, valueNode);
        }
    }

    private void convertObjectFieldToJson(ObjectNode node, String fieldName, EntityMetadata md,
            FieldCursor mdCursor, Object value, Path p) {
        if (value instanceof DBObject) {
            if (mdCursor.firstChild()) {
                JsonNode valueNode = objectToJson((DBObject) value, md, mdCursor);
                if (valueNode != null) {
                    node.set(fieldName, valueNode);
                }
                mdCursor.parent();
            }
        } else {
            LOGGER.error("Expected DBObject, found {} for {}", value.getClass(), p);
        }
    }

    @SuppressWarnings("rawtypes")
    private void convertArrayFieldToJson(ObjectNode node, String fieldName, EntityMetadata md, FieldCursor mdCursor,
            Object value) {
        ArrayNode valueNode = factory.arrayNode();
        node.set(fieldName, valueNode);
        // We must have an array element here
        FieldTreeNode x = mdCursor.getCurrentNode();
        if (x instanceof ArrayElement) {
            for (Object item : (List) value) {
                valueNode.add(arrayElementToJson(item, (ArrayElement) x, md, mdCursor));
            }
        }
        mdCursor.parent();
    }

    private void convertReferenceFieldToJson() {
        //TODO
        LOGGER.debug("Converting reference field: ");
    }

    private JsonNode arrayElementToJson(Object value, ArrayElement el, EntityMetadata md, FieldCursor mdCursor) {
        JsonNode ret = null;
        if (el instanceof SimpleArrayElement) {
            if (value != null) {
                ret = ((SimpleArrayElement) el).getType().toJson(factory, value);
            }
        } else {
            if (value != null) {
                if (value instanceof DBObject) {
                    if (mdCursor.firstChild()) {
                        ret = objectToJson((DBObject) value, md, mdCursor);
                        mdCursor.parent();
                    }
                } else {
                    LOGGER.error("Expected DBObject, got {}", value.getClass().getName());
                }
            }
        }
        return ret;
    }

    private BasicDBObject toBson(JsonDoc doc, EntityMetadata md) {
        LOGGER.debug("Entity: {}", md.getName());
        BasicDBObject ret = null;
        JsonNodeCursor cursor = doc.cursor();
        if (cursor.firstChild()) {
            ret = objectToBson(cursor, md);
        }
        return ret;
    }

    private Object toValue(Type t, JsonNode node) {
        if (node == null || node instanceof NullNode) {
            return null;
        } else {
            return t.fromJson(node);
        }
    }

    private void toBson(BasicDBObject dest, SimpleField fieldMd, Path path, JsonNode node) {
        Object value = toValue(fieldMd.getType(), node);
        // Should we add fields with null values to the bson doc? 
        if (value != null) {
            LOGGER.debug("{} = {}", path, value);
            if (path.equals(ID_PATH)) {
                value = new ObjectId(value.toString());
            }
            // Store big values as string. Mongo does not support big values
            if (value instanceof BigDecimal || value instanceof BigInteger) {
                value = value.toString();
            }

            dest.append(path.tail(0), value);
        }
    }

    /**
     * @param cursor The cursor, pointing to the first element of the object
     */
    private BasicDBObject objectToBson(JsonNodeCursor cursor, EntityMetadata md) {
        BasicDBObject ret = new BasicDBObject();
        do {
            Path path = cursor.getCurrentPath();
            JsonNode node = cursor.getCurrentNode();
            LOGGER.debug("field: {}", path);
            FieldTreeNode fieldMdNode = md.resolve(path);
            if (fieldMdNode == null) {
                throw Error.get(ERR_INVALID_FIELD, path.toString());
            }

            if (fieldMdNode instanceof SimpleField) {
                toBson(ret, (SimpleField) fieldMdNode, path, node);
            } else if (fieldMdNode instanceof ObjectField) {
                convertObjectFieldToBson(node, cursor, ret, path, md);
            } else if (fieldMdNode instanceof ArrayField) {
                convertArrayFieldToBson(node, cursor, ret, fieldMdNode, path, md);
            } else if (fieldMdNode instanceof ReferenceField) {
                convertReferenceFieldToBson();
            }
        } while (cursor.nextSibling());
        return ret;
    }

    private void convertObjectFieldToBson(JsonNode node, JsonNodeCursor cursor, BasicDBObject ret, Path path,
            EntityMetadata md) {
        if (node != null) {
            if (node instanceof ObjectNode) {
                if (cursor.firstChild()) {
                    ret.append(path.tail(0), objectToBson(cursor, md));
                    cursor.parent();
                }
            } else {
                throw Error.get(ERR_INVALID_FIELD, path.toString());
            }
        }
    }

    private void convertArrayFieldToBson(JsonNode node, JsonNodeCursor cursor, BasicDBObject ret,
            FieldTreeNode fieldMdNode, Path path, EntityMetadata md) {
        if (node != null) {
            if (node instanceof ArrayNode) {
                if (cursor.firstChild()) {
                    ret.append(path.tail(0), arrayToBson(cursor, ((ArrayField) fieldMdNode).getElement(), md));
                    cursor.parent();
                }
            } else {
                throw Error.get(ERR_INVALID_FIELD, path.toString());
            }
        }
    }

    private void convertReferenceFieldToBson() {
        //TODO  
        throw new java.lang.UnsupportedOperationException();
    }

    /**
     * @param cursor The cursor, pointing to the first element of the array
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private List arrayToBson(JsonNodeCursor cursor, ArrayElement el, EntityMetadata md) {
        List l = new ArrayList();
        if (el instanceof SimpleArrayElement) {
            Type t = el.getType();
            do {
                Object value = toValue(t, cursor.getCurrentNode());
                l.add(value);
            } while (cursor.nextSibling());
        } else {
            do {
                JsonNode node = cursor.getCurrentNode();
                if (node == null || node instanceof NullNode) {
                    l.add(null);
                } else {
                    if (cursor.firstChild()) {
                        l.add(objectToBson(cursor, md));
                        cursor.parent();
                    } else {
                        l.add(null);
                    }
                }
            } while (cursor.nextSibling());
        }
        return l;
    }

}