/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* 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.
* #L%
*/
package org.broadleafcommerce.openadmin.web.rulebuilder;
import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.common.presentation.client.SupportedFieldType;
import org.broadleafcommerce.common.util.FormatUtil;
import org.broadleafcommerce.openadmin.server.service.persistence.module.FieldManager;
import org.broadleafcommerce.openadmin.web.rulebuilder.dto.DataDTO;
import org.broadleafcommerce.openadmin.web.rulebuilder.dto.ExpressionDTO;
import org.broadleafcommerce.openadmin.web.rulebuilder.service.RuleBuilderFieldService;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Utility class to convert a DataDTO/ExpressionDTO into an MVEL string
*
* @author Elbert Bautista (elbertbautista)
*/
public class DataDTOToMVELTranslator {
public String createMVEL(String entityKey, DataDTO dataDTO, RuleBuilderFieldService fieldService)
throws MVELTranslationException {
StringBuffer sb = new StringBuffer();
buildMVEL(dataDTO, sb, entityKey, null, fieldService);
String response = sb.toString().trim();
if (response.length() == 0) {
response = null;
}
return response;
}
protected void buildMVEL(DataDTO dataDTO, StringBuffer sb, String entityKey, String groupOperator,
RuleBuilderFieldService fieldService) throws MVELTranslationException {
BLCOperator operator = null;
if (dataDTO instanceof ExpressionDTO) {
operator = BLCOperator.valueOf(((ExpressionDTO) dataDTO).getOperator());
} else {
operator = BLCOperator.valueOf(dataDTO.getGroupOperator());
}
ArrayList<DataDTO> groups = dataDTO.getGroups();
if (sb.length() != 0 && sb.charAt(sb.length() - 1) != '(' && groupOperator != null) {
BLCOperator groupOp = BLCOperator.valueOf(groupOperator);
switch(groupOp) {
default:
sb.append("&&");
break;
case OR:
sb.append("||");
}
}
if (dataDTO instanceof ExpressionDTO) {
buildExpression((ExpressionDTO)dataDTO, sb, entityKey, operator, fieldService);
} else {
boolean includeTopLevelParenthesis = false;
if (sb.length() != 0 || BLCOperator.NOT.equals(operator) || (sb.length() == 0 && groupOperator != null)) {
includeTopLevelParenthesis = true;
}
if (BLCOperator.NOT.equals(operator)) {
sb.append("!");
}
if (includeTopLevelParenthesis) sb.append("(");
for (DataDTO dto : groups) {
buildMVEL(dto, sb, entityKey, dataDTO.getGroupOperator(), fieldService);
}
if (includeTopLevelParenthesis) sb.append(")");
}
}
protected void buildExpression(ExpressionDTO expressionDTO, StringBuffer sb, String entityKey,
BLCOperator operator, RuleBuilderFieldService fieldService)
throws MVELTranslationException {
String field = expressionDTO.getName();
SupportedFieldType type = fieldService.getSupportedFieldType(field);
SupportedFieldType secondaryType = fieldService.getSecondaryFieldType(field);
Object[] value;
if (type == null) {
throw new MVELTranslationException(MVELTranslationException.SPECIFIED_FIELD_NOT_FOUND, "The DataDTO is not compatible with the RuleBuilderFieldService " +
"associated with the current rules builder. Unable to find the field " +
"specified: ("+field+")");
}
if (
SupportedFieldType.DATE.toString().equals(type.toString()) &&
!BLCOperator.CONTAINS_FIELD.equals(operator) &&
!BLCOperator.ENDS_WITH_FIELD.equals(operator) &&
!BLCOperator.EQUALS_FIELD.equals(operator) &&
!BLCOperator.GREATER_OR_EQUAL_FIELD.equals(operator) &&
!BLCOperator.GREATER_THAN_FIELD.equals(operator) &&
!BLCOperator.LESS_OR_EQUAL_FIELD.equals(operator) &&
!BLCOperator.LESS_THAN_FIELD.equals(operator) &&
!BLCOperator.NOT_EQUAL_FIELD.equals(operator) &&
!BLCOperator.STARTS_WITH_FIELD.equals(operator) &&
!BLCOperator.BETWEEN.equals(operator) &&
!BLCOperator.BETWEEN_INCLUSIVE.equals(operator)
) {
value = extractDate(expressionDTO, operator, "value");
} else {
value = extractBasicValues(expressionDTO.getValue());
}
switch(operator) {
case CONTAINS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".contains",
true, false, false, false, false);
break;
}
case CONTAINS_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".contains",
true, true, false, false, false);
break;
}
case ENDS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".endsWith",
true, false, false, false, false);
break;
}
case ENDS_WITH_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".endsWith",
true, true, false, false, false);
break;
}
case EQUALS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "==", false, false, false, false, false);
break;
}
case EQUALS_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "==", false, true, false, false, false);
break;
}
case GREATER_OR_EQUAL: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ">=", false, false, false, false, false);
break;
}
case GREATER_OR_EQUAL_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ">=", false, true, false, false, false);
break;
}
case GREATER_THAN: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ">", false, false, false, false, false);
break;
}
case GREATER_THAN_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ">", false, true, false, false, false);
break;
}
case ICONTAINS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".contains",
true, false, true, false, false);
break;
}
case IENDS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".endsWith",
true, false, true, false, false);
break;
}
case IEQUALS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "==", false, false, true, false, false);
break;
}
case INOT_CONTAINS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".contains",
true, false, true, true, false);
break;
}
case INOT_ENDS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".endsWith",
true, false, true, true, false);
break;
}
case INOT_EQUAL: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "!=", false, false, true, false, false);
break;
}
case INOT_STARTS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".startsWith",
true, false, true, true, false);
break;
}
case IS_NULL: {
buildExpression(sb, entityKey, field, new Object[]{"null"}, type, secondaryType, "==",
false, false, false, false, true);
break;
}
case ISTARTS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".startsWith",
true, false, true, false, false);
break;
}
case LESS_OR_EQUAL: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "<=", false, false, false, false, false);
break;
}
case LESS_OR_EQUAL_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "<=", false, true, false, false, false);
break;
}
case LESS_THAN: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "<", false, false, false, false, false);
break;
}
case LESS_THAN_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "<",
false, true, false, false, false);
break;
}
case NOT_CONTAINS: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".contains",
true, false, false, true, false);
break;
}
case NOT_ENDS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".endsWith",
true, false, false, true, false);
break;
}
case NOT_EQUAL: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "!=", false, false, false, false, false);
break;
}
case NOT_EQUAL_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, "!=",
false, true, false, false, false);
break;
}
case NOT_NULL: {
buildExpression(sb, entityKey, field, new Object[]{"null"}, type, secondaryType, "!=",
false, false, false, false, true);
break;
}
case NOT_STARTS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".startsWith",
true, false, false, true, false);
break;
}
case STARTS_WITH: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".startsWith",
true, false, false, false, false);
break;
}
case STARTS_WITH_FIELD: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".startsWith",
true, true, false, false, false);
break;
}
case COUNT_GREATER_THAN: {
buildExpression(sb, entityKey, field, value, type, secondaryType, ".size()>", false, false, false, false, true);
break;
}
case COUNT_GREATER_OR_EQUAL:{
buildExpression(sb, entityKey, field, value, type, secondaryType, ".size()>=", false, false, false, false, true);
break;
}
case COUNT_LESS_THAN:{
buildExpression(sb, entityKey, field, value, type, secondaryType, ".size()<", false, false, false, false, true);
break;
}
case COUNT_LESS_OR_EQUAL:{
buildExpression(sb, entityKey, field, value, type, secondaryType, ".size()<=", false, false, false, false, true);
break;
}
case COUNT_EQUALS:{
buildExpression(sb, entityKey, field, value, type, secondaryType, ".size()==", false, false, false, false, true);
break;
}
case BETWEEN: {
if (SupportedFieldType.DATE.toString().equals(type.toString())) {
sb.append("(");
buildExpression(sb, entityKey, field, extractDate(expressionDTO, BLCOperator.GREATER_THAN, "start"),
type, secondaryType, ">", false, false, false, false, false);
sb.append("&&");
buildExpression(sb, entityKey, field, extractDate(expressionDTO, BLCOperator.LESS_THAN, "end"),
type, secondaryType, "<", false, false, false, false, false);
sb.append(")");
} else {
sb.append("(");
buildExpression(sb, entityKey, field, new Object[]{expressionDTO.getStart()}, type, secondaryType, ">",
false, false, false, false, false);
sb.append("&&");
buildExpression(sb, entityKey, field, new Object[]{expressionDTO.getEnd()}, type, secondaryType, "<",
false, false, false, false, false);
sb.append(")");
}
break;
}
case BETWEEN_INCLUSIVE: {
if (
SupportedFieldType.DATE.toString().equals(type.toString())
) {
sb.append("(");
buildExpression(sb, entityKey, field,
extractDate(expressionDTO, BLCOperator.GREATER_OR_EQUAL, "start"), type,
secondaryType, ">=", false, false, false, false, false);
sb.append("&&");
buildExpression(sb, entityKey, field, extractDate(expressionDTO, BLCOperator.LESS_OR_EQUAL, "end"),
type, secondaryType, "<=", false, false, false, false, false);
sb.append(")");
} else {
sb.append("(");
buildExpression(sb, entityKey, field, new Object[]{expressionDTO.getStart()}, type, secondaryType, ">=",
false, false, false, false, false);
sb.append("&&");
buildExpression(sb, entityKey, field, new Object[]{expressionDTO.getEnd()}, type, secondaryType, "<=",
false, false, false, false, false);
sb.append(")");
}
break;
}
}
}
@SuppressWarnings({ "rawtypes", "deprecation", "unchecked" })
protected Object[] extractDate(ExpressionDTO expressionDTO, BLCOperator operator, String key) {
String value;
if ("start".equals(key)) {
value = expressionDTO.getStart();
} else if ("end".equals(key)) {
value = expressionDTO.getEnd();
} else {
value = expressionDTO.getValue();
}
//TODO handle Date Time Format
// if (BLCOperator.GREATER_THAN.equals(operator) || BLCOperator.LESS_OR_EQUAL.equals(operator)) {
// ((Date) value).setHours(23);
// ((Date) value).setMinutes(59);
// } else {
// ((Date) value).setHours(0);
// ((Date) value).setMinutes(0);
// }
return new Object[]{value};
}
protected Object[] extractBasicValues(Object value) {
if (value == null) {
return null;
}
String stringValue = value.toString().trim();
Object[] response = new Object[]{};
if (isProjection(value)) {
List<String> temp = new ArrayList<String>();
int initial = 1;
//assume this is a multi-value phrase
boolean eof = false;
while (!eof) {
int end = stringValue.indexOf(",", initial);
if (end == -1) {
eof = true;
end = stringValue.length() - 1;
}
temp.add(stringValue.substring(initial, end));
initial = end + 1;
}
response = temp.toArray(response);
} else {
response = new Object[]{value};
}
return response;
}
public boolean isProjection(Object value) {
String stringValue = value.toString().trim();
return stringValue.startsWith("[") && stringValue.endsWith("]") && stringValue.indexOf(",") > 0;
}
protected void buildExpression(StringBuffer sb, String entityKey, String field, Object[] value,
SupportedFieldType type, SupportedFieldType secondaryType, String operator,
boolean includeParenthesis, boolean isFieldComparison, boolean ignoreCase,
boolean isNegation, boolean ignoreQuotes)
throws MVELTranslationException {
if (operator.equals("==") && !isFieldComparison && value.length > 1) {
sb.append("(");
sb.append("[");
sb.append(formatValue(field, entityKey, type, secondaryType, value, isFieldComparison,
ignoreCase, ignoreQuotes));
sb.append("] contains ");
sb.append(formatField(entityKey, type, field, ignoreCase, isNegation));
if ((type.equals(SupportedFieldType.ID) && secondaryType != null &&
secondaryType.equals(SupportedFieldType.INTEGER)) || type.equals(SupportedFieldType.INTEGER)) {
sb.append(".intValue()");
}
sb.append(")");
} else {
sb.append(formatField(entityKey, type, field, ignoreCase, isNegation));
sb.append(operator);
if (includeParenthesis) {
sb.append("(");
}
sb.append(formatValue(field, entityKey, type, secondaryType, value,
isFieldComparison, ignoreCase, ignoreQuotes));
if (includeParenthesis) {
sb.append(")");
}
}
}
protected String buildFieldName(String entityKey, String fieldName) {
String response = entityKey + "." + fieldName;
response = response.replaceAll("\\.", ".?");
return response;
}
protected String formatField(String entityKey, SupportedFieldType type, String field,
boolean ignoreCase, boolean isNegation) {
StringBuilder response = new StringBuilder();
if (isNegation) {
response.append("!");
}
String convertedField = field;
boolean isMapField = false;
if (convertedField.contains(FieldManager.MAPFIELDSEPARATOR)) {
//This must be a map field, convert the field name to syntax MVEL can understand for map access
convertedField = convertedField.substring(0, convertedField.indexOf(FieldManager.MAPFIELDSEPARATOR))
+ "[\"" + convertedField.substring(convertedField.indexOf(FieldManager.MAPFIELDSEPARATOR) +
FieldManager.MAPFIELDSEPARATOR.length(), convertedField.length()) + "\"]";
isMapField = true;
}
if (isMapField) {
switch(type) {
case BOOLEAN:
response.append("MvelHelper.convertField(\"BOOLEAN\",");
response.append(buildFieldName(entityKey, convertedField));
response.append(")");
break;
case INTEGER:
response.append("MvelHelper.convertField(\"INTEGER\",");
response.append(buildFieldName(entityKey, convertedField));
response.append(")");
break;
case DECIMAL:
case MONEY:
response.append("MvelHelper.convertField(\"DECIMAL\",");
response.append(buildFieldName(entityKey, convertedField));
response.append(")");
break;
case DATE:
response.append("MvelHelper.convertField(\"DATE\",");
response.append(buildFieldName(entityKey, convertedField));
response.append(")");
break;
case STRING:
if (ignoreCase) {
response.append("MvelHelper.toUpperCase(");
}
response.append(buildFieldName(entityKey, convertedField));
if (ignoreCase) {
response.append(")");
}
break;
case STRING_LIST:
response.append(buildFieldName(entityKey, convertedField));
break;
default:
throw new UnsupportedOperationException(type.toString() + " is not supported for map fields in the rule builder.");
}
} else {
switch(type) {
case BROADLEAF_ENUMERATION:
if (isMapField) {
throw new UnsupportedOperationException("Enumerations are not supported for map fields in the rule builder.");
} else {
response.append(buildFieldName(entityKey, convertedField));
response.append(".getType()");
}
break;
case MONEY:
response.append(buildFieldName(entityKey, convertedField));
response.append(".getAmount()");
break;
case STRING:
if (ignoreCase) {
response.append("MvelHelper.toUpperCase(");
}
response.append(buildFieldName(entityKey, convertedField));
if (ignoreCase) {
response.append(")");
}
break;
default:
response.append(buildFieldName(entityKey, convertedField));
break;
}
}
return response.toString();
}
protected String formatValue(String fieldName, String entityKey, SupportedFieldType type,
SupportedFieldType secondaryType, Object[] value,
boolean isFieldComparison, boolean ignoreCase,
boolean ignoreQuotes) throws MVELTranslationException {
StringBuilder response = new StringBuilder();
if (isFieldComparison) {
switch(type) {
case MONEY:
response.append(entityKey);
response.append(".");
response.append(value[0]);
response.append(".getAmount()");
break;
case STRING:
if (ignoreCase) {
response.append("MvelHelper.toUpperCase(");
}
response.append(entityKey);
response.append(".");
response.append(value[0]);
if (ignoreCase) {
response.append(")");
}
break;
default:
response.append(entityKey);
response.append(".");
response.append(value[0]);
break;
}
} else {
for (int j=0;j<value.length;j++){
if (StringUtils.isBlank(value[j].toString())) {
break;
}
switch(type) {
case BOOLEAN:
response.append(value[j]);
break;
case DECIMAL:
try {
Double.parseDouble(value[j].toString());
} catch (Exception e) {
throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_DECIMAL_VALUE, "Cannot format value for the field ("
+ fieldName + ") based on field type. The type of field is Decimal, " +
"and you entered: (" + value[j] +")");
}
response.append(value[j]);
break;
case ID:
if (secondaryType != null && secondaryType.toString().equals(
SupportedFieldType.STRING.toString())) {
if (ignoreCase) {
response.append("MvelHelper.toUpperCase(");
}
if (!ignoreQuotes) {
response.append("\"");
}
response.append(value[j]);
if (!ignoreQuotes) {
response.append("\"");
}
if (ignoreCase) {
response.append(")");
}
} else {
try {
Integer.parseInt(value[j].toString());
} catch (Exception e) {
throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_INTEGER_VALUE, "Cannot format value for the field (" +
fieldName + ") based on field type. The type of field is Integer, " +
"and you entered: (" + value[j] +")");
}
response.append(value[j]);
}
break;
case INTEGER:
try {
Integer.parseInt(value[j].toString());
} catch (Exception e) {
throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_INTEGER_VALUE, "Cannot format value for the field (" +
fieldName + ") based on field type. The type of field is Integer, " +
"and you entered: (" + value[j] +")");
}
response.append(value[j]);
break;
case MONEY:
try {
Double.parseDouble(value[j].toString());
} catch (Exception e) {
throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_DECIMAL_VALUE, "Cannot format value for the field (" +
fieldName + ") based on field type. The type of field is Money, " +
"and you entered: (" + value[j] +")");
}
response.append(value[j]);
break;
case DATE:
//convert the date to our standard date/time format
Date temp = null;
try {
temp = RuleBuilderFormatUtil.parseDate(String.valueOf(value[j]));
} catch (ParseException e) {
throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_DATE_VALUE, "Cannot format value for the field (" +
fieldName + ") based on field type. The type of field is Date, " +
"and you entered: (" + value[j] +"). Dates must be in the format MM/dd/yyyy HH:mm.");
}
String convertedDate = FormatUtil.getTimeZoneFormat().format(temp);
response.append("MvelHelper.convertField(\"DATE\",\"");
response.append(convertedDate);
response.append("\")");
break;
default:
if (ignoreCase) {
response.append("MvelHelper.toUpperCase(");
}
if (!ignoreQuotes) {
response.append("\"");
}
response.append(value[j]);
if (!ignoreQuotes) {
response.append("\"");
}
if (ignoreCase) {
response.append(")");
}
break;
}
if (j < value.length - 1) {
response.append(",");
}
}
}
return response.toString();
}
}