/******************************************************************************* * 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.minilang.method.envops; import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.ObjectType; import org.apache.ofbiz.base.util.Scriptlet; import org.apache.ofbiz.base.util.StringUtil; import org.apache.ofbiz.base.util.collections.FlexibleMapAccessor; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; import org.apache.ofbiz.minilang.MiniLangException; import org.apache.ofbiz.minilang.MiniLangUtil; import org.apache.ofbiz.minilang.MiniLangValidate; import org.apache.ofbiz.minilang.SimpleMethod; import org.apache.ofbiz.minilang.method.MethodContext; import org.apache.ofbiz.minilang.method.MethodOperation; import org.w3c.dom.Element; /** * Implements the <set> element. * * @see <a href="https://cwiki.apache.org/confluence/display/OFBADMIN/Mini+Language+-+minilang+-+simple-method+-+Reference">Mini-language Referenc</a> */ public final class SetOperation extends MethodOperation { public static final String module = SetOperation.class.getName(); // This method is needed only during the v1 to v2 transition private static boolean autoCorrect(Element element) { boolean elementModified = false; // Correct deprecated default-value attribute String defaultAttr = element.getAttribute("default-value"); if (defaultAttr.length() > 0) { element.setAttribute("default", defaultAttr); element.removeAttribute("default-value"); elementModified = true; } // Correct deprecated from-field attribute String fromAttr = element.getAttribute("from-field"); if (fromAttr.length() > 0) { element.setAttribute("from", fromAttr); element.removeAttribute("from-field"); elementModified = true; } // Correct value attribute expression that belongs in from attribute String valueAttr = element.getAttribute("value").trim(); if (valueAttr.startsWith("${") && valueAttr.endsWith("}")) { valueAttr = valueAttr.substring(2, valueAttr.length() - 1); if (!valueAttr.contains("${")) { element.setAttribute("from", valueAttr); element.removeAttribute("value"); elementModified = true; } } return elementModified; } private final FlexibleStringExpander defaultFse; private final FlexibleStringExpander formatFse; private final FlexibleMapAccessor<Object> fieldFma; private final FlexibleMapAccessor<Object> fromFma; private final Scriptlet scriptlet; private final boolean setIfEmpty; private final boolean setIfNull; private final Class<?> targetClass; private final String type; private final FlexibleStringExpander valueFse; public SetOperation(Element element, SimpleMethod simpleMethod) throws MiniLangException { super(element, simpleMethod); if (MiniLangValidate.validationOn()) { MiniLangValidate.deprecatedAttribute(simpleMethod, element, "from-field", "replace with \"from\""); MiniLangValidate.deprecatedAttribute(simpleMethod, element, "default-value", "replace with \"default\""); MiniLangValidate.attributeNames(simpleMethod, element, "field", "from-field", "from", "value", "default-value", "default", "format", "type", "set-if-null", "set-if-empty"); MiniLangValidate.requiredAttributes(simpleMethod, element, "field"); MiniLangValidate.requireAnyAttribute(simpleMethod, element, "from-field", "from", "value"); MiniLangValidate.constantPlusExpressionAttributes(simpleMethod, element, "value"); MiniLangValidate.constantAttributes(simpleMethod, element, "type", "set-if-null", "set-if-empty"); MiniLangValidate.expressionAttributes(simpleMethod, element, "field"); MiniLangValidate.noChildElements(simpleMethod, element); } boolean elementModified = autoCorrect(element); if (elementModified && MiniLangUtil.autoCorrectOn()) { MiniLangUtil.flagDocumentAsCorrected(element); } this.fieldFma = FlexibleMapAccessor.getInstance(element.getAttribute("field")); String fromAttribute = element.getAttribute("from"); if (MiniLangUtil.containsScript(fromAttribute)) { this.scriptlet = new Scriptlet(StringUtil.convertOperatorSubstitutions(fromAttribute)); this.fromFma = FlexibleMapAccessor.getInstance(null); } else { this.scriptlet = null; this.fromFma = FlexibleMapAccessor.getInstance(fromAttribute); } this.valueFse = FlexibleStringExpander.getInstance(element.getAttribute("value")); this.defaultFse = FlexibleStringExpander.getInstance(element.getAttribute("default")); this.formatFse = FlexibleStringExpander.getInstance(element.getAttribute("format")); this.type = element.getAttribute("type"); Class<?> targetClass = null; if (!this.type.isEmpty() && !"NewList".equals(this.type) && !"NewMap".equals(this.type)) { try { targetClass = ObjectType.loadClass(this.type); } catch (ClassNotFoundException e) { MiniLangValidate.handleError("Invalid type " + this.type, simpleMethod, element); } } this.targetClass = targetClass; this.setIfNull = "true".equals(element.getAttribute("set-if-null")); // default to false, anything but true is false this.setIfEmpty = !"false".equals(element.getAttribute("set-if-empty")); // default to true, anything but false is true if (!fromAttribute.isEmpty() && !this.valueFse.isEmpty()) { throw new IllegalArgumentException("Cannot include both a from attribute and a value attribute in a <set> element."); } } @Override public boolean exec(MethodContext methodContext) throws MiniLangException { boolean isConstant = false; Object newValue = null; if (this.scriptlet != null) { try { newValue = this.scriptlet.executeScript(methodContext.getEnvMap()); } catch (Exception exc) { Debug.logWarning(exc, "Error evaluating scriptlet [" + this.scriptlet + "]: " + exc, module); } } else if (!this.fromFma.isEmpty()) { newValue = this.fromFma.get(methodContext.getEnvMap()); if (Debug.verboseOn()) Debug.logVerbose("In screen getting value for field from [" + this.fromFma.toString() + "]: " + newValue, module); } else if (!this.valueFse.isEmpty()) { newValue = this.valueFse.expand(methodContext.getEnvMap()); isConstant = true; } // If newValue is still empty, use the default value if (ObjectType.isEmpty(newValue) && !this.defaultFse.isEmpty()) { newValue = this.defaultFse.expand(methodContext.getEnvMap()); isConstant = true; } if (!setIfNull && newValue == null && !"NewMap".equals(this.type) && !"NewList".equals(this.type)) { if (Debug.verboseOn()) Debug.logVerbose("Field value not found (null) with name [" + fromFma + "] and value [" + valueFse + "], and there was not default value, not setting field", module); return true; } if (!setIfEmpty && ObjectType.isEmpty(newValue)) { if (Debug.verboseOn()) Debug.logVerbose("Field value not found (empty) with name [" + fromFma + "] and value [" + valueFse + "], and there was not default value, not setting field", module); return true; } if (this.type.length() > 0) { if ("NewMap".equals(this.type)) { newValue = new HashMap<String, Object>(); } else if ("NewList".equals(this.type)) { newValue = new LinkedList<Object>(); } else { try { String format = null; if (!this.formatFse.isEmpty()) { format = this.formatFse.expandString(methodContext.getEnvMap()); } Class<?> targetClass = this.targetClass; if (targetClass == null) { targetClass = MiniLangUtil.getObjectClassForConversion(newValue); } if (isConstant) { // We use en locale here so constant (literal) values are converted properly. newValue = MiniLangUtil.convertType(newValue, targetClass, Locale.ENGLISH, methodContext.getTimeZone(), format); } else { newValue = MiniLangUtil.convertType(newValue, targetClass, methodContext.getLocale(), methodContext.getTimeZone(), format); } } catch (Exception e) { String errMsg = "Could not convert field value for the field: [" + this.fieldFma.toString() + "] to the [" + this.type + "] type for the value [" + newValue + "]: " + e.getMessage(); Debug.logWarning(e, errMsg, module); this.simpleMethod.addErrorMessage(methodContext, errMsg); return false; } } } if (Debug.verboseOn()) Debug.logVerbose("Setting field [" + this.fieldFma.toString() + "] to value: " + newValue, module); this.fieldFma.put(methodContext.getEnvMap(), newValue); return true; } @Override public String toString() { StringBuilder sb = new StringBuilder("<set "); if (!this.fieldFma.isEmpty()) { sb.append("field=\"").append(this.fieldFma).append("\" "); } if (!this.fromFma.isEmpty()) { sb.append("from=\"").append(this.fromFma).append("\" "); } if (this.scriptlet != null) { sb.append("from=\"").append(this.scriptlet).append("\" "); } if (!this.valueFse.isEmpty()) { sb.append("value=\"").append(this.valueFse).append("\" "); } if (!this.defaultFse.isEmpty()) { sb.append("default=\"").append(this.defaultFse).append("\" "); } if (this.type.length() > 0) { sb.append("type=\"").append(this.type).append("\" "); } if (this.setIfNull) { sb.append("set-if-null=\"true\" "); } if (!this.setIfEmpty) { sb.append("set-if-empty=\"false\" "); } sb.append("/>"); return sb.toString(); } /** * A factory for the <set> element. */ public static final class SetOperationFactory implements Factory<SetOperation> { @Override public SetOperation createMethodOperation(Element element, SimpleMethod simpleMethod) throws MiniLangException { return new SetOperation(element, simpleMethod); } @Override public String getName() { return "set"; } } }