com.redhat.lightblue.crud.mongo.Translator Maven / Gradle / Ivy
The 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.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 java.util.Set;
import java.util.HashSet;
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.ResolvedReferenceField;
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.NaryValueRelationalExpression;
import com.redhat.lightblue.query.NaryFieldRelationalExpression;
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.Projection;
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 = "objectType";
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 = "mongo-translation:no-object-type";
public static final String ERR_INVALID_OBJECTTYPE = "mongo-translation:invalid-object-type";
public static final String ERR_INVALID_FIELD = "mongo-translation:invalid-field";
public static final String ERR_INVALID_COMPARISON = "mongo-translation:invalid-comparison";
public static final String ERR_CANNOT_TRANSLATE_REFERENCE = "mongo-translation:cannot-translate-reference";
private static final Logger LOGGER = LoggerFactory.getLogger(Translator.class);
private final MetadataResolver mdResolver;
private final JsonNodeFactory factory;
private static final Map BINARY_COMPARISON_OPERATOR_JS_MAP;
private static final Map BINARY_COMPARISON_OPERATOR_MAP;
private static final Map NARY_LOGICAL_OPERATOR_MAP;
private static final Map UNARY_LOGICAL_OPERATOR_MAP;
private static final Map 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, "$nor"); // Note: _not maps to $nor, not $not. $not applies to operator expression
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 toJson(List objects) {
List 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 of 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&&seg+1 getRequiredFields(EntityMetadata md,
Projection p,
QueryExpression q,
Sort s) {
Set fields=new HashSet<>();
FieldCursor cursor=md.getFieldCursor();
// skipPrefix will be set to the root of a subtree that needs to be skipped.
// If it is non-null, all fields with a prefix 'skipPrefix' will be skipped.
Path skipPrefix=null;
if(cursor.next()) {
boolean done=false;
do {
Path field=cursor.getCurrentPath();
if(skipPrefix!=null) {
if(!field.matchingDescendant(skipPrefix)) {
skipPrefix=null;
}
}
if(skipPrefix==null) {
FieldTreeNode node=cursor.getCurrentNode();
LOGGER.debug("Checking if {} is included ({})",field,node);
if(node instanceof ResolvedReferenceField||
node instanceof ReferenceField) {
skipPrefix=field;
} else {
if( (node instanceof ObjectField) ||
(node instanceof ArrayField && ((ArrayField)node).getElement() instanceof ObjectArrayElement) ||
(node instanceof ArrayElement)) {
// include its member fields
} else if( (p!=null && p.isFieldRequiredToEvaluateProjection(field)) ||
(q!=null && q.isRequired(field)) ||
(s!=null && s.isRequired(field)) ) {
LOGGER.debug("{}: required",field);
fields.add(field);
} else {
LOGGER.debug("{}: not required", field);
}
done=!cursor.next();
}
} else
done=!cursor.next();
} while(!done);
}
return fields;
}
/**
* Writes a MongoDB projection containing fields to evaluate the projection, sort, and query
*/
public DBObject translateProjection(EntityMetadata md,
Projection p,
QueryExpression q,
Sort s) {
Set fields=getRequiredFields(md,p,q,s);
BasicDBObject ret=new BasicDBObject();
for(Path f:fields) {
ret.append(translatePath(f),1);
}
LOGGER.debug("Resulting projection:{}",ret);
return ret;
}
/**
* 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 = createIdFrom(valueObject);
}
obj.put(translatePath(field), filterBigNumbers(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(context, (FieldComparisonExpression) query);
} else if (query instanceof NaryLogicalExpression) {
ret = translateNaryLogicalExpression(context, (NaryLogicalExpression) query);
} else if (query instanceof NaryValueRelationalExpression) {
ret = translateNaryValueRelationalExpression(context, (NaryValueRelationalExpression) query);
} else if (query instanceof NaryFieldRelationalExpression) {
ret = translateNaryFieldRelationalExpression(context, (NaryFieldRelationalExpression) 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy