/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.ofbiz.base.util.string; import java.math.BigDecimal; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import javax.el.ArrayELResolver; import javax.el.BeanELResolver; import javax.el.CompositeELResolver; import javax.el.ELContext; import javax.el.ELResolver; import javax.el.ExpressionFactory; import javax.el.FunctionMapper; import javax.el.ListELResolver; import javax.el.MapELResolver; import javax.el.PropertyNotFoundException; import javax.el.PropertyNotWritableException; import javax.el.ResourceBundleELResolver; import javax.el.ValueExpression; import javax.el.VariableMapper; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.collections.LocalizedMap; /** Implements the Unified Expression Language (JSR-245). */ public final class UelUtil { protected static final String module = UelUtil.class.getName(); private static final String localizedMapLocaleKey = LocalizedMap.class.getName() + "_locale".replace(".", "_"); private static final ExpressionFactory exprFactory = JuelConnector.newExpressionFactory(); private static final ELResolver defaultResolver = new ExtendedCompositeResolver() { { add(new ExtendedMapResolver(false)); add(new ExtendedListResolver(false)); add(new ArrayELResolver(false)); add(new NodeELResolver()); // Below the most common but must be kept above BeanELResolver add(new ResourceBundleELResolver()); add(new BeanELResolver(false)); } }; private UelUtil () {} public static String getLocalizedMapLocaleKey() { return localizedMapLocaleKey; } /** Evaluates a Unified Expression Language expression and returns the result. * @param context Evaluation context (variables) * @param expression UEL expression * @return Result object */ public static Object evaluate(Map<String, ? extends Object> context, String expression) { return evaluate(context, expression, Object.class); } /** Evaluates a Unified Expression Language expression and returns the result. * @param context Evaluation context (variables) * @param expression UEL expression * @param expectedType The expected object Class to return * @return Result object */ public static Object evaluate(Map<String, ? extends Object> context, String expression, Class expectedType) { ELContext elContext = new ReadOnlyContext(context); ValueExpression ve = exprFactory.createValueExpression(elContext, expression, expectedType); return ve.getValue(elContext); } /** Evaluates a Unified Expression Language expression and sets the resulting object * to the specified value. * @param context Evaluation context (variables) * @param expression UEL expression * @param expectedType The expected object Class to set */ public static void setValue(Map<String, Object> context, String expression, Class expectedType, Object value) { if (Debug.verboseOn()) { Debug.logVerbose("UelUtil.setValue invoked, expression = " + expression + ", value = " + value, module); } ELContext elContext = new BasicContext(context); ValueExpression ve = exprFactory.createValueExpression(elContext, expression, expectedType); ve.setValue(elContext, value); } /** Evaluates a Unified Expression Language expression and sets the resulting object * to null. * @param context Evaluation context (variables) * @param expression UEL expression */ public static void removeValue(Map<String, Object> context, String expression) { if (Debug.verboseOn()) { Debug.logVerbose("UelUtil.removeValue invoked, expression = " + expression , module); } ELContext elContext = new BasicContext(context); ValueExpression ve = exprFactory.createValueExpression(elContext, expression, Object.class); ve.setValue(elContext, null); } private static class BasicContext extends ELContext { private final Map<String, Object> variables; private final VariableMapper variableMapper; private BasicContext(Map<String, Object> context) { this.variableMapper = new BasicVariableMapper(this); this.variables = context; } @Override public ELResolver getELResolver() { return defaultResolver; } @Override public FunctionMapper getFunctionMapper() { return UelFunctions.getFunctionMapper(); } @Override public VariableMapper getVariableMapper() { return this.variableMapper; } } private static class ReadOnlyContext extends ELContext { private final Map<String, ? extends Object> variables; private final VariableMapper variableMapper; private ReadOnlyContext(Map<String, ? extends Object> context) { this.variableMapper = new ReadOnlyVariableMapper(this); this.variables = UtilGenerics.cast(context); } @Override public ELResolver getELResolver() { return defaultResolver; } @Override public FunctionMapper getFunctionMapper() { return UelFunctions.getFunctionMapper(); } @Override public VariableMapper getVariableMapper() { return this.variableMapper; } private static class ReadOnlyVariableMapper extends VariableMapper { private final ReadOnlyContext elContext; private ReadOnlyVariableMapper(ReadOnlyContext elContext) { this.elContext = elContext; } @Override public ValueExpression resolveVariable(String variable) { Object obj = UelUtil.resolveVariable(variable, this.elContext.variables, null); if (obj != null) { return new ReadOnlyExpression(obj); } return null; } @Override public ValueExpression setVariable(String variable, ValueExpression expression) { throw new PropertyNotWritableException(); } } } private static class BasicVariableMapper extends VariableMapper { private final BasicContext elContext; private BasicVariableMapper(BasicContext elContext) { this.elContext = elContext; } /** * Returns a BasicValueExpression containing the value of the named variable. * Resolves against LocalizedMap if available. * @param variable the variable's name * @return a BasicValueExpression containing the variable's value or null if the variable is unknown */ @Override public ValueExpression resolveVariable(String variable) { Object obj = UelUtil.resolveVariable(variable, this.elContext.variables, null); if (obj != null) { return new BasicValueExpression(variable, this.elContext); } return null; } @Override public ValueExpression setVariable(String variable, ValueExpression expression) { Object originalObj = this.elContext.variables.put(variable, expression.getValue(this.elContext)); if (originalObj == null) { return null; } return new ReadOnlyExpression(originalObj); } } @SuppressWarnings("serial") private static class ReadOnlyExpression extends ValueExpression { private final Object object; private ReadOnlyExpression(Object object) { this.object = object; } @Override public Class<?> getExpectedType() { return this.object.getClass(); } @Override public Class<?> getType(ELContext context) { return this.getExpectedType(); } @Override public Object getValue(ELContext context) { return this.object; } @Override public boolean isReadOnly(ELContext context) { return true; } @Override public void setValue(ELContext context, Object value) { throw new PropertyNotWritableException(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } try { ReadOnlyExpression other = (ReadOnlyExpression) obj; return this.object.equals(other.object); } catch (ClassCastException e) {} return false; } @Override public String getExpressionString() { return null; } @Override public int hashCode() { return this.object.hashCode(); } @Override public boolean isLiteralText() { return false; } } @SuppressWarnings("serial") private static class BasicValueExpression extends ValueExpression { private final BasicContext elContext; private final String varName; private BasicValueExpression(String varName, BasicContext elContext) { this.elContext = elContext; this.varName = varName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } try { BasicValueExpression other = (BasicValueExpression) obj; return this.varName.equals(other.varName); } catch (ClassCastException e) {} return false; } @Override public int hashCode() { return this.varName.hashCode(); } @Override public Object getValue(ELContext context) { return this.elContext.variables.get(this.varName); } @Override public String getExpressionString() { return null; } @Override public boolean isLiteralText() { return false; } @Override public Class<?> getType(ELContext context) { return this.getExpectedType(); } @Override public boolean isReadOnly(ELContext context) { return false; } @Override public void setValue(ELContext context, Object value) { this.elContext.variables.put(this.varName, value); } @Override public String toString() { return "ValueExpression(" + this.varName + ")"; } @Override public Class<?> getExpectedType() { Object obj = this.elContext.variables.get(this.varName); return obj == null ? null : obj.getClass(); } } /** Custom <code>CompositeELResolver</code> used to handle variable * auto-vivify. */ private static class ExtendedCompositeResolver extends CompositeELResolver { @Override public void setValue(ELContext context, Object base, Object property, Object val) { super.setValue(context, base, property, val); if (!context.isPropertyResolved() && base == null) { if (Debug.verboseOn()) { Debug.logVerbose("ExtendedCompositeResolver.setValue: base = " + base + ", property = " + property + ", value = " + val, module); } try { BasicContext elContext = (BasicContext) context; elContext.variables.put(property.toString(), val); context.setPropertyResolved(true); } catch (ClassCastException e) {} } } } /** Custom <code>ListELResolver</code> used to handle OFBiz * <code>List</code> syntax. */ private static class ExtendedListResolver extends ListELResolver { private boolean isReadOnly; private ExtendedListResolver(boolean isReadOnly) { super(isReadOnly); this.isReadOnly = isReadOnly; } @Override @SuppressWarnings("unchecked") public void setValue(ELContext context, Object base, Object property, Object val) { if (context == null) { throw new NullPointerException(); } if (base != null && base instanceof List) { if (isReadOnly) { throw new PropertyNotWritableException(); } String str = property.toString(); if ("add".equals(str)) { if (Debug.verboseOn()) { Debug.logVerbose("ExtendedListResolver.setValue adding List element: base = " + base + ", property = " + property + ", value = " + val, module); } context.setPropertyResolved(true); List list = (List) base; list.add(val); } else if (str.startsWith("insert@")) { if (Debug.verboseOn()) { Debug.logVerbose("ExtendedListResolver.setValue inserting List element: base = " + base + ", property = " + property + ", value = " + val, module); } context.setPropertyResolved(true); String indexStr = str.replace("insert@", ""); int index = Integer.parseInt(indexStr); List list = (List) base; try { list.add(index, val); } catch (UnsupportedOperationException ex) { throw new PropertyNotWritableException(); } catch (IndexOutOfBoundsException ex) { throw new PropertyNotFoundException(); } } else { super.setValue(context, base, property, val); } } } } /** Custom <code>MapELResolver</code> class used to accommodate * <code>LocalizedMap</code> instances. */ private static class ExtendedMapResolver extends MapELResolver { private ExtendedMapResolver(boolean isReadOnly) { super(isReadOnly); } @Override @SuppressWarnings("unchecked") public Object getValue(ELContext context, Object base, Object property) { if (context == null) { throw new NullPointerException(); } if (base != null && base instanceof LocalizedMap) { context.setPropertyResolved(true); LocalizedMap map = (LocalizedMap) base; Locale locale = null; try { VariableMapper vm = context.getVariableMapper(); ValueExpression ve = vm.resolveVariable(localizedMapLocaleKey); if (ve != null) { locale = (Locale) ve.getValue(context); } if (locale == null) { ve = vm.resolveVariable("locale"); if (ve != null) { locale = (Locale) ve.getValue(context); } } } catch (Exception e) { Debug.logWarning("Exception thrown while getting LocalizedMap element, locale = " + locale + ", exception " + e, module); } if (locale == null) { if (Debug.verboseOn()) { Debug.logVerbose("ExtendedMapResolver.getValue: unable to find Locale for LocalizedMap element, using default locale", module); } locale = Locale.getDefault(); } return resolveVariable(property.toString(), (Map) map, locale); } if (base != null && base instanceof Map && property instanceof String) { context.setPropertyResolved(true); return resolveVariable(property.toString(), (Map) base, null); } return super.getValue(context, base, property); } } /** Evaluates a property <code>Object</code> and returns a new * <code>List</code> or <code>Map</code>. If <code>property</code> * is not a String object type and it evaluates to an integer value, * a new <code>List</code> instance is returned, otherwise a new * <code>Map</code> instance is returned. * @param property Property <code>Object</code> to be evaluated * @return New <code>List</code> or <code>Map</code> */ @SuppressWarnings("rawtypes") public static Object autoVivifyListOrMap(Object property) { String str = property.toString(); boolean isList = ("add".equals(str) || str.startsWith("insert@")); if (!isList && !"java.lang.String".equals(property.getClass().getName())) { Integer index = UtilMisc.toIntegerObject(property); isList = (index != null); } if (isList) { return new LinkedList(); } else { return new HashMap(); } } /** Prepares an expression for evaluation by UEL.<p>The OFBiz syntax is * converted to UEL-compatible syntax and the resulting expression is * returned.</p> * @see <a href="StringUtil.html#convertOperatorSubstitutions(java.lang.String)">StringUtil.convertOperatorSubstitutions(java.lang.String)</a> * @param expression Expression to be converted * @return Converted expression */ public static String prepareExpression(String expression) { String result = StringUtil.convertOperatorSubstitutions(expression); result = result.replace("[]", "['add']"); int openBrace = result.indexOf("[+"); int closeBrace = (openBrace == -1 ? -1 : result.indexOf(']', openBrace)); if (closeBrace != -1) { String base = result.substring(0, openBrace); String property = result.substring(openBrace+2, closeBrace).trim(); String end = result.substring(closeBrace + 1); result = base + "['insert@" + property + "']" + end; } return result; } public static Object resolveVariable(String variable, Map<String, ? extends Object> variables, Locale locale) { Object obj = null; String createObjectType = null; String name = variable; if (variable.contains("$")) { if (variable.endsWith("$string")) { name = variable.substring(0, variable.length() - 7); createObjectType = "string"; } else if (variable.endsWith("$null")) { name = variable.substring(0, variable.length() - 5); createObjectType = "null"; } else if (variable.endsWith("$boolean")) { name = variable.substring(0, variable.length() - 8); createObjectType = "boolean"; } else if (variable.endsWith("$integer")) { name = variable.substring(0, variable.length() - 8); createObjectType = "integer"; } else if (variable.endsWith("$long")) { name = variable.substring(0, variable.length() - 5); createObjectType = "long"; } else if (variable.endsWith("$double")) { name = variable.substring(0, variable.length() - 7); createObjectType = "double"; } else if (variable.endsWith("$bigDecimal")) { name = variable.substring(0, variable.length() - 11); createObjectType = "bigDecimal"; } } if (variables instanceof LocalizedMap<?>) { if (locale == null) { locale = (Locale) variables.get(localizedMapLocaleKey); if (locale == null) { locale = (Locale) variables.get("locale"); if (locale == null) { locale = Locale.getDefault(); } } } obj = ((LocalizedMap<?>) variables).get(name, locale); } else { obj = variables.get(name); } if (obj != null) { return obj; } if (createObjectType != null) { if ("string".equals(createObjectType)) { return ""; } else if ("null".equals(createObjectType)) { return null; } else if ("boolean".equals(createObjectType)) { return Boolean.FALSE; } else if ("integer".equals(createObjectType)) { return Integer.valueOf(0); } else if ("long".equals(createObjectType)) { return Long.valueOf(0); } else if ("double".equals(createObjectType)) { return Double.valueOf(0); } else if ("bigDecimal".equals(createObjectType)) { return BigDecimal.ZERO; } } return null; } }