| /******************************************************************************* |
| * 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.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.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.StringUtil; |
| import org.ofbiz.base.util.UtilGenerics; |
| import org.ofbiz.base.util.UtilMisc; |
| import org.ofbiz.base.util.collections.LocalizedMap; |
| |
| /** Implements the Unified Expression Language (JSR-245). */ |
| public class UelUtil { |
| protected static final String module = UelUtil.class.getName(); |
| public static final String localizedMapLocaleKey = LocalizedMap.class.getName() + "_locale".replace(".", "_"); |
| protected static final ExpressionFactory exprFactory = JuelConnector.newExpressionFactory(); |
| protected 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)); |
| } |
| }; |
| |
| /** Evaluates a Unified Expression Language expression and returns the result. |
| * @param context Evaluation context (variables) |
| * @param expression UEL expression |
| * @return Result object |
| * @throws <code>javax.el.*</code> exceptions |
| */ |
| 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 |
| * @throws <code>javax.el.*</code> exceptions |
| */ |
| @SuppressWarnings("unchecked") |
| 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 |
| * @throws <code>javax.el.*</code> exceptions |
| */ |
| @SuppressWarnings("unchecked") |
| 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 |
| * @throws <code>javax.el.*</code> exceptions |
| */ |
| 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); |
| } |
| |
| protected static class BasicContext extends ELContext { |
| protected final Map<String, Object> variables; |
| protected final VariableMapper variableMapper; |
| public 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; |
| } |
| } |
| |
| protected static class ReadOnlyContext extends ELContext { |
| protected final Map<String, ? extends Object> variables; |
| protected final VariableMapper variableMapper; |
| public 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; |
| } |
| protected static class ReadOnlyVariableMapper extends VariableMapper { |
| protected final ReadOnlyContext elContext; |
| protected 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(); |
| } |
| } |
| } |
| |
| protected static class BasicVariableMapper extends VariableMapper { |
| protected final BasicContext elContext; |
| protected 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") |
| protected static class ReadOnlyExpression extends ValueExpression { |
| protected final Object object; |
| protected 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") |
| protected static class BasicValueExpression extends ValueExpression { |
| protected final BasicContext elContext; |
| protected final String varName; |
| public 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. |
| */ |
| protected 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. |
| */ |
| protected static class ExtendedListResolver extends ListELResolver { |
| protected boolean isReadOnly; |
| public 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. |
| */ |
| protected static class ExtendedMapResolver extends MapELResolver { |
| public 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; |
| } |
| |
| } |