Please wait. This can take some minutes ...
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.
com.github.fakemongo.impl.aggregation.Project Maven / Gradle / Ivy
package com.github.fakemongo.impl.aggregation;
import com.github.fakemongo.impl.ExpressionParser;
import com.github.fakemongo.impl.Util;
import com.mongodb.*;
import com.mongodb.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* TODO : { project : { _id : 0} } must remove the _id field. If a $sort exist after...
*/
@ThreadSafe
public class Project extends PipelineKeyword {
private static final Logger LOG = LoggerFactory.getLogger(Project.class);
public static final Project INSTANCE = new Project();
private Project() {
}
static abstract class ProjectedAbstract {
static final Map> projectedAbstractMap = new HashMap>();
static {
projectedAbstractMap.put(ProjectedSize.KEYWORD, ProjectedSize.class);
projectedAbstractMap.put(ProjectedStrcasecmp.KEYWORD, ProjectedStrcasecmp.class);
projectedAbstractMap.put(ProjectedCmp.KEYWORD, ProjectedCmp.class);
projectedAbstractMap.put(ProjectedCond.KEYWORD, ProjectedCond.class);
projectedAbstractMap.put(ProjectedSubstr.KEYWORD, ProjectedSubstr.class);
projectedAbstractMap.put(ProjectedIfNull.KEYWORD, ProjectedIfNull.class);
projectedAbstractMap.put(ProjectedConcat.KEYWORD, ProjectedConcat.class);
projectedAbstractMap.put(ProjectedToLower.KEYWORD, ProjectedToLower.class);
projectedAbstractMap.put(ProjectedToUpper.KEYWORD, ProjectedToUpper.class);
projectedAbstractMap.put(ProjectedToDivide.KEYWORD, ProjectedToDivide.class);
projectedAbstractMap.put(ProjectedToMod.KEYWORD, ProjectedToMod.class);
projectedAbstractMap.put(ProjectedToMultiply.KEYWORD, ProjectedToMultiply.class);
projectedAbstractMap.put(ProjectedToAdd.KEYWORD, ProjectedToAdd.class);
projectedAbstractMap.put(ProjectedToSubtract.KEYWORD, ProjectedToSubtract.class);
projectedAbstractMap.put(ProjectedDateDayOfYear.KEYWORD, ProjectedDateDayOfYear.class);
projectedAbstractMap.put(ProjectedDateDayOfMonth.KEYWORD, ProjectedDateDayOfMonth.class);
projectedAbstractMap.put(ProjectedDateDayOfWeek.KEYWORD, ProjectedDateDayOfWeek.class);
projectedAbstractMap.put(ProjectedDateYear.KEYWORD, ProjectedDateYear.class);
projectedAbstractMap.put(ProjectedDateMonth.KEYWORD, ProjectedDateMonth.class);
projectedAbstractMap.put(ProjectedDateWeek.KEYWORD, ProjectedDateWeek.class);
projectedAbstractMap.put(ProjectedDateHour.KEYWORD, ProjectedDateHour.class);
projectedAbstractMap.put(ProjectedDateMinute.KEYWORD, ProjectedDateMinute.class);
projectedAbstractMap.put(ProjectedDateSecond.KEYWORD, ProjectedDateSecond.class);
projectedAbstractMap.put(ProjectedDateMillisecond.KEYWORD, ProjectedDateMillisecond.class);
projectedAbstractMap.put(ProjectedOr.KEYWORD, ProjectedOr.class);
projectedAbstractMap.put(ProjectedAnd.KEYWORD, ProjectedAnd.class);
projectedAbstractMap.put(ProjectedFilter.KEYWORD, ProjectedFilter.class);
}
final String keyword;
final String destName;
private ProjectedAbstract(String keyword, String destName, DBObject object) {
this.keyword = keyword;
this.destName = destName;
}
/**
* Transform the "object" into the "result" with this "value"
*
* @param result
* @param object
* @param key
*/
public abstract void unapply(DBObject result, DBObject object, String key);
abstract void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace);
public final void apply(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, DBObject value, String namespace) {
doWork(coll, projectResult, projectedFields, key, value.get(this.keyword), namespace);
}
/**
* Create the mapping and the criteria for the collection.
*
* @param projectResult find criteria.
* @param projectedFields mapping from criteria to project structure.
* @param key keyword from a DBObject.
* @param kvalue value for k from a DBObject.
* @param namespace "" if empty, "fieldname." elsewhere.
* @param projected use for unapplying.
*/
public static void createMapping(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object kvalue, String namespace, ProjectedAbstract projected) {
// Simple case : nb : "$pop"
if (kvalue instanceof String) {
String value = kvalue.toString();
if (value.startsWith("$")) {
// Case { date: "$date"}
// Extract filename from projection.
String fieldName = kvalue.toString().substring(1);
// Prepare for renaming.
multimapPut(projectedFields, fieldName, projected);
projectResult.removeField(key);
// Handle complex case like $bar.foo with a little trick.
if (fieldName.contains(".")) {
projectResult.put(fieldName.substring(0, fieldName.indexOf('.')), 1);
} else {
projectResult.put(fieldName, 1);
}
} else {
multimapPut(projectedFields, value, projected);
}
} else if (ExpressionParser.isDbObject(kvalue)) {
DBObject value = ExpressionParser.toDbObject(kvalue);
ProjectedAbstract projectedAbstract = getProjected(value, coll, key);
if (projectedAbstract != null) {
// case : {cmp : {$cmp:[$firstname, $lastname]}}
projectResult.removeField(key);
projectedAbstract.apply(coll, projectResult, projectedFields, key, value, namespace);
} else {
// case : {biggestCity: { name: "$biggestCity", pop: "$biggestPop" }}
projectResult.removeField(key);
for (Map.Entry subentry : Util.entrySet(value)) {
createMapping(coll, projectResult, projectedFields, subentry.getKey(), subentry.getValue(), namespace + key + ".", ProjectedRename.newInstance(namespace + key + "." + subentry.getKey(), coll, null));
}
}
} else {
// Case: {date : 1}
multimapPut(projectedFields, key, projected);
}
}
private static void multimapPut(Map> projectedFields, String key, ProjectedAbstract projected) {
if (projectedFields.containsKey(key)) {
projectedFields.get(key).add(projected);
} else {
List list = new ArrayList();
list.add(projected);
projectedFields.put(key, list);
}
}
/**
* Search the projected field if any.
*
* @param value the DbObject being worked.
* @param coll collection used.
* @param destName destination name for the field.
* @return null if it's not a keyword.
*/
private static ProjectedAbstract getProjected(DBObject value, DBCollection coll, String destName) {
for (Map.Entry> entry : projectedAbstractMap.entrySet()) {
if (value.containsField(entry.getKey())) {
try {
return entry.getValue().getConstructor(String.class, DBCollection.class, DBObject.class).newInstance(destName, coll, value);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof MongoException) {
throw (MongoException) e.getTargetException();
}
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
return null;
}
static void errorResult(DBCollection coll, int code, String err) {
((FongoDB) coll.getDB()).notOkErrorResult(code, err).throwOnError();
}
/**
* Extract a value from a field name or value.
*/
static T extractValue(DBObject object, Object fieldOrValue) {
if (fieldOrValue instanceof String && fieldOrValue.toString().startsWith("$")) {
return Util.extractField(object, fieldOrValue.toString().substring(1));
}
return (T) fieldOrValue;
}
}
static class ProjectedRename extends ProjectedAbstract {
public static final String KEYWORD = "$___fongo$internal$";
private ProjectedRename(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
}
public static ProjectedRename newInstance(String destName, DBCollection coll, DBObject object) {
return new ProjectedRename(destName, coll, object);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
if (key != null) {
Object value = Util.extractField(object, key);
Util.putValue(result, destName, value);
}
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
}
}
static class ProjectedSize extends ProjectedAbstract {
public static final String KEYWORD = "$size";
private final String field;
private final DBCollection coll;
public ProjectedSize(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedSize(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
this.coll = coll;
Object value = object.get(keyword);
field = parseOperand(value);
}
private String parseOperand(Object value) {
if (!(value instanceof String) && (!(value instanceof List) || ((List) value).size() != 1)) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 1 operand");
}
Object parsedValue = (value instanceof List) ? ((List) value).get(0) : value;
return (String) parsedValue;
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, destName, destName, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
int size = 0;
Object value = extractValue(object, field);
if (value instanceof Collection) {
size = ((Collection) value).size();
} else {
errorResult(coll, 17124, "the " + KEYWORD + " operator requires an list.");
}
result.put(destName, size);
}
}
static class ProjectedIfNull extends ProjectedAbstract {
public static final String KEYWORD = "$ifNull";
private final String field;
private final Object valueIfNull;
public ProjectedIfNull(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "the $ifNull operator requires an array of 2 operands");
}
@SuppressWarnings("unchecked") List values = (List) value;
this.field = values.get(0);
this.valueIfNull = values.get(1);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, field, field, namespace, this);
createMapping(coll, projectResult, projectedFields, String.valueOf(valueIfNull), valueIfNull, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Object value = extractValue(object, field);
if (value == null) {
value = extractValue(object, valueIfNull);
}
result.put(destName, value);
}
}
static class ProjectedConcat extends ProjectedAbstract {
public static final String KEYWORD = "$concat";
private List toConcat = null;
public ProjectedConcat(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() == 0) {
errorResult(coll, 16020, "the $concat operator requires an array of operands");
}
//noinspection unchecked
toConcat = (List) value;
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
for (Object field : toConcat) {
if (field instanceof String) {
createMapping(coll, projectResult, projectedFields, (String) field, field, namespace, this);
} else if (ExpressionParser.isDbObject(field)) {
// $concat : [ { $ifnull : [ "$item", "item is null" ] } ]
}
}
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
StringBuilder sb = new StringBuilder();
for (Object info : toConcat) {
Object value = extractValue(object, info);
if (value == null) {
result.put(destName, null);
return;
} else {
String str = value.toString();
sb.append(str);
}
}
result.put(destName, sb.toString());
}
}
static class ProjectedSubstr extends ProjectedAbstract {
public static final String KEYWORD = "$substr";
private final String field;
private final int start, end;
public ProjectedSubstr(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 3) {
errorResult(coll, 16020, "the $substr operator requires an array of 3 operands");
}
@SuppressWarnings("unchecked") List values = (List) value;
field = (String) values.get(0);
start = ((Number) values.get(1)).intValue();
end = ((Number) values.get(2)).intValue();
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, destName, destName, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Object exracted = extractValue(object, field);
String value = exracted == null ? null : String.valueOf(exracted);
if (value == null) {
value = "";
} else {
if (start >= value.length()) {
value = "";
} else {
value = value.substring(start, Math.min(end, value.length()));
}
}
result.put(destName, value);
}
}
static class ProjectedCmp extends ProjectedAbstract {
public static final String KEYWORD = "$cmp";
private final String field1;
private final String field2;
public ProjectedCmp(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
public ProjectedCmp(String keyword, String destName, DBCollection coll, DBObject object) {
super(keyword, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 2 operands");
}
List values = (List) value;
field1 = values.get(0);
field2 = values.get(1);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, field1, field1, namespace, this);
createMapping(coll, projectResult, projectedFields, field2, field2, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
String value = extractValue(object, field1).toString();
String secondValue = extractValue(object, field2).toString();
int strcmp = compare(value, secondValue);
result.put(destName, strcmp < 0 ? -1 : strcmp > 1 ? 1 : 0);
}
int compare(String value1, String value2) {
return value1.compareTo(value2);
}
}
static class ProjectedCond extends ProjectedAbstract {
static final String KEYWORD = "$cond";
private final DBObject cond;
private final Object cThen;
private final Object cElse;
public ProjectedCond(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedCond(String keyword, String destName, DBCollection coll, DBObject object) {
super(keyword, destName, object);
Object value = object.get(keyword);
if ((value instanceof List)) {
if (((List) value).size() != 3) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 3 operands");
}
List values = (List) value;
cond = (DBObject) values.get(0);
cThen = values.get(1);
cElse = values.get(2);
} else {
cond = null;
cElse = cThen = null;
}
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
// createMapping(coll, projectResult, projectedFields, cond, cond, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
String value = extractValue(object, cond).toString();
String secondValue = extractValue(object, cond).toString();
int strcmp = compare(value, secondValue);
result.put(destName, strcmp < 0 ? -1 : strcmp > 1 ? 1 : 0);
}
int compare(String value1, String value2) {
return value1.compareTo(value2);
}
}
static class ProjectedStrcasecmp extends ProjectedCmp {
static final String KEYWORD = "$strcasecmp";
public ProjectedStrcasecmp(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, coll, object);
}
@Override
protected int compare(String value1, String value2) {
return value1.compareToIgnoreCase(value2);
}
}
static class ProjectedToLower extends ProjectedAbstract {
static final String KEYWORD = "$toLower";
private final String field;
public ProjectedToLower(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToLower(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (value instanceof List) {
List values = (List) value;
if (values.size() != 1) {
errorResult(coll, 16020, "the " + keyword + " operator requires 1 operand(s)");
}
field = (String) values.get(0);
} else {
field = value.toString();
}
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, field, field, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Object value = extractValue(object, field);
if (value == null) {
value = "";
} else {
value = transformValue(value.toString());
}
result.put(destName, value);
}
String transformValue(String value) {
return value.toLowerCase();
}
}
static class ProjectedToUpper extends ProjectedToLower {
static final String KEYWORD = "$toUpper";
public ProjectedToUpper(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, coll, object);
}
@Override
protected String transformValue(String value) {
return value.toUpperCase();
}
}
static class ProjectedToDivide extends ProjectedAbstract {
static final String KEYWORD = "$divide";
private final Object expression1;
private final Object expression2;
public ProjectedToDivide(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToDivide(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 2 operands");
}
List values = (List) value;
expression1 = values.get(0);
expression2 = values.get(1);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
// createMapping(coll, projectResult, projectedFields, destName, destName, namespace, this);
createMapping(coll, projectResult, projectedFields, destName, expression1, namespace, this);
createMapping(coll, projectResult, projectedFields, destName, expression2, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
final Number left = extractValue(object, expression1);
final Number right = extractValue(object, expression2);
result.put(destName, Util.genericDiv(left, right));
}
}
static class ProjectedToMod extends ProjectedAbstract {
static final String KEYWORD = "$mod";
private final Object expression1;
private final Object expression2;
public ProjectedToMod(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToMod(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 2 operands");
}
List values = (List) value;
expression1 = values.get(0);
expression2 = values.get(1);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, destName, expression1, namespace, this);
createMapping(coll, projectResult, projectedFields, destName, expression2, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
final Number left = extractValue(object, expression1);
final Number right = extractValue(object, expression2);
result.put(destName, Util.genericMod(left, right));
}
}
static class ProjectedToMultiply extends ProjectedAbstract {
static final String KEYWORD = "$multiply";
private final Object expression1;
private final Object expression2;
public ProjectedToMultiply(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToMultiply(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array of 2 operands");
}
List values = (List) value;
expression1 = values.get(0);
expression2 = values.get(1);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, destName, expression1, namespace, this);
createMapping(coll, projectResult, projectedFields, destName, expression2, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
final Number left = extractValue(object, expression1);
final Number right = extractValue(object, expression2);
result.put(destName, Util.genericMul(left, right));
}
}
static class ProjectedToAdd extends ProjectedAbstract {
static final String KEYWORD = "$add";
private final List expressions;
public ProjectedToAdd(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToAdd(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() < 1) {
errorResult(coll, 16020, "the " + keyword + " operator requires an array with at least 1 operand");
}
expressions = (List) value;
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
for (Object expression : expressions) {
createMapping(coll, projectResult, projectedFields, destName, expression, namespace, this);
}
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Number add = extractValue(object, expressions.get(0));
if (expressions.size() > 1) {
for (int i = 1; i < expressions.size(); i++) {
add = Util.genericAdd(add, (Number) extractValue(object, expressions.get(i)));
}
}
result.put(destName, add);
}
}
static class ProjectedToSubtract extends ProjectedAbstract {
static final String KEYWORD = "$subtract";
private final List expressions;
public ProjectedToSubtract(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedToSubtract(String keyword, String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object value = object.get(keyword);
if (!(value instanceof List) || ((List) value).size() != 2) {
errorResult(coll, 16020, "Expression " + keyword + " takes exactly 2 arguments.");
}
expressions = (List) value;
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
for (Object expression : expressions) {
createMapping(coll, projectResult, projectedFields, destName, expression, namespace, this);
}
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Number add = extractValue(object, expressions.get(0));
for (int i = 1; i < expressions.size(); i++) {
add = Util.genericSub(add, (Number) extractValue(object, expressions.get(i)));
}
result.put(destName, add);
}
}
static abstract class ProjectedDate extends ProjectedAbstract {
private final String field;
private final int fromCalendar; // See Calendar.*
private final int modifier; // See Calendar.*
public ProjectedDate(String keyword, int fromCalendar, int modifier, String destName, DBCollection coll, DBObject object) {
super(keyword, destName, object);
Object value = object.get(keyword);
this.fromCalendar = fromCalendar;
this.modifier = modifier;
if (value instanceof List) {
List list = (List) value;
if (list.size() != 1) {
errorResult(coll, 16020, "Expression " + keyword + " takes exactly 1 arguments. " + list.size() + " were passed in.");
}
value = list.get(0);
}
if (!(value instanceof String)) {
errorResult(coll, 16020, "the " + keyword + " operator requires a field name");
}
this.field = (String) value;
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
createMapping(coll, projectResult, projectedFields, field, field, namespace, this);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Object value = extractValue(object, field);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
calendar.setTimeInMillis((((Date) value).getTime()));
int extracted = calendar.get(fromCalendar) + modifier;
result.put(destName, extracted);
}
}
// http://docs.mongodb.org/manual/reference/operator/aggregation-date/
static class ProjectedDateDayOfYear extends ProjectedDate {
public static final String KEYWORD = "$dayOfYear";
public ProjectedDateDayOfYear(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.DAY_OF_YEAR, 0, destName, coll, object);
}
}
static class ProjectedDateDayOfMonth extends ProjectedDate {
public static final String KEYWORD = "$dayOfMonth";
public ProjectedDateDayOfMonth(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.DAY_OF_MONTH, 0, destName, coll, object);
}
}
static class ProjectedDateDayOfWeek extends ProjectedDate {
public static final String KEYWORD = "$dayOfWeek";
public ProjectedDateDayOfWeek(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.DAY_OF_WEEK, 0, destName, coll, object);
}
}
static class ProjectedDateYear extends ProjectedDate {
public static final String KEYWORD = "$year";
public ProjectedDateYear(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.YEAR, 0, destName, coll, object);
}
}
static class ProjectedDateMonth extends ProjectedDate {
public static final String KEYWORD = "$month";
public ProjectedDateMonth(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.MONTH, 1, destName, coll, object);
}
}
static class ProjectedDateWeek extends ProjectedDate {
public static final String KEYWORD = "$week";
public ProjectedDateWeek(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.WEEK_OF_YEAR, -1, destName, coll, object);
}
}
static class ProjectedDateHour extends ProjectedDate {
public static final String KEYWORD = "$hour";
public ProjectedDateHour(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.HOUR_OF_DAY, 0, destName, coll, object);
}
}
static class ProjectedDateMinute extends ProjectedDate {
public static final String KEYWORD = "$minute";
public ProjectedDateMinute(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.MINUTE, 0, destName, coll, object);
}
}
static class ProjectedDateSecond extends ProjectedDate {
public static final String KEYWORD = "$second";
public ProjectedDateSecond(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.SECOND, 0, destName, coll, object);
}
}
static class ProjectedDateMillisecond extends ProjectedDate {
public static final String KEYWORD = "$millisecond";
public ProjectedDateMillisecond(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, Calendar.MILLISECOND, 0, destName, coll, object);
}
}
/**
* Simple {@see http://docs.mongodb.org/manual/reference/aggregation/project/#pipe._S_project}
*/
@Override
public DBCollection apply(DB originalDB, DBCollection coll, DBObject object) {
LOG.debug("project() : {}", object);
DBObject project = ExpressionParser.toDbObject(object.get(getKeyword()));
DBObject projectResult = Util.clone(project);
// Extract fields who will be renamed.
Map> projectedFields = new HashMap>();
for (Map.Entry entry : Util.entrySet(project)) {
if (entry.getValue() != null) {
ProjectedAbstract.createMapping(coll, projectResult, projectedFields, entry.getKey(), entry.getValue(), "", ProjectedRename.newInstance(entry.getKey(), coll, null));
}
}
LOG.debug("project() of {} renamed {}", projectResult, projectedFields);
List objects = coll.find(null, projectResult).toArray();
// Rename or transform fields
List objectsResults = new ArrayList(objects.size());
for (DBObject result : objects) {
DBObject renamed = new BasicDBObject(FongoDBCollection.ID_FIELD_NAME, result.get(FongoDBCollection.ID_FIELD_NAME));
for (Map.Entry> entry : projectedFields.entrySet()) {
if (Util.containsField(result, entry.getKey())) {
for (ProjectedAbstract projected : entry.getValue()) {
projected.unapply(renamed, result, entry.getKey());
}
}
}
// TODO REFACTOR
// Handle special case like ifNull who can doesn't have field in list.
for (List projecteds : projectedFields.values()) {
for (ProjectedAbstract projected : projecteds) {
// if (!projected.isDone() && (projected.keyword.recallIfNotFound)) {
projected.unapply(renamed, result, null);
// }
// projected.setDone(false);
}
}
objectsResults.add(renamed);
}
coll = dropAndInsert(coll, objectsResults);
LOG.debug("project() : {}, result : {}", object, objects);
return coll;
}
@Override
public String getKeyword() {
return "$project";
}
public static class ProjectedFilter extends ProjectedAbstract {
public static final String KEYWORD = "$filter";
private static final String INPUT = "input";
private static final String COND = "cond";
private static final String ITEMS = "items";
private static final String AS = "as";
public ProjectedFilter(String destName, DBCollection coll, DBObject object) {
this(KEYWORD, destName, coll, object);
}
ProjectedFilter(String keyword, String destName, DBCollection coll, DBObject pipeline) {
super(KEYWORD, destName, pipeline);
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
Object items = result.get(ITEMS);
if (items == null && key == null) {
result.put(ITEMS, null);
return;
}
BasicDBObject basicDBObject = (BasicDBObject) object;
BasicDBList elementToAdd = (BasicDBList) basicDBObject.get(key);
if (elementToAdd == null)
return;
result.put(ITEMS, elementToAdd);
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String items_key, Object pipeline, String namespace) {
BasicDBObject pipelineOasicDBObject = (BasicDBObject) pipeline;
String input = getInput(pipelineOasicDBObject);
BasicDBObject queryToDelete = new BasicDBObject("$pull", new BasicDBObject(input, projectQuery(new ArrayList(), new BasicDBObject(), getAs(pipelineOasicDBObject), getCond(pipelineOasicDBObject))));
DBCollection tmpCollection = getClonedCollection(coll);
tmpCollection.updateMulti(new BasicDBObject(), queryToDelete);
subtraction(input, tmpCollection, coll);
createMapping(coll, projectResult, projectedFields, INPUT, input, namespace, this);
}
/**
* This method creates a new DBCollection with the elements of the DBCollection passed like parameter
*
* @param coll
* @return a copy of the DBCollection
*/
private DBCollection getClonedCollection(DBCollection coll) {
DBCollection tmpCollection = new FongoDBCollection((FongoDB) coll.getDB(), "tmp");
DBCursor cursor = coll.find();
while (cursor.hasNext())
tmpCollection.insert(cursor.next());
return tmpCollection;
}
private String getInput(BasicDBObject pipelineOasicDBObject) {
String input = pipelineOasicDBObject.getString(INPUT);
return (input.startsWith("$")) ? input.substring(1, input.length())
: input;
}
private String getAs(BasicDBObject pipelineOasicDBObject) {
return pipelineOasicDBObject.getString(AS);
}
private BasicDBObject getCond(BasicDBObject pipelineOasicDBObject) {
return (BasicDBObject) pipelineOasicDBObject.get(COND);
}
private boolean isLeaf(Object element) {
return !(element instanceof BasicDBObject);
}
/**
* This method creates a query to pass at a collection. This query will remove the element which DOESN'T match with the piupeline condition
*
* @param as
* @param cond
* @return
*/
private BasicDBObject projectQuery(List leaves, BasicDBObject result, String as, BasicDBObject cond) {
if (cond.isEmpty())
return new BasicDBObject();
for (String function : cond.keySet()) {
BasicDBList functionParameters = (BasicDBList) cond.get(function);
if (functionParameters instanceof List) {
for (Object ele : functionParameters) {
if (isLeaf(ele))
return getLeafBasicDBObject(ele, as, functionParameters, function);
else {
BasicDBObject leaf = projectQuery(leaves, result, as, (BasicDBObject) ele);
leaves.addAll(Arrays.asList(leaf));
result.append(function, leaves);
}
}
}
}
return result;
}
private BasicDBObject getLeafBasicDBObject(Object ele, String as, BasicDBList functionParameters, String function) {
if (!(ele instanceof String))
return new BasicDBObject();
String[] functionValue = ((String) ele).split("\\.");
String alias = functionValue[0];
if (!alias.replaceAll("\\$", "").equals(as))
throw new IllegalArgumentException("Use of undefined variable: " + alias.replaceAll("\\$", ""));
String valueToApplay = functionValue[1];
Object value = functionParameters.get(1);
BasicDBObject condition = new BasicDBObject(function, value);
BasicDBObject result = new BasicDBObject(valueToApplay, condition);
return result;
}
private void subtraction(String input, DBCollection sourceCollection, DBCollection targetCollection) {
DBCursor cursor = sourceCollection.find();
while (cursor.hasNext()) {
DBObject document = (DBObject) cursor.next();
BasicDBList arrayEmelents = (BasicDBList) document.get(input);
if (arrayEmelents != null) {
for (int i = 0; i <= arrayEmelents.size() - 1; i++) {
BasicDBObject arrayObjectToDelete = (BasicDBObject) arrayEmelents.get(i);
targetCollection.updateMulti(new BasicDBObject(), new BasicDBObject("$pull", new BasicDBObject(input, arrayObjectToDelete)));
}
}
}
}
}
public static class ProjectedOr extends ProjectedAbstract {
public static final String KEYWORD = "$or";
private List fromLocations = null;
public ProjectedOr(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object from = object.get(keyword);
if (from instanceof List) {
fromLocations = (List) from;
} else if (from instanceof String) {
fromLocations = new ArrayList();
fromLocations.add((String) from);
}
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
for (String fromLocation : fromLocations) {
if (fromLocation.startsWith("$")) {
fromLocation = fromLocation.substring(1);
}
BasicDBList list = (BasicDBList) object.get(fromLocation);
Boolean orValue = false;
for (Object o : list) {
if (o instanceof Boolean) {
Boolean current = (Boolean) o;
orValue = current || orValue;
if (orValue) {
break;
}
}
}
result.put(destName, orValue);
}
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
for (String fromLocation : fromLocations) {
createMapping(coll, projectResult, projectedFields, destName, fromLocation, namespace, this); }
}
}
public static class ProjectedAnd extends ProjectedAbstract {
public static final String KEYWORD = "$and";
private List fromLocations = null;
public ProjectedAnd(String destName, DBCollection coll, DBObject object) {
super(KEYWORD, destName, object);
Object from = object.get(keyword);
if (from instanceof List) {
fromLocations = (List) from;
} else if (from instanceof String) {
fromLocations = new ArrayList();
fromLocations.add((String) from);
}
}
@Override
public void unapply(DBObject result, DBObject object, String key) {
for (String fromLocation : fromLocations) {
if (fromLocation.startsWith("$")) {
fromLocation = fromLocation.substring(1);
}
BasicDBList list = (BasicDBList) object.get(fromLocation);
Boolean andValue = true;
for (Object o : list) {
if (o instanceof Boolean) {
Boolean current = (Boolean) o;
andValue = current && andValue;
if (!andValue) {
break;
}
}
}
result.put(destName, andValue);
}
}
@Override
void doWork(DBCollection coll, DBObject projectResult, Map> projectedFields, String key, Object value, String namespace) {
for (String fromLocation : fromLocations) {
createMapping(coll, projectResult, projectedFields, destName, fromLocation, namespace, this); }
}
}
}