Skip to content

Commit 693f402

Browse files
Merge pull request BroadleafCommerce#325 from BroadleafCommerce/qa-1508-rule-parsing
Rule-based evaluation does not work with various attribute maps
2 parents 6d0bcb2 + ee06b4d commit 693f402

File tree

4 files changed

+77
-70
lines changed

4 files changed

+77
-70
lines changed

common/src/main/java/org/broadleafcommerce/common/rule/AbstractRuleProcessor.java

+1-21
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424
import org.broadleafcommerce.common.util.EfficientLRUMap;
25-
import org.mvel2.CompileException;
2625
import org.mvel2.MVEL;
2726
import org.mvel2.ParserContext;
2827

@@ -61,26 +60,7 @@ protected ParserContext getParserContext() {
6160
* @return the result of the expression
6261
*/
6362
protected Boolean executeExpression(String expression, Map<String, Object> vars) {
64-
Serializable exp = (Serializable) expressionCache.get(expression);
65-
vars.put("MVEL", MVEL.class);
66-
67-
if (exp == null) {
68-
try {
69-
exp = MVEL.compileExpression(expression, getParserContext());
70-
} catch (CompileException ce) {
71-
LOG.warn("Compile exception processing phrase: " + expression,ce);
72-
return Boolean.FALSE;
73-
}
74-
expressionCache.put(expression, exp);
75-
}
76-
77-
try {
78-
return (Boolean) MVEL.executeExpression(exp, vars);
79-
} catch (Exception e) {
80-
LOG.error(e);
81-
}
82-
83-
return false;
63+
return MvelHelper.evaluateRule(expression, vars, expressionCache);
8464
}
8565

8666
/**

common/src/main/java/org/broadleafcommerce/common/rule/MvelHelper.java

+66-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.broadleafcommerce.common.rule;
2121

22+
import org.apache.commons.collections4.MapUtils;
2223
import org.apache.commons.logging.Log;
2324
import org.apache.commons.logging.LogFactory;
2425
import org.broadleafcommerce.common.RequestDTO;
@@ -36,6 +37,7 @@
3637
import java.text.ParseException;
3738
import java.util.HashMap;
3839
import java.util.Map;
40+
import java.util.Map.Entry;
3941

4042
import javax.servlet.http.HttpServletRequest;
4143

@@ -62,7 +64,7 @@ public class MvelHelper {
6264

6365
// The following attribute is set in BroadleafProcessURLFilter
6466
public static final String REQUEST_DTO = "blRequestDTO";
65-
67+
6668
/**
6769
* Converts a field to the specified type. Useful when
6870
* @param type
@@ -122,16 +124,38 @@ public static boolean evaluateRule(String rule, Map<String, Object> ruleParamete
122124
*/
123125
public static boolean evaluateRule(String rule, Map<String, Object> ruleParameters,
124126
Map<String, Serializable> expressionCache) {
127+
return evaluateRule(rule, ruleParameters, expressionCache, null);
128+
}
129+
130+
/**
131+
* @param rule
132+
* @param ruleParameters
133+
* @param expressionCache
134+
* @param additionalContextImports additional imports to give to the {@link ParserContext} besides "MVEL" ({@link MVEL} and
135+
* "MvelHelper" ({@link MvelHelper}) since they are automatically added
136+
* @return
137+
*/
138+
public static boolean evaluateRule(String rule, Map<String, Object> ruleParameters,
139+
Map<String, Serializable> expressionCache, Map<String, Class<?>> additionalContextImports) {
140+
125141
// Null or empty is a match
126142
if (rule == null || "".equals(rule)) {
127143
return true;
128144
} else {
129145
// MVEL expression compiling can be expensive so let's cache the expression
130-
Serializable exp = (Serializable) expressionCache.get(rule);
146+
Serializable exp = expressionCache.get(rule);
131147
if (exp == null) {
132148
ParserContext context = new ParserContext();
133149
context.addImport("MVEL", MVEL.class);
134150
context.addImport("MvelHelper", MvelHelper.class);
151+
if (MapUtils.isNotEmpty(additionalContextImports)) {
152+
for (Entry<String, Class<?>> entry : additionalContextImports.entrySet()) {
153+
context.addImport(entry.getKey(), entry.getValue());
154+
}
155+
}
156+
157+
rule = modifyExpression(rule, ruleParameters, context);
158+
135159
exp = MVEL.compileExpression(rule, context);
136160
expressionCache.put(rule, exp);
137161
}
@@ -161,6 +185,45 @@ public static boolean evaluateRule(String rule, Map<String, Object> ruleParamete
161185
}
162186
}
163187
}
188+
189+
/**
190+
* <p>
191+
* Provides a hook point to modify the final expression before it's built. By default, this looks for attribute
192+
* maps and replaces them such that it does string comparison.
193+
*
194+
* <p>
195+
* For example, given an expression like getProductAttributes()['somekey'] == 'someval', getProductAttributes()['somekey']
196+
* actually returns a ProductAttribute object, not a String, so the comparison is wrong. Instead, we actually want
197+
* to do this: getProductAttributes()['somekey'].?value == 'someval'. This function performs that replacement
198+
*
199+
* @param rule the rule to replace
200+
* @return a modified version of <b>rule</b>
201+
* @see {@link #getRuleAttributeMaps()}
202+
*/
203+
protected static String modifyExpression(String rule, Map<String, Object> ruleParameters, ParserContext context) {
204+
String modifiedExpression = rule;
205+
for (String attributeMap : getRuleAttributeMaps()) {
206+
modifiedExpression = modifiedExpression.replaceAll(attributeMap + "\\(\\)\\[(.*)\\](?!\\.\\?value)", attributeMap + "()[$1].?value");
207+
}
208+
return modifiedExpression;
209+
}
210+
211+
/**
212+
* Returns an array of attribute map field names that we need to do replacements for in
213+
* {@link #modifyExpression(String, Map, ParserContext)}
214+
*/
215+
protected static String[] getRuleAttributeMaps() {
216+
// intentionally left out pricing context getPricingContextAttributes because that's a Map<String, String>
217+
return new String[]{ "getProductAttributes",
218+
"getCategoryAttributesMap",
219+
"getSkuAttributes",
220+
"getOrderItemAttributes",
221+
"getCustomerAttributes",
222+
// Map<String, PageAttribute>
223+
"getAdditionalAttributes",
224+
// Map<String, AdminUserAttribute>
225+
"getAdditionalFields"};
226+
}
164227

165228
/**
166229
* When true, LOG.info statement will be suppressed. Should only be set from within MvelHelperTest.
@@ -185,7 +248,7 @@ public static Map<String, Object> buildMvelParameters() {
185248
if (brc != null && brc.getRequest() != null) {
186249
TimeDTO timeDto = new TimeDTO(SystemTime.asCalendar());
187250
HttpServletRequest request = brc.getRequest();
188-
RequestDTO requestDto = (RequestDTO) brc.getRequestDTO();
251+
RequestDTO requestDto = brc.getRequestDTO();
189252
mvelParameters.put("time", timeDto);
190253
mvelParameters.put("request", requestDto);
191254

core/broadleaf-framework/src/main/java/org/broadleafcommerce/core/offer/service/processor/AbstractBaseProcessor.java

+5-31
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@
4343
import org.broadleafcommerce.core.order.service.type.FulfillmentType;
4444
import org.broadleafcommerce.profile.core.domain.Customer;
4545
import org.joda.time.LocalDateTime;
46-
import org.mvel2.MVEL;
47-
import org.mvel2.ParserContext;
4846

49-
import java.io.Serializable;
5047
import java.util.ArrayList;
5148
import java.util.Calendar;
5249
import java.util.Collection;
@@ -235,35 +232,12 @@ protected boolean couldOrderItemMeetOfferRequirement(OfferItemCriteria criteria,
235232
* @return a Boolean object containing the result of executing the MVEL expression
236233
*/
237234
public Boolean executeExpression(String expression, Map<String, Object> vars) {
238-
try {
239-
Serializable exp;
240-
synchronized (EXPRESSION_CACHE) {
241-
exp = (Serializable) EXPRESSION_CACHE.get(expression);
242-
if (exp == null) {
243-
ParserContext context = new ParserContext();
244-
context.addImport("OfferType", OfferType.class);
245-
context.addImport("FulfillmentType", FulfillmentType.class);
246-
context.addImport("MVEL", MVEL.class);
247-
context.addImport("MvelHelper", MvelHelper.class);
248-
// StringBuffer completeExpression = new StringBuffer(functions.toString());
249-
// completeExpression.append(" ").append(expression);
250-
exp = MVEL.compileExpression(expression, context);
251-
252-
EXPRESSION_CACHE.put(expression, exp);
253-
}
254-
}
255-
256-
Object test = MVEL.executeExpression(exp, vars);
257-
258-
return (Boolean) test;
259-
} catch (Exception e) {
260-
//Unable to execute the MVEL expression for some reason
261-
//Return false, but notify about the bad expression through logs
262-
LOG.warn("Unable to parse and/or execute an mvel expression. Reporting to the logs and returning false " +
263-
"for the match expression:" + expression, e);
264-
return false;
235+
synchronized (EXPRESSION_CACHE) {
236+
Map<String, Class<?>> contextImports = new HashMap<>();
237+
contextImports.put("OfferType", OfferType.class);
238+
contextImports.put("FulfillmentType", FulfillmentType.class);
239+
return MvelHelper.evaluateRule(expression, vars, EXPRESSION_CACHE, contextImports);
265240
}
266-
267241
}
268242

269243
/**

core/broadleaf-framework/src/main/java/org/broadleafcommerce/core/pricing/service/workflow/ConsolidateFulfillmentFeesActivity.java

+5-15
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@
3333
import org.broadleafcommerce.core.order.service.FulfillmentGroupService;
3434
import org.broadleafcommerce.core.workflow.BaseActivity;
3535
import org.broadleafcommerce.core.workflow.ProcessContext;
36-
import org.mvel2.MVEL;
37-
import org.mvel2.ParserContext;
3836

39-
import java.io.Serializable;
4037
import java.util.HashMap;
4138
import java.util.List;
4239
import java.util.Map;
@@ -106,19 +103,12 @@ protected boolean shouldApplyFeeToFulfillmentGroup(SkuFee fee, FulfillmentGroup
106103
boolean appliesToFulfillmentGroup = true;
107104
String feeExpression = fee.getExpression();
108105

109-
if (!StringUtils.isEmpty(feeExpression)) {
110-
Serializable exp = (Serializable) EXPRESSION_CACHE.get(feeExpression);
111-
if (exp == null) {
112-
ParserContext mvelContext = new ParserContext();
113-
mvelContext.addImport("MVEL", MVEL.class);
114-
mvelContext.addImport("MvelHelper", MvelHelper.class);
115-
exp = MVEL.compileExpression(feeExpression, mvelContext);
116-
117-
EXPRESSION_CACHE.put(feeExpression, exp);
106+
if (StringUtils.isNotEmpty(feeExpression)) {
107+
synchronized (EXPRESSION_CACHE) {
108+
HashMap<String, Object> vars = new HashMap<String, Object>();
109+
vars.put("fulfillmentGroup", fulfillmentGroup);
110+
MvelHelper.evaluateRule(feeExpression, vars, EXPRESSION_CACHE);
118111
}
119-
HashMap<String, Object> vars = new HashMap<String, Object>();
120-
vars.put("fulfillmentGroup", fulfillmentGroup);
121-
return (Boolean)MVEL.executeExpression(feeExpression, vars);
122112
}
123113

124114
return appliesToFulfillmentGroup;

0 commit comments

Comments
 (0)