com.autobizlogic.abl.rule.FormulaRule.java Source code

Java tutorial

Introduction

Here is the source code for com.autobizlogic.abl.rule.FormulaRule.java

Source

package com.autobizlogic.abl.rule;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;

import com.autobizlogic.abl.config.LogicConfiguration;
import com.autobizlogic.abl.config.LogicConfiguration.PropertyName;
import com.autobizlogic.abl.data.PersistentBean;
import com.autobizlogic.abl.engine.LogicRunner;
import com.autobizlogic.abl.perf.PerformanceMonitor;
import com.autobizlogic.abl.engine.LogicException;
import com.autobizlogic.abl.logic.LogicContext;
import com.autobizlogic.abl.metadata.MetaAttribute;
import com.autobizlogic.abl.event.LogicAfterFormulaEvent;
import com.autobizlogic.abl.session.LogicTransactionContext;
import com.autobizlogic.abl.util.MethodInvocationUtil;
import com.autobizlogic.abl.util.ObjectUtil;

/**
 * Define and execute a formula rule. This gets created by LogicGroup when it encounters a
 * @Formula annotation on a method.
 */
public class FormulaRule extends AbstractDependsOnRule {

    private boolean lazy = false;

    private boolean persistent = true;

    private String expression = null;

    private boolean skipDuringRecompute = false;

    private boolean pruning = true;

    protected static final JexlEngine jexlEngine = new JexlEngine();
    static {
        jexlEngine.setCache(512);
        jexlEngine.setSilent(false);
    }

    /**
     * Construct a new formula
     * @param logicGroup The logic group containing the logic for this formula
     * @param logicMethodName The name of the method containing the logic for this formula
     * @param beanAttributeName The name of the bean's attribute for which this formula is defined
     */
    protected FormulaRule(LogicGroup logicGroup, String logicMethodName, String beanAttributeName) {
        this.logicGroup = logicGroup;
        this.logicMethodName = logicMethodName;
        this.setBeanAttributeName(beanAttributeName);
    }

    public boolean isLazy() {
        return lazy;
    }

    protected void setLazy(boolean b) {
        lazy = b;
    }

    public boolean isPersistent() {
        return persistent;
    }

    protected void setPersistent(boolean b) {
        persistent = b;
    }

    public String getExpression() {
        return expression;
    }

    protected void setExpression(String s) {
        expression = s;
    }

    public boolean isSkipDuringRecompute() {
        return skipDuringRecompute;
    }

    protected void setSkipDuringRecompute(boolean b) {
        this.skipDuringRecompute = b;
    }

    public boolean isPruning() {
        return pruning;
    }

    protected void setPruning(boolean b) {
        pruning = b;
    }

    /**
     * Execute this formula method in a LogicObject
     */
    public boolean execute(Object aLogicObject, LogicRunner aLogicRunner) {

        long startTime = System.nanoTime();
        boolean rtnDidExecute = false;
        String theLogicMethodName = getLogicMethodName();
        Object result = null;
        if (pruning && isFormulaPrunable(aLogicRunner)) {
            if (log.isDebugEnabled())
                log.debug("Formula pruned " + getBeanAttributeName(), aLogicRunner);
            return false; // Note that we do not fire any event since the formula was not executed
        }

        PersistentBean currentDomainObject = aLogicRunner.getCurrentDomainObject();

        // Is the formula expressed in the annotation? If so, we evaluate it, and we call
        // the method as a courtesy but ignore its return value.
        if (expression != null && expression.trim().length() > 0) {
            result = evaluateExpression(currentDomainObject, aLogicRunner.getLogicContext());

            MetaAttribute metaAttribute = currentDomainObject.getMetaEntity()
                    .getMetaAttribute(getBeanAttributeName());
            Object convertedResult = ObjectUtil.convertToDataType(result, metaAttribute.getType());

            Object oldValue = currentDomainObject.get(getBeanAttributeName());

            // If the formula has the same value as the stored value, there is nothing to do, and the formula should not
            // be considered to have really fired.
            if (!((oldValue == null && result == null)
                    || (oldValue != null && result != null && oldValue.equals(result)))) {
                currentDomainObject.put(getBeanAttributeName(), convertedResult);
                rtnDidExecute = true;

                if ("true".equals(
                        LogicConfiguration.getInstance().getProperty(PropertyName.INVOKE_FORMULA_METHODS))) {
                    try { // Then call the method for debugging purposes, but ignore its return value
                        MethodInvocationUtil.invokeMethodOnObject(aLogicObject, theLogicMethodName);
                    } catch (Exception ex) {
                        if (log.isWarnEnabled())
                            log.warn("Formula method " + this.getLogicGroup().getLogicClassName() + "."
                                    + this.getLogicMethodName() + " threw an exception. This is not fatal because "
                                    + "the formula has an expression in its declaration, and therefore the method itself "
                                    + "is called purely as a courtesy, but this should be examined. The exception was: "
                                    + ex);
                    }
                }

                firePostEvent(aLogicObject, aLogicRunner, oldValue, System.nanoTime() - startTime);
            }

            if (rtnDidExecute && log.isDebugEnabled())
                log.debug("Formula changes attribute " + getBeanAttributeName() + " -> " + convertedResult + " on",
                        aLogicRunner);

            return rtnDidExecute;
        }

        // If the formula is not expressed in the annotation, it must be in the code

        if (!"true".equals(LogicConfiguration.getInstance().getProperty(PropertyName.INVOKE_FORMULA_METHODS))) {
            throw new RuntimeException(toString()
                    + " does not have an annotation-based definition, but the ABL configuration "
                    + "specifies that formula methods should not be invoked. If you wish to enable formula methods, you should change "
                    + "your ABLConfig.properties file to indicate that with: invokeFormulaMethods=true");
        }

        try {
            result = MethodInvocationUtil.invokeMethodOnObject(aLogicObject, theLogicMethodName);
            if (result == null && getLogicGroup().isGroovy()) {
                if (sysLog.isDebugEnabled())
                    sysLog.debug("Groovy formula returns null, value unchanged " + getBeanAttributeName(),
                            aLogicRunner);

            } else {
                if (result != null && result.equals(LogicContext.nullValue) && getLogicGroup().isGroovy()) {
                    result = null;
                }
                currentDomainObject = aLogicRunner.getCurrentDomainObject();
                Object oldValue = currentDomainObject.get(getBeanAttributeName());

                // If the formula has the same value as the stored value, there is nothing to do, and the formula should not
                // be considered to have really fired.
                if (!((oldValue == null && result == null)
                        || (oldValue != null && result != null && oldValue.equals(result)))) {
                    currentDomainObject.put(getBeanAttributeName(), result);
                    rtnDidExecute = true;
                    firePostEvent(aLogicObject, aLogicRunner, oldValue, System.nanoTime() - startTime);
                }

                if (rtnDidExecute && log.isDebugEnabled())
                    log.debug("Formula changes attribute " + getBeanAttributeName() + " -> " + result + " on",
                            aLogicRunner);
            }
        } catch (Exception e) {
            throw new LogicException(
                    "Failure setting formula for : " + getBeanAttributeName() + " on: " + currentDomainObject, e);
        }

        return rtnDidExecute;
    }

    /**
     * Get the formula's value for a given object. This is used for recompute.
     */
    public Object getFormulaValueForObject(Object aLogicObject, PersistentBean bean) {
        String theLogicMethodName = getLogicMethodName();
        Object result = null;

        if (expression != null && expression.trim().length() > 0) {
            try {
                result = evaluateExpression(bean, null);
            } catch (Exception ex) {
                throw new RuntimeException("Exception while computing formula expression " + theLogicMethodName
                        + " on object " + aLogicObject, ex);
            }
        } else {
            try {
                result = MethodInvocationUtil.invokeMethodOnObject(aLogicObject, theLogicMethodName);
            } catch (Exception ex) {
                throw new RuntimeException(
                        "Exception while computing formula " + theLogicMethodName + " on object " + aLogicObject,
                        ex);
            }
        }

        return result;
    }

    /**
     * Translate the expression into a valid SQL expression
     */
    public String getExpressionSQL() {

        if (expression == null)
            return null;
        String sqlClause = expression.replaceAll("\\|\\|", " OR ");
        sqlClause = sqlClause.replaceAll("&&", " AND ");
        sqlClause = sqlClause.replaceAll("==null", " IS NULL ");
        sqlClause = sqlClause.replaceAll("== null", " IS NULL ");
        sqlClause = sqlClause.replaceAll("!=null", " IS NOT NULL ");
        sqlClause = sqlClause.replaceAll("!= null", " IS NOT NULL ");
        sqlClause = sqlClause.replaceAll("==", "=");
        sqlClause = sqlClause.replaceAll("!=", "<>");
        sqlClause = sqlClause.replaceAll("!", " NOT ");
        return sqlClause;
    }

    /**
     * If the formula is defined with an expression, this is where we evaluate the expression.
     * @param aLogicObject
     * @return
     */
    private Object evaluateExpression(PersistentBean obj, LogicContext logicContext) {
        if (expression == null || expression.trim().length() == 0)
            return null;

        JexlContext context = new BeanMapContext(obj, logicContext, true);
        Expression expr = jexlEngine.createExpression(expression);
        Object res = null;
        try {
            res = expr.evaluate(context);
        } catch (Exception ex) {
            throw new LogicException("Error while evaluating expression : " + expression, ex);
        }

        if (res instanceof BeanMapContext.NullObjectMap)
            return null;

        return res;
    }

    /*
    public void computeValue(Object object, LogicTransactionContext context) {
       Class<?> cls = object.getClass();
       String methodName = getLogicMethodName();
       Object value = null;
       try {
     Method method = cls.getMethod(methodName, (Class<?>[])null);
     value = method.invoke(object, (Object[])null);
       } catch (Exception ex) {
     throw new RuntimeException("Exception while invoking formula - ", ex);
       }
        
       BeanMap beanMap = new BeanMap(object);
       beanMap.put(this.getBeanAttributeName(), value);
    }
     */

    /**
     * Fire the post event for this formula.
     */
    protected void firePostEvent(Object aLogicObject, LogicRunner aLogicRunner, Object oldValue,
            long executionTime) {
        LogicAfterFormulaEvent evt = new LogicAfterFormulaEvent(aLogicRunner.getContext(),
                aLogicRunner.getLogicContext(), this, aLogicRunner.getCurrentDomainObject(), oldValue);
        evt.setExecutionTime(executionTime);
        LogicTransactionContext.fireEvent(evt);
        PerformanceMonitor.addRuleExecution(this, executionTime);
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("Formula ");
        sb.append(getLogicGroup().getMetaEntity().getEntityName());
        sb.append("#");
        sb.append(getLogicMethodName());
        if (expression != null && expression.trim().length() > 0) {
            sb.append(" [");
            sb.append(expression);
            sb.append("]");
        }
        return sb.toString();
    }

    @SuppressWarnings("unused")
    private final static String SVN_ID = "$Id: Version 2.1.5 Build 0602 Date 2012-04-28-14-13  FormulaRule.java 1303 2012-04-28 00:16:10Z max@automatedbusinesslogic.com $";
}

/*
 * The contents of this file are subject to the Automated Business Logic Public License Version 1.0 (the "License"),
 * which is derived from the Mozilla Public License version 1.1. You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://www.automatedbusinesslogic.com/license/public-license
 *
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, 
 * either express or implied. See the License for the specific language governing rights and limitations under the License.
 */