/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.spel;
import static org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* An {@link ExpressionNode} representing a method reference.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Sebastien Gerard
* @author Christoph Strobl
*/
public class MethodReferenceNode extends ExpressionNode {
private static final Map<String, AggregationMethodReference> FUNCTIONS;
static {
Map<String, AggregationMethodReference> map = new HashMap<String, AggregationMethodReference>();
// BOOLEAN OPERATORS
map.put("and", arrayArgumentAggregationMethodReference().forOperator("$and"));
map.put("or", arrayArgumentAggregationMethodReference().forOperator("$or"));
map.put("not", arrayArgumentAggregationMethodReference().forOperator("$not"));
// SET OPERATORS
map.put("setEquals", arrayArgumentAggregationMethodReference().forOperator("$setEquals"));
map.put("setIntersection", arrayArgumentAggregationMethodReference().forOperator("$setIntersection"));
map.put("setUnion", arrayArgumentAggregationMethodReference().forOperator("$setUnion"));
map.put("setDifference", arrayArgumentAggregationMethodReference().forOperator("$setDifference"));
// 2nd.
map.put("setIsSubset", arrayArgumentAggregationMethodReference().forOperator("$setIsSubset"));
map.put("anyElementTrue", arrayArgumentAggregationMethodReference().forOperator("$anyElementTrue"));
map.put("allElementsTrue", arrayArgumentAggregationMethodReference().forOperator("$allElementsTrue"));
// COMPARISON OPERATORS
map.put("cmp", arrayArgumentAggregationMethodReference().forOperator("$cmp"));
map.put("eq", arrayArgumentAggregationMethodReference().forOperator("$eq"));
map.put("gt", arrayArgumentAggregationMethodReference().forOperator("$gt"));
map.put("gte", arrayArgumentAggregationMethodReference().forOperator("$gte"));
map.put("lt", arrayArgumentAggregationMethodReference().forOperator("$lt"));
map.put("lte", arrayArgumentAggregationMethodReference().forOperator("$lte"));
map.put("ne", arrayArgumentAggregationMethodReference().forOperator("$ne"));
// ARITHMETIC OPERATORS
map.put("abs", singleArgumentAggregationMethodReference().forOperator("$abs"));
map.put("add", arrayArgumentAggregationMethodReference().forOperator("$add"));
map.put("ceil", singleArgumentAggregationMethodReference().forOperator("$ceil"));
map.put("divide", arrayArgumentAggregationMethodReference().forOperator("$divide"));
map.put("exp", singleArgumentAggregationMethodReference().forOperator("$exp"));
map.put("floor", singleArgumentAggregationMethodReference().forOperator("$floor"));
map.put("ln", singleArgumentAggregationMethodReference().forOperator("$ln"));
map.put("log", arrayArgumentAggregationMethodReference().forOperator("$log"));
map.put("log10", singleArgumentAggregationMethodReference().forOperator("$log10"));
map.put("mod", arrayArgumentAggregationMethodReference().forOperator("$mod"));
map.put("multiply", arrayArgumentAggregationMethodReference().forOperator("$multiply"));
map.put("pow", arrayArgumentAggregationMethodReference().forOperator("$pow"));
map.put("sqrt", singleArgumentAggregationMethodReference().forOperator("$sqrt"));
map.put("subtract", arrayArgumentAggregationMethodReference().forOperator("$subtract"));
map.put("trunc", singleArgumentAggregationMethodReference().forOperator("$trunc"));
// STRING OPERATORS
map.put("concat", arrayArgumentAggregationMethodReference().forOperator("$concat"));
map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp"));
map.put("substr", arrayArgumentAggregationMethodReference().forOperator("$substr"));
map.put("toLower", singleArgumentAggregationMethodReference().forOperator("$toLower"));
map.put("toUpper", singleArgumentAggregationMethodReference().forOperator("$toUpper"));
map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp"));
map.put("indexOfBytes", arrayArgumentAggregationMethodReference().forOperator("$indexOfBytes"));
map.put("indexOfCP", arrayArgumentAggregationMethodReference().forOperator("$indexOfCP"));
map.put("split", arrayArgumentAggregationMethodReference().forOperator("$split"));
map.put("strLenBytes", singleArgumentAggregationMethodReference().forOperator("$strLenBytes"));
map.put("strLenCP", singleArgumentAggregationMethodReference().forOperator("$strLenCP"));
map.put("substrCP", arrayArgumentAggregationMethodReference().forOperator("$substrCP"));
// TEXT SEARCH OPERATORS
map.put("meta", singleArgumentAggregationMethodReference().forOperator("$meta"));
// ARRAY OPERATORS
map.put("arrayElemAt", arrayArgumentAggregationMethodReference().forOperator("$arrayElemAt"));
map.put("concatArrays", arrayArgumentAggregationMethodReference().forOperator("$concatArrays"));
map.put("filter", mapArgumentAggregationMethodReference().forOperator("$filter") //
.mappingParametersTo("input", "as", "cond"));
map.put("isArray", singleArgumentAggregationMethodReference().forOperator("$isArray"));
map.put("size", singleArgumentAggregationMethodReference().forOperator("$size"));
map.put("slice", arrayArgumentAggregationMethodReference().forOperator("$slice"));
map.put("reverseArray", singleArgumentAggregationMethodReference().forOperator("$reverseArray"));
map.put("reduce", mapArgumentAggregationMethodReference().forOperator("$reduce").mappingParametersTo("input",
"initialValue", "in"));
map.put("zip", mapArgumentAggregationMethodReference().forOperator("$zip").mappingParametersTo("inputs",
"useLongestLength", "defaults"));
map.put("in", arrayArgumentAggregationMethodReference().forOperator("$in"));
// VARIABLE OPERATORS
map.put("map", mapArgumentAggregationMethodReference().forOperator("$map") //
.mappingParametersTo("input", "as", "in"));
map.put("let", mapArgumentAggregationMethodReference().forOperator("$let").mappingParametersTo("vars", "in"));
// LITERAL OPERATORS
map.put("literal", singleArgumentAggregationMethodReference().forOperator("$literal"));
// DATE OPERATORS
map.put("dayOfYear", singleArgumentAggregationMethodReference().forOperator("$dayOfYear"));
map.put("dayOfMonth", singleArgumentAggregationMethodReference().forOperator("$dayOfMonth"));
map.put("dayOfWeek", singleArgumentAggregationMethodReference().forOperator("$dayOfWeek"));
map.put("year", singleArgumentAggregationMethodReference().forOperator("$year"));
map.put("month", singleArgumentAggregationMethodReference().forOperator("$month"));
map.put("week", singleArgumentAggregationMethodReference().forOperator("$week"));
map.put("hour", singleArgumentAggregationMethodReference().forOperator("$hour"));
map.put("minute", singleArgumentAggregationMethodReference().forOperator("$minute"));
map.put("second", singleArgumentAggregationMethodReference().forOperator("$second"));
map.put("millisecond", singleArgumentAggregationMethodReference().forOperator("$millisecond"));
map.put("dateToString", mapArgumentAggregationMethodReference().forOperator("$dateToString") //
.mappingParametersTo("format", "date"));
map.put("isoDayOfWeek", singleArgumentAggregationMethodReference().forOperator("$isoDayOfWeek"));
map.put("isoWeek", singleArgumentAggregationMethodReference().forOperator("$isoWeek"));
map.put("isoWeekYear", singleArgumentAggregationMethodReference().forOperator("$isoWeekYear"));
// CONDITIONAL OPERATORS
map.put("cond", mapArgumentAggregationMethodReference().forOperator("$cond") //
.mappingParametersTo("if", "then", "else"));
map.put("ifNull", arrayArgumentAggregationMethodReference().forOperator("$ifNull"));
// GROUP OPERATORS
map.put("sum", arrayArgumentAggregationMethodReference().forOperator("$sum"));
map.put("avg", arrayArgumentAggregationMethodReference().forOperator("$avg"));
map.put("first", singleArgumentAggregationMethodReference().forOperator("$first"));
map.put("last", singleArgumentAggregationMethodReference().forOperator("$last"));
map.put("max", arrayArgumentAggregationMethodReference().forOperator("$max"));
map.put("min", arrayArgumentAggregationMethodReference().forOperator("$min"));
map.put("push", singleArgumentAggregationMethodReference().forOperator("$push"));
map.put("addToSet", singleArgumentAggregationMethodReference().forOperator("$addToSet"));
map.put("stdDevPop", arrayArgumentAggregationMethodReference().forOperator("$stdDevPop"));
map.put("stdDevSamp", arrayArgumentAggregationMethodReference().forOperator("$stdDevSamp"));
// TYPE OPERATORS
map.put("type", singleArgumentAggregationMethodReference().forOperator("$type"));
FUNCTIONS = Collections.unmodifiableMap(map);
}
MethodReferenceNode(MethodReference reference, ExpressionState state) {
super(reference, state);
}
/**
* Returns the name of the method.
*
* @Deprecated since 1.10. Please use {@link #getMethodReference()}.
*/
@Deprecated
public String getMethodName() {
AggregationMethodReference methodReference = getMethodReference();
return methodReference != null ? methodReference.getMongoOperator() : null;
}
/**
* Return the {@link AggregationMethodReference}.
*
* @return can be {@literal null}.
* @since 1.10
*/
public AggregationMethodReference getMethodReference() {
String name = getName();
String methodName = name.substring(0, name.indexOf('('));
return FUNCTIONS.get(methodName);
}
/**
* @author Christoph Strobl
* @since 1.10
*/
public static final class AggregationMethodReference {
private final String mongoOperator;
private final ArgumentType argumentType;
private final String[] argumentMap;
/**
* Creates new {@link AggregationMethodReference}.
*
* @param mongoOperator can be {@literal null}.
* @param argumentType can be {@literal null}.
* @param argumentMap can be {@literal null}.
*/
private AggregationMethodReference(String mongoOperator, ArgumentType argumentType, String[] argumentMap) {
this.mongoOperator = mongoOperator;
this.argumentType = argumentType;
this.argumentMap = argumentMap;
}
/**
* Get the MongoDB specific operator.
*
* @return can be {@literal null}.
*/
public String getMongoOperator() {
return this.mongoOperator;
}
/**
* Get the {@link ArgumentType} used by the MongoDB.
*
* @return never {@literal null}.
*/
public ArgumentType getArgumentType() {
return this.argumentType;
}
/**
* Get the property names in order order of appearance in resulting operation.
*
* @return never {@literal null}.
*/
public String[] getArgumentMap() {
return argumentMap != null ? argumentMap : new String[] {};
}
/**
* Create a new {@link AggregationMethodReference} for a {@link ArgumentType#SINGLE} argument.
*
* @return never {@literal null}.
*/
static AggregationMethodReference singleArgumentAggregationMethodReference() {
return new AggregationMethodReference(null, ArgumentType.SINGLE, null);
}
/**
* Create a new {@link AggregationMethodReference} for an {@link ArgumentType#ARRAY} argument.
*
* @return never {@literal null}.
*/
static AggregationMethodReference arrayArgumentAggregationMethodReference() {
return new AggregationMethodReference(null, ArgumentType.ARRAY, null);
}
/**
* Create a new {@link AggregationMethodReference} for a {@link ArgumentType#MAP} argument.
*
* @return never {@literal null}.
*/
static AggregationMethodReference mapArgumentAggregationMethodReference() {
return new AggregationMethodReference(null, ArgumentType.MAP, null);
}
/**
* Create a new {@link AggregationMethodReference} for a given {@literal aggregationExpressionOperator} reusing
* previously set arguments.
*
* @param aggregationExpressionOperator should not be {@literal null}.
* @return never {@literal null}.
*/
AggregationMethodReference forOperator(String aggregationExpressionOperator) {
return new AggregationMethodReference(aggregationExpressionOperator, argumentType, argumentMap);
}
/**
* Create a new {@link AggregationMethodReference} for mapping actual parameters within the AST to the given
* {@literal aggregationExpressionProperties} reusing previously set arguments. <br />
* <strong>NOTE:</strong> Can only be applied to {@link AggregationMethodReference} of type
* {@link ArgumentType#MAP}.
*
* @param aggregationExpressionProperties should not be {@literal null}.
* @return never {@literal null}.
* @throws IllegalArgumentException
*/
AggregationMethodReference mappingParametersTo(String... aggregationExpressionProperties) {
Assert.isTrue(ObjectUtils.nullSafeEquals(argumentType, ArgumentType.MAP),
"Parameter mapping can only be applied to AggregationMethodReference with MAPPED ArgumentType.");
return new AggregationMethodReference(mongoOperator, argumentType, aggregationExpressionProperties);
}
/**
* The actual argument type to use when mapping parameters to MongoDB specific format.
*
* @author Christoph Strobl
* @since 1.10
*/
public enum ArgumentType {
SINGLE, ARRAY, MAP
}
}
}