Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
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.mongo.crud;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Date;
import java.util.stream.Stream;
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.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.Index;
import com.redhat.lightblue.metadata.IndexSortKey;
import com.redhat.lightblue.metadata.ObjectArrayElement;
import com.redhat.lightblue.metadata.ObjectField;
import com.redhat.lightblue.metadata.ReferenceField;
import com.redhat.lightblue.metadata.ResolvedReferenceField;
import com.redhat.lightblue.metadata.SimpleArrayElement;
import com.redhat.lightblue.metadata.SimpleField;
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.metadata.types.*;
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.NaryFieldRelationalExpression;
import com.redhat.lightblue.query.NaryLogicalExpression;
import com.redhat.lightblue.query.NaryLogicalOperator;
import com.redhat.lightblue.query.NaryRelationalOperator;
import com.redhat.lightblue.query.NaryValueRelationalExpression;
import com.redhat.lightblue.query.PartialUpdateExpression;
import com.redhat.lightblue.query.PrimitiveUpdateExpression;
import com.redhat.lightblue.query.Projection;
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.JsonNodeCursor;
import com.redhat.lightblue.util.MutablePath;
import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.Util;
import com.redhat.lightblue.mongo.crud.js.JSQueryTranslator;
/**
* Translations between BSON and JSON. This class is thread-safe, and can be
* shared between threads
*/
public class ExpressionTranslator {
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";
private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionTranslator.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;
/**
* This is used as flow control. If query translation fails
* because the query cannot be translated and a Javascript query
* is required, this is thrown. It is caught and handled when it is
* possible to write a JS query
*/
private static final class NeedsJS extends RuntimeException {}
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 ExpressionTranslator(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();
}
/**
* Appends objectType:X to the query
*/
public static QueryExpression appendObjectType(QueryExpression q,String entityName) {
QueryExpression ot=new ValueComparisonExpression(DocTranslator.OBJECT_TYPE,BinaryComparisonOperator._eq,new Value(entityName));
if(q==null) {
return ot;
} if(q instanceof NaryLogicalExpression &&
((NaryLogicalExpression)q).getOp()==NaryLogicalOperator._and) {
List l=new ArrayList<>(((NaryLogicalExpression)q).getQueries());
l.add(ot);
return new NaryLogicalExpression(NaryLogicalOperator._and,l);
} else {
return new NaryLogicalExpression(NaryLogicalOperator._and,q,ot);
}
}
/**
* 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);
}
} catch (Error e) {
// rethrow lightblue error
throw e;
} catch (Exception e) {
// throw new Error (preserves current error context)
LOGGER.error(e.getMessage(), e);
throw Error.get(MongoCrudConstants.ERR_INVALID_OBJECT, e.getMessage());
} 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) {
Error.push("translateQuery");
FieldTreeNode mdRoot = md.getFieldTreeRoot();
try {
return translate(mdRoot, query, md, new MutablePath());
} catch (Error e) {
// rethrow lightblue error
throw e;
} catch (Exception e) {
// throw new Error (preserves current error context)
LOGGER.error(e.getMessage(), e);
throw Error.get(MongoCrudConstants.ERR_INVALID_OBJECT, e.getMessage());
} 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 {
Error.push("translateUpdate");
try {
BasicDBObject ret = new BasicDBObject();
translateUpdate(md.getFieldTreeRoot(), expr, ret);
return ret;
} catch (Error | CannotTranslateException e) {
// rethrow lightblue error
throw e;
} catch (Exception e) {
// throw new Error (preserves current error context)
LOGGER.error(e.getMessage(), e);
throw Error.get(MongoCrudConstants.ERR_INVALID_OBJECT, e.getMessage());
} finally {
Error.pop();
}
}
/**
* Returns all the fields required to evaluate the given projection, query,
* and sort
*
* @param md Entity metadata
* @param p Projection
* @param q Query
* @param s Sort
*
* All arguments are optional. The returned set contains the fields required
* to evaluate all the non-null expressions
*/
public static Set 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);
}
ret.append(translatePath(DocTranslator.HIDDEN_SUB_PATH),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 = DocTranslator.createIdFrom(valueObject);
}
obj.put(translatePath(field), DocTranslator.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;
}
/**
* @param context The field tree node for the latest enclosing context (array of elemMatch, or top level node)
* @param query The query to translate
* @param emd The metadata
* @param fullPath Current full path
*/
private DBObject translate(FieldTreeNode context, QueryExpression query, EntityMetadata emd, MutablePath fullPath) {
DBObject ret;
try {
if (query instanceof ArrayContainsExpression) {
ret = translateArrayContains(context, emd, (ArrayContainsExpression) query,fullPath);
} else if (query instanceof ArrayMatchExpression) {
ret = translateArrayElemMatch(context, emd, (ArrayMatchExpression) query, emd, fullPath);
} else if (query instanceof FieldComparisonExpression) {
ret = translateFieldComparison(context, emd, (FieldComparisonExpression) query, fullPath);
} else if (query instanceof NaryLogicalExpression) {
ret = translateNaryLogicalExpression(context, emd, (NaryLogicalExpression) query, emd, fullPath);
} else if (query instanceof NaryValueRelationalExpression) {
ret = translateNaryValueRelationalExpression(context, emd, (NaryValueRelationalExpression) query,fullPath);
} else if (query instanceof NaryFieldRelationalExpression) {
ret = translateNaryFieldRelationalExpression(context, emd, (NaryFieldRelationalExpression) query,fullPath);
} else if (query instanceof RegexMatchExpression) {
ret = translateRegexMatchExpression(context, emd, (RegexMatchExpression) query, emd, fullPath);
} else if (query instanceof UnaryLogicalExpression) {
ret = translateUnaryLogicalExpression(context, emd, (UnaryLogicalExpression) query, emd, fullPath);
} else {
ret = translateValueComparisonExpression(context, emd, (ValueComparisonExpression) query,fullPath);
}
} catch (NeedsJS x) {
// We can't translate the query, we have to write a $where query
// But we can't do that if we're in an arrayElemMatch
if(fullPath.isEmpty()) {
JSQueryTranslator tx=new JSQueryTranslator(emd);
ret=new BasicDBObject("$where",tx.translateQuery(query).toString());
} else
throw x; // Continue unroll stack
}
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;
}
private static class FieldInfo {
final Path field;
final FieldTreeNode fieldMd;
FieldInfo(Path field,FieldTreeNode fieldMd) {
this.field=field;
this.fieldMd=fieldMd;
}
}
private FieldInfo resolveFieldForQuery(FieldTreeNode context,Path contextPath,Path field) {
int n=field.numSegments();
// Two paths: p: the full thing, s: only the relative field
// If the field reference moves to a parent of relative field, we use p
// otherwise, we use s
MutablePath p=new MutablePath(contextPath);
MutablePath s=new MutablePath();
FieldTreeNode fieldNode=context;
for(int i=0;i translateValueList(Type t, List values,boolean idList) {
List