/******************************************************************************* * 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.widget.model; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import org.apache.oro.text.regex.MalformedPatternException; import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.PatternMatcher; import org.apache.oro.text.regex.Perl5Matcher; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.GeneralException; import org.apache.ofbiz.base.util.ObjectType; import org.apache.ofbiz.base.util.PatternFactory; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.UtilXml; import org.apache.ofbiz.base.util.collections.FlexibleMapAccessor; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entityext.permission.EntityPermissionChecker; import org.apache.ofbiz.minilang.operation.BaseCompare; import org.apache.ofbiz.security.Security; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; import org.w3c.dom.Element; /** * Abstract base class for the condition models. */ @SuppressWarnings("serial") public abstract class AbstractModelCondition implements Serializable, ModelCondition { /* * ----------------------------------------------------------------------- * * DEVELOPERS PLEASE READ * ----------------------------------------------------------------------- * * * This model is intended to be a read-only data structure that represents * an XML element. Outside of object construction, the class should not * have any behaviors. * * Instances of this class will be shared by multiple threads - therefore * it is immutable. DO NOT CHANGE THE OBJECT'S STATE AT RUN TIME! * */ public static final String module = AbstractModelCondition.class.getName(); public static final ModelConditionFactory DEFAULT_CONDITION_FACTORY = new DefaultConditionFactory(); public static List<ModelCondition> readSubConditions(ModelConditionFactory factory, ModelWidget modelWidget, Element conditionElement) { List<? extends Element> subElementList = UtilXml.childElementList(conditionElement); List<ModelCondition> condList = new ArrayList<ModelCondition>(subElementList.size()); for (Element subElement : subElementList) { condList.add(factory.newInstance(modelWidget, subElement)); } return Collections.unmodifiableList(condList); } private final ModelWidget modelWidget; protected AbstractModelCondition(ModelConditionFactory factory, ModelWidget modelWidget, Element conditionElement) { this.modelWidget = modelWidget; } public ModelWidget getModelWidget() { return modelWidget; } @Override public String toString() { StringBuilder sb = new StringBuilder(); ModelConditionVisitor visitor = new XmlWidgetConditionVisitor(sb); try { accept(visitor); } catch (Exception e) { Debug.logWarning(e, "Exception thrown in XmlWidgetConditionVisitor: ", module); } return sb.toString(); } /** * Models the <and> element. * * @see <code>widget-common.xsd</code> */ public static class And extends AbstractModelCondition { private final List<ModelCondition> subConditions; private And(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.subConditions = readSubConditions(factory, modelWidget, condElement); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { // return false for the first one in the list that is false, basic and algo for (ModelCondition subCondition : this.subConditions) { if (!subCondition.eval(context)) { return false; } } return true; } public List<ModelCondition> getSubConditions() { return subConditions; } } /** * A <code>ModelCondition</code> factory. This factory handles elements * common to all widgets that support conditions. Widgets that have * specialized conditions can extend this class. * */ public static class DefaultConditionFactory implements ModelConditionFactory { public static final ModelCondition TRUE = new ModelCondition() { @Override public boolean eval(Map<String, Object> context) { return true; } @Override public void accept(ModelConditionVisitor visitor) throws Exception { } }; public static final ModelCondition FALSE = new ModelCondition() { @Override public boolean eval(Map<String, Object> context) { return false; } @Override public void accept(ModelConditionVisitor visitor) throws Exception { } }; public ModelCondition newInstance(ModelWidget modelWidget, Element conditionElement) { return newInstance(this, modelWidget, conditionElement); } // TODO: Test extended factory protected ModelCondition newInstance(ModelConditionFactory factory, ModelWidget modelWidget, Element conditionElement) { if (conditionElement == null) { return TRUE; } String nodeName = conditionElement.getNodeName(); if ("and".equals(nodeName)) { return new And(factory, modelWidget, conditionElement); } else if ("xor".equals(nodeName)) { return new Xor(factory, modelWidget, conditionElement); } else if ("or".equals(nodeName)) { return new Or(factory, modelWidget, conditionElement); } else if ("not".equals(nodeName)) { return new Not(factory, modelWidget, conditionElement); } else if ("if-service-permission".equals(nodeName)) { return new IfServicePermission(factory, modelWidget, conditionElement); } else if ("if-has-permission".equals(nodeName)) { return new IfHasPermission(factory, modelWidget, conditionElement); } else if ("if-validate-method".equals(nodeName)) { return new IfValidateMethod(factory, modelWidget, conditionElement); } else if ("if-compare".equals(nodeName)) { return new IfCompare(factory, modelWidget, conditionElement); } else if ("if-compare-field".equals(nodeName)) { return new IfCompareField(factory, modelWidget, conditionElement); } else if ("if-regexp".equals(nodeName)) { return new IfRegexp(factory, modelWidget, conditionElement); } else if ("if-empty".equals(nodeName)) { return new IfEmpty(factory, modelWidget, conditionElement); } else if ("if-entity-permission".equals(nodeName)) { return new IfEntityPermission(factory, modelWidget, conditionElement); } else { throw new IllegalArgumentException("Condition element not supported with name: " + conditionElement.getNodeName()); } } } /** * Models the <if-compare> element. * * @see <code>widget-common.xsd</code> */ public static class IfCompare extends AbstractModelCondition { private final FlexibleMapAccessor<Object> fieldAcsr; private final FlexibleStringExpander formatExdr; private final String operator; private final String type; private final FlexibleStringExpander valueExdr; private IfCompare(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); String fieldAcsr = condElement.getAttribute("field"); if (fieldAcsr.isEmpty()) fieldAcsr = condElement.getAttribute("field-name"); this.fieldAcsr = FlexibleMapAccessor.getInstance(fieldAcsr); this.valueExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("value")); this.operator = condElement.getAttribute("operator"); this.type = condElement.getAttribute("type"); this.formatExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("format")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { String value = this.valueExdr.expandString(context); String format = this.formatExdr.expandString(context); Object fieldVal = this.fieldAcsr.get(context); // always use an empty string by default if (fieldVal == null) { fieldVal = ""; } List<Object> messages = new LinkedList<Object>(); Boolean resultBool = BaseCompare.doRealCompare(fieldVal, value, operator, type, format, messages, null, null, true); if (messages.size() > 0) { messages.add(0, "Error with comparison in if-compare between field [" + fieldAcsr.toString() + "] with value [" + fieldVal + "] and value [" + value + "] with operator [" + operator + "] and type [" + type + "]: "); StringBuilder fullString = new StringBuilder(); for (Object item : messages) { fullString.append(item.toString()); } Debug.logWarning(fullString.toString(), module); throw new IllegalArgumentException(fullString.toString()); } return resultBool.booleanValue(); } public FlexibleMapAccessor<Object> getFieldAcsr() { return fieldAcsr; } public FlexibleStringExpander getFormatExdr() { return formatExdr; } public String getOperator() { return operator; } public String getType() { return type; } public FlexibleStringExpander getValueExdr() { return valueExdr; } } /** * Models the <if-compare-field> element. * * @see <code>widget-common.xsd</code> */ public static class IfCompareField extends AbstractModelCondition { private final FlexibleMapAccessor<Object> fieldAcsr; private final FlexibleStringExpander formatExdr; private final String operator; private final FlexibleMapAccessor<Object> toFieldAcsr; private final String type; private IfCompareField(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); String fieldAcsr = condElement.getAttribute("field"); if (fieldAcsr.isEmpty()) fieldAcsr = condElement.getAttribute("field-name"); this.fieldAcsr = FlexibleMapAccessor.getInstance(fieldAcsr); String toFieldAcsr = condElement.getAttribute("to-field"); if (toFieldAcsr.isEmpty()) toFieldAcsr = condElement.getAttribute("to-field-name"); this.toFieldAcsr = FlexibleMapAccessor.getInstance(toFieldAcsr); this.operator = condElement.getAttribute("operator"); this.type = condElement.getAttribute("type"); this.formatExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("format")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { String format = this.formatExdr.expandString(context); Object fieldVal = this.fieldAcsr.get(context); Object toFieldVal = this.toFieldAcsr.get(context); // always use an empty string by default if (fieldVal == null) { fieldVal = ""; } List<Object> messages = new LinkedList<Object>(); Boolean resultBool = BaseCompare.doRealCompare(fieldVal, toFieldVal, operator, type, format, messages, null, null, false); if (messages.size() > 0) { messages.add(0, "Error with comparison in if-compare-field between field [" + fieldAcsr.toString() + "] with value [" + fieldVal + "] and to-field [" + toFieldAcsr.toString() + "] with value [" + toFieldVal + "] with operator [" + operator + "] and type [" + type + "]: "); StringBuilder fullString = new StringBuilder(); for (Object item : messages) { fullString.append(item.toString()); } Debug.logWarning(fullString.toString(), module); throw new IllegalArgumentException(fullString.toString()); } return resultBool.booleanValue(); } public FlexibleMapAccessor<Object> getFieldAcsr() { return fieldAcsr; } public FlexibleStringExpander getFormatExdr() { return formatExdr; } public String getOperator() { return operator; } public FlexibleMapAccessor<Object> getToFieldAcsr() { return toFieldAcsr; } public String getType() { return type; } } /** * Models the <if-empty> element. * * @see <code>widget-common.xsd</code> */ public static class IfEmpty extends AbstractModelCondition { private final FlexibleMapAccessor<Object> fieldAcsr; private IfEmpty(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); String fieldAcsr = condElement.getAttribute("field"); if (fieldAcsr.isEmpty()) fieldAcsr = condElement.getAttribute("field-name"); this.fieldAcsr = FlexibleMapAccessor.getInstance(fieldAcsr); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { Object fieldVal = this.fieldAcsr.get(context); return ObjectType.isEmpty(fieldVal); } public FlexibleMapAccessor<Object> getFieldAcsr() { return fieldAcsr; } } /** * Models the <if-entity-permission> element. * * @see <code>widget-common.xsd</code> */ public static class IfEntityPermission extends AbstractModelCondition { private final EntityPermissionChecker permissionChecker; private IfEntityPermission(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.permissionChecker = new EntityPermissionChecker(condElement); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { return permissionChecker.runPermissionCheck(context); } public EntityPermissionChecker getPermissionChecker() { return permissionChecker; } } /** * Models the <if-has-permission> element. * * @see <code>widget-common.xsd</code> */ public static class IfHasPermission extends AbstractModelCondition { private final FlexibleStringExpander actionExdr; private final FlexibleStringExpander permissionExdr; private IfHasPermission(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.permissionExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("permission")); this.actionExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("action")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { // if no user is logged in, treat as if the user does not have permission GenericValue userLogin = (GenericValue) context.get("userLogin"); if (userLogin != null) { String permission = permissionExdr.expandString(context); String action = actionExdr.expandString(context); Security security = (Security) context.get("security"); if (UtilValidate.isNotEmpty(action)) { // run hasEntityPermission if (security.hasEntityPermission(permission, action, userLogin)) { return true; } } else { // run hasPermission if (security.hasPermission(permission, userLogin)) { return true; } } } return false; } public FlexibleStringExpander getActionExdr() { return actionExdr; } public FlexibleStringExpander getPermissionExdr() { return permissionExdr; } } /** * Models the <if-regexp> element. * * @see <code>widget-common.xsd</code> */ public static class IfRegexp extends AbstractModelCondition { private final FlexibleStringExpander exprExdr; private final FlexibleMapAccessor<Object> fieldAcsr; private IfRegexp(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); String fieldAcsr = condElement.getAttribute("field"); if (fieldAcsr.isEmpty()) fieldAcsr = condElement.getAttribute("field-name"); this.fieldAcsr = FlexibleMapAccessor.getInstance(fieldAcsr); this.exprExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("expr")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { Object fieldVal = this.fieldAcsr.get(context); String expr = this.exprExdr.expandString(context); Pattern pattern; try { pattern = PatternFactory.createOrGetPerl5CompiledPattern(expr, true); } catch (MalformedPatternException e) { String errMsg = "Error in evaluation in if-regexp in screen: " + e.toString(); Debug.logError(e, errMsg, module); throw new IllegalArgumentException(errMsg); } String fieldString = null; try { fieldString = (String) ObjectType.simpleTypeConvert(fieldVal, "String", null, (TimeZone) context.get("timeZone"), (Locale) context.get("locale"), true); } catch (GeneralException e) { Debug.logError(e, "Could not convert object to String, using empty String", module); } // always use an empty string by default if (fieldString == null) fieldString = ""; PatternMatcher matcher = new Perl5Matcher(); return matcher.matches(fieldString, pattern); } public FlexibleStringExpander getExprExdr() { return exprExdr; } public FlexibleMapAccessor<Object> getFieldAcsr() { return fieldAcsr; } } /** * Models the <if-service-permission> element. * * @see <code>widget-common.xsd</code> */ public static class IfServicePermission extends AbstractModelCondition { private final FlexibleStringExpander actionExdr; private final FlexibleStringExpander ctxMapExdr; private final FlexibleStringExpander resExdr; private final FlexibleStringExpander serviceExdr; private IfServicePermission(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.serviceExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("service-name")); this.actionExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("main-action")); this.ctxMapExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("context-map")); this.resExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("resource-description")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { // if no user is logged in, treat as if the user does not have permission GenericValue userLogin = (GenericValue) context.get("userLogin"); if (userLogin != null) { String serviceName = serviceExdr.expandString(context); String mainAction = actionExdr.expandString(context); String contextMap = ctxMapExdr.expandString(context); String resource = resExdr.expandString(context); if (UtilValidate.isEmpty(resource)) { resource = serviceName; } if (UtilValidate.isEmpty(serviceName)) { Debug.logWarning("No permission service-name specified!", module); return false; } Map<String, Object> serviceContext = UtilGenerics.toMap(context.get(contextMap)); if (serviceContext != null) { // copy the required internal fields serviceContext.put("userLogin", context.get("userLogin")); serviceContext.put("locale", context.get("locale")); } else { serviceContext = context; } // get the service engine objects LocalDispatcher dispatcher = (LocalDispatcher) context.get("dispatcher"); DispatchContext dctx = dispatcher.getDispatchContext(); // get the service ModelService permService; try { permService = dctx.getModelService(serviceName); } catch (GenericServiceException e) { Debug.logError(e, module); return false; } if (permService != null) { // build the context Map<String, Object> svcCtx = permService.makeValid(serviceContext, ModelService.IN_PARAM); svcCtx.put("resourceDescription", resource); if (UtilValidate.isNotEmpty(mainAction)) { svcCtx.put("mainAction", mainAction); } // invoke the service Map<String, Object> resp; try { resp = dispatcher.runSync(permService.name, svcCtx, 300, true); } catch (GenericServiceException e) { Debug.logError(e, module); return false; } if (ServiceUtil.isError(resp) || ServiceUtil.isFailure(resp)) { Debug.logError(ServiceUtil.getErrorMessage(resp), module); return false; } Boolean hasPermission = (Boolean) resp.get("hasPermission"); if (hasPermission != null) { return hasPermission.booleanValue(); } } } return false; } public FlexibleStringExpander getActionExdr() { return actionExdr; } public FlexibleStringExpander getCtxMapExdr() { return ctxMapExdr; } public FlexibleStringExpander getResExdr() { return resExdr; } public FlexibleStringExpander getServiceExdr() { return serviceExdr; } } /** * Models the <if-validate-method> element. * * @see <code>widget-common.xsd</code> */ public static class IfValidateMethod extends AbstractModelCondition { private final FlexibleStringExpander classExdr; private final FlexibleMapAccessor<Object> fieldAcsr; private final FlexibleStringExpander methodExdr; private IfValidateMethod(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); String fieldAcsr = condElement.getAttribute("field"); if (fieldAcsr.isEmpty()) fieldAcsr = condElement.getAttribute("field-name"); this.fieldAcsr = FlexibleMapAccessor.getInstance(fieldAcsr); this.methodExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("method")); this.classExdr = FlexibleStringExpander.getInstance(condElement.getAttribute("class")); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { String methodName = this.methodExdr.expandString(context); String className = this.classExdr.expandString(context); Object fieldVal = this.fieldAcsr.get(context); String fieldString = null; if (fieldVal != null) { try { fieldString = (String) ObjectType.simpleTypeConvert(fieldVal, "String", null, (TimeZone) context.get("timeZone"), (Locale) context.get("locale"), true); } catch (GeneralException e) { Debug.logError(e, "Could not convert object to String, using empty String", module); } } // always use an empty string by default if (fieldString == null) fieldString = ""; Class<?>[] paramTypes = new Class[] { String.class }; Object[] params = new Object[] { fieldString }; Class<?> valClass; try { valClass = ObjectType.loadClass(className); } catch (ClassNotFoundException cnfe) { Debug.logError("Could not find validation class: " + className, module); return false; } Method valMethod; try { valMethod = valClass.getMethod(methodName, paramTypes); } catch (NoSuchMethodException cnfe) { Debug.logError("Could not find validation method: " + methodName + " of class " + className, module); return false; } Boolean resultBool = Boolean.FALSE; try { resultBool = (Boolean) valMethod.invoke(null, params); } catch (Exception e) { Debug.logError(e, "Error in IfValidationMethod " + methodName + " of class " + className + ", defaulting to false ", module); } return resultBool.booleanValue(); } public FlexibleStringExpander getClassExdr() { return classExdr; } public FlexibleMapAccessor<Object> getFieldAcsr() { return fieldAcsr; } public FlexibleStringExpander getMethodExdr() { return methodExdr; } } /** * Models the <not> element. * * @see <code>widget-common.xsd</code> */ public static class Not extends AbstractModelCondition { private final ModelCondition subCondition; private Not(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); Element firstChildElement = UtilXml.firstChildElement(condElement); this.subCondition = factory.newInstance(modelWidget, firstChildElement); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { return !this.subCondition.eval(context); } public ModelCondition getSubCondition() { return subCondition; } } /** * Models the <or> element. * * @see <code>widget-common.xsd</code> */ public static class Or extends AbstractModelCondition { private final List<ModelCondition> subConditions; private Or(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.subConditions = readSubConditions(factory, modelWidget, condElement); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { // return true for the first one in the list that is true, basic or algo for (ModelCondition subCondition : this.subConditions) { if (subCondition.eval(context)) { return true; } } return false; } public List<ModelCondition> getSubConditions() { return subConditions; } } /** * Models the <xor> element. * * @see <code>widget-common.xsd</code> */ public static class Xor extends AbstractModelCondition { private final List<ModelCondition> subConditions; private Xor(ModelConditionFactory factory, ModelWidget modelWidget, Element condElement) { super(factory, modelWidget, condElement); this.subConditions = readSubConditions(factory, modelWidget, condElement); } @Override public void accept(ModelConditionVisitor visitor) throws Exception { visitor.visit(this); } @Override public boolean eval(Map<String, Object> context) { // if more than one is true stop immediately and return false; if all are false return false; if only one is true return true boolean foundOneTrue = false; for (ModelCondition subCondition : this.subConditions) { if (subCondition.eval(context)) { if (foundOneTrue) { // now found two true, so return false return false; } else { foundOneTrue = true; } } } return foundOneTrue; } public List<ModelCondition> getSubConditions() { return subConditions; } } }