/* * #%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.statement; import org.broadleafcommerce.openadmin.server.service.persistence.module.FieldManager; import org.broadleafcommerce.openadmin.web.rulebuilder.BLCOperator; import org.broadleafcommerce.openadmin.web.rulebuilder.MVELTranslationException; import org.broadleafcommerce.openadmin.web.rulebuilder.RuleBuilderFormatUtil; import java.text.ParseException; /** * @author jfischer * @author Elbert Bautista (elbertbautista) */ public class PhraseTranslator { private static final String[] SPECIALCASES = { ".startsWith", ".endsWith", ".contains" }; private static final String[] STANDARDOPERATORS = { ".size()>", ".size()>=", ".size()<", ".size()<=", ".size()==", "==", "!=", "<=", "<", ">=", ">" }; public Expression createExpression(String phrase) throws MVELTranslationException { String[] components = extractComponents(phrase); String field = components[0]; String operator = components[1]; String value = components[2]; boolean isNegation = false; if (field.startsWith("!")) { isNegation = true; } boolean isIgnoreCase = false; //remove null check syntax field = field.replaceAll("\\.\\?", "."); //keep for backwards compatibility with legacy generated MVEL String legacyCaseInsensitivityKey = "MVEL.eval(\"toUpperCase()\","; String newCaseInsensitivityKey = "MvelHelper.toUpperCase("; String caseInsensitivityKey; if (field.contains(legacyCaseInsensitivityKey) || value.contains(legacyCaseInsensitivityKey)) { caseInsensitivityKey = legacyCaseInsensitivityKey; } else { caseInsensitivityKey = newCaseInsensitivityKey; } if (field.startsWith(caseInsensitivityKey)) { isIgnoreCase = true; field = field.substring(caseInsensitivityKey.length(), field.length()-1); } //check for NOT operator if (field.startsWith("!" + caseInsensitivityKey)) { isIgnoreCase = true; field = field.substring(("!" + caseInsensitivityKey).length(), field.length()-1); } while(value.contains(caseInsensitivityKey)) { value = value.substring(0, value.indexOf(caseInsensitivityKey)) + value.substring(value.indexOf(caseInsensitivityKey) + caseInsensitivityKey.length(), value.length()); value = value.substring(0, value.indexOf(")")) + value.substring(value.indexOf(")")+1, value.length()); } if (value.startsWith("[") && value.endsWith("]")) { value = value.substring(1, value.length() - 1); String[] temps = value.split(","); for (int j = 0;j<temps.length;j++) { if (temps[j].startsWith("\"") && temps[j].endsWith("\"")) { temps[j] = temps[j].substring(1, temps[j].length()-1); } } StringBuffer sb = new StringBuffer(); sb.append("["); for (int j = 0;j<temps.length;j++) { sb.append(temps[j]); if (j < temps.length - 1) { sb.append(","); } } sb.append("]"); value = sb.toString(); } //keep for backwards compatibility with legacy generated MVEL String legacyDateFormatKey = "java.text.DateFormat.getDateTimeInstance(3,3).parse("; String newDateFormatKey = "MvelHelper.convertField(\"DATE\",\""; String dateFormatKey; if (value.contains(legacyDateFormatKey)) { dateFormatKey = legacyDateFormatKey; } else { dateFormatKey = newDateFormatKey; } if (value.startsWith(dateFormatKey)) { value = value.substring(dateFormatKey.length(), value.length()-1); //convert the date into admin display format try { if (value.startsWith("\"")) { value = value.substring(1, value.length()); } if (value.endsWith("\"")) { value = value.substring(0, value.length()-1); } value = RuleBuilderFormatUtil.formatDate(RuleBuilderFormatUtil.parseDate(value)); } catch (ParseException e) { throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_DATE_VALUE, "Unable to convert " + "the persisted date value(" + value + ") to the admin display format."); } } int entityKeyIndex = field.indexOf("."); if (entityKeyIndex < 0) { throw new MVELTranslationException(MVELTranslationException.NO_FIELD_FOUND_IN_RULE, "Could not identify a " + "valid property field value in the expression: ("+phrase+")"); } if (value.startsWith(caseInsensitivityKey)) { value = value.substring(caseInsensitivityKey.length(), value.length()-1); } String entityKey = field.substring(0, entityKeyIndex); boolean isFieldComparison = false; if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length()-1); } else if (value.startsWith(entityKey + ".")){ isFieldComparison = true; value = value.substring(entityKey.length() + 1, value.length()); } field = field.substring(entityKeyIndex + 1, field.length()); // If this is a Money field, then DataDTOToMVELTranslator.formatValue() will append .getAmount() onto the end // of the field name. We need to remove that as it should not be considered part of the field name, but we still // want to support other method invocations in MVEL expressions (like for getProductAttributes()) String moneyAmountMethod = ".getAmount()"; int amountMethodPos = field.lastIndexOf(moneyAmountMethod); if (amountMethodPos >= 0) { field = field.substring(0, amountMethodPos); } // Same as above, but for Enumeration types String typeMethod = ".getType()"; int typeMethodPos = field.lastIndexOf(typeMethod); if (typeMethodPos >= 0) { field = field.substring(0, typeMethodPos); } Expression expression = new Expression(); expression.setField(field); BLCOperator operatorId = getOperator(field, operator, value, isNegation, isFieldComparison, isIgnoreCase); expression.setOperator(operatorId); expression.setValue(value); return expression; } protected String[] extractComponents(String phrase) throws MVELTranslationException { String[] components = new String[]{}; for (String operator : STANDARDOPERATORS) { String[] temp; if (phrase.contains(operator)) { temp = new String[2]; temp[0] = phrase.substring(0, phrase.indexOf(operator)); temp[1] = phrase.substring(phrase.indexOf(operator) + operator.length(), phrase.length()); } else { temp = new String[1]; temp[0] = phrase; } if (temp.length == 2) { components = new String[3]; components[0] = temp[0]; components[1] = operator; components[2] = temp[1]; break; } components = temp; } if (components.length != 3) { //may be a special expression try { for (String key : SPECIALCASES) { if (components[0].indexOf(key) >= 0) { String[] temp = extractSpecialComponents(components, key); components = temp; break; } } } catch (Exception e) { //do nothing } if (components.length != 3) { //may be a projection try { String[] temp = extractProjection(components); components = temp; } catch (Exception e1) { //do nothing } if (components.length != 3) { throw new MVELTranslationException(MVELTranslationException.UNRECOGNIZABLE_RULE, "Could not parse the MVEL expression to a " + "compatible form for the rules builder (" + phrase + ")"); } } } if (components[0].matches(".*\\[\".*?\"\\].*")) { //this is using map access syntax - must be a map field components[0] = components[0].substring(0, components[0].lastIndexOf("[")) + FieldManager.MAPFIELDSEPARATOR + components[0].substring(components[0].lastIndexOf("[") + 2, components[0].lastIndexOf("]") - 1) + components[0].substring(components[0].lastIndexOf("]") + 1, components[0].length()); //strip any convertField usage if (components[0].startsWith("MvelHelper.convertField(")) { components[0] = components[0].substring(components[0].indexOf("(") + 1, components[0].length()-1); } } return components; } protected String[] extractProjection(String[] components) { String[] temp = new String[3]; int startsWithIndex = components[0].indexOf("contains"); temp[0] = components[0].substring(startsWithIndex+"contains".length()+1, components[0].length()).trim(); if (temp[0].endsWith(".intValue()")) { temp[0] = temp[0].substring(0, temp[0].indexOf(".intValue()")); } temp[1] = "=="; temp[2] = components[0].substring(components[0].indexOf("["), components[0].indexOf("]") + 1); return temp; } protected String[] extractSpecialComponents(String[] components, String key) { String[] temp = new String[3]; int startsWithIndex = components[0].indexOf(key); temp[0] = components[0].substring(0, startsWithIndex); temp[1] = key.substring(1, key.length()); temp[2] = components[0].substring(startsWithIndex + key.length() + 1, components[0].lastIndexOf(")")); return temp; } protected BLCOperator getOperator(String field, String operator, String value, boolean isNegation, boolean isFieldComparison, boolean isIgnoreCase) throws MVELTranslationException { if (operator.equals("==") && value.equals("null")) { return BLCOperator.IS_NULL; } else if (operator.equals("==") && isFieldComparison) { return BLCOperator.EQUALS_FIELD; } else if ( isIgnoreCase && operator.equals("==") ) { return BLCOperator.IEQUALS; } else if (operator.equals("==")) { return BLCOperator.EQUALS; } else if (operator.equals("!=") && value.equals("null")) { return BLCOperator.NOT_NULL; } else if (operator.equals("!=") && isFieldComparison) { return BLCOperator.NOT_EQUAL_FIELD; } else if ( isIgnoreCase && operator.equals("!=") ) { return BLCOperator.INOT_EQUAL; } else if (operator.equals("!=")) { return BLCOperator.NOT_EQUAL; } else if (operator.equals(">") && isFieldComparison) { return BLCOperator.GREATER_THAN_FIELD; } else if (operator.equals(">")) { return BLCOperator.GREATER_THAN; } else if (operator.equals("<") && isFieldComparison) { return BLCOperator.LESS_THAN_FIELD; } else if (operator.equals("<")) { return BLCOperator.LESS_THAN; } else if (operator.equals(">=") && isFieldComparison) { return BLCOperator.GREATER_OR_EQUAL_FIELD; } else if (operator.equals(">=")) { return BLCOperator.GREATER_OR_EQUAL; } else if (operator.equals("<=") && isFieldComparison) { return BLCOperator.LESS_OR_EQUAL_FIELD; } else if (operator.equals("<=")) { return BLCOperator.LESS_OR_EQUAL; } else if ( isIgnoreCase && operator.equals("contains") && isNegation ) { return BLCOperator.INOT_CONTAINS; } else if (operator.equals("contains") && isNegation) { return BLCOperator.NOT_CONTAINS; } else if ( isIgnoreCase && operator.equals("contains") ) { return BLCOperator.ICONTAINS; } else if (operator.equals("contains") && isFieldComparison) { return BLCOperator.CONTAINS_FIELD; } else if (operator.equals("contains")) { return BLCOperator.CONTAINS; } else if ( isIgnoreCase && operator.equals("startsWith") && isNegation ) { return BLCOperator.INOT_STARTS_WITH; } else if (operator.equals("startsWith") && isNegation) { return BLCOperator.NOT_STARTS_WITH; } else if ( isIgnoreCase && operator.equals("startsWith") ) { return BLCOperator.ISTARTS_WITH; } else if (operator.equals("startsWith") && isFieldComparison) { return BLCOperator.STARTS_WITH_FIELD; } else if (operator.equals("startsWith")) { return BLCOperator.STARTS_WITH; } else if ( isIgnoreCase && operator.equals("endsWith") && isNegation ) { return BLCOperator.INOT_ENDS_WITH; } else if (operator.equals("endsWith") && isNegation) { return BLCOperator.NOT_ENDS_WITH; } else if ( isIgnoreCase && operator.equals("endsWith") ) { return BLCOperator.IENDS_WITH; } else if (operator.equals("endsWith") && isFieldComparison) { return BLCOperator.ENDS_WITH_FIELD; } else if (operator.equals("endsWith")) { return BLCOperator.ENDS_WITH; } else if (operator.equals(".size()>")) { return BLCOperator.COUNT_GREATER_THAN; } else if (operator.equals(".size()>=")) { return BLCOperator.COUNT_GREATER_OR_EQUAL; } else if (operator.equals(".size()<")) { return BLCOperator.COUNT_LESS_THAN; } else if (operator.equals(".size()<=")) { return BLCOperator.COUNT_LESS_OR_EQUAL; } else if (operator.equals(".size()==")) { return BLCOperator.COUNT_EQUALS; } throw new MVELTranslationException(MVELTranslationException.OPERATOR_NOT_FOUND, "Unable to identify an operator compatible with the " + "rules builder: ("+(isNegation?"!":""+field+operator+value)+")"); } }