org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator.java

Source

/*
 * 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.unomi.plugins.baseplugin.conditions;

import ognl.Node;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.enhance.ExpressionAccessor;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.unomi.api.Event;
import org.apache.unomi.api.Item;
import org.apache.unomi.api.conditions.Condition;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionEvaluator;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionEvaluatorDispatcher;
import org.apache.unomi.persistence.spi.PropertyHelper;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * Evaluator for property comparison conditions
 */
public class PropertyConditionEvaluator implements ConditionEvaluator {

    private static final Logger logger = LoggerFactory.getLogger(PropertyConditionEvaluator.class.getName());

    private static final SimpleDateFormat yearMonthDayDateFormat = new SimpleDateFormat("yyyyMMdd");

    private BeanUtilsBean beanUtilsBean = BeanUtilsBean.getInstance();

    private Map<String, Map<String, ExpressionAccessor>> expressionCache = new HashMap<>(64);

    private int compare(Object actualValue, String expectedValue, Object expectedValueDate,
            Object expectedValueInteger, Object expectedValueDateExpr) {
        if (expectedValue == null && expectedValueDate == null && expectedValueInteger == null
                && getDate(expectedValueDateExpr) == null) {
            return actualValue == null ? 0 : 1;
        } else if (actualValue == null) {
            return -1;
        }

        if (expectedValueInteger != null) {
            return PropertyHelper.getInteger(actualValue)
                    .compareTo(PropertyHelper.getInteger(expectedValueInteger));
        } else if (expectedValueDate != null) {
            return getDate(actualValue).compareTo(getDate(expectedValueDate));
        } else if (expectedValueDateExpr != null) {
            return getDate(actualValue).compareTo(getDate(expectedValueDateExpr));
        } else {
            return actualValue.toString().compareTo(expectedValue);
        }
    }

    private boolean compareMultivalue(Object actualValue, List<?> expectedValues, List<?> expectedValuesDate,
            List<?> expectedValuesNumber, List<?> expectedValuesDateExpr, String op) {
        @SuppressWarnings("unchecked")
        List<?> expected = ObjectUtils.firstNonNull(expectedValues, expectedValuesDate, expectedValuesNumber);
        if (actualValue == null) {
            return expected == null;
        } else if (expected == null) {
            return false;
        }

        List<Object> actual = ConditionContextHelper.foldToASCII(getValueSet(actualValue));

        boolean result = true;

        switch (op) {
        case "in":
            result = false;
            for (Object a : actual) {
                if (expected.contains(a)) {
                    result = true;
                    break;
                }
            }
            break;
        case "notIn":
            for (Object a : actual) {
                if (expected.contains(a)) {
                    result = false;
                    break;
                }
            }
            break;
        case "all":
            for (Object e : expected) {
                if (!actual.contains(e)) {
                    result = false;
                    break;
                }
            }
            break;

        default:
            throw new IllegalArgumentException("Unknown comparison operator " + op);
        }

        return result;
    }

    @Override
    public boolean eval(Condition condition, Item item, Map<String, Object> context,
            ConditionEvaluatorDispatcher dispatcher) {
        String op = (String) condition.getParameter("comparisonOperator");
        String name = (String) condition.getParameter("propertyName");

        String expectedValue = ConditionContextHelper.foldToASCII((String) condition.getParameter("propertyValue"));
        Object expectedValueInteger = condition.getParameter("propertyValueInteger");
        Object expectedValueDate = condition.getParameter("propertyValueDate");
        Object expectedValueDateExpr = condition.getParameter("propertyValueDateExpr");

        Object actualValue;
        if (item instanceof Event && "eventType".equals(name)) {
            actualValue = ((Event) item).getEventType();
        } else {
            try {
                long time = System.nanoTime();
                //actualValue = beanUtilsBean.getPropertyUtils().getProperty(item, name);
                actualValue = getPropertyValue(item, name);
                time = System.nanoTime() - time;
                if (time > 5000000L) {
                    logger.info("eval took {} ms for {} {}", time / 1000000L, item.getClass().getName(), name);
                }
            } catch (NullPointerException e) {
                // property not found
                actualValue = null;
            } catch (Exception e) {
                if (!(e instanceof OgnlException)
                        || (!StringUtils.startsWith(e.getMessage(), "source is null for getProperty(null"))) {
                    logger.warn("Error evaluating value for " + item.getClass().getName() + " " + name, e);
                }
                actualValue = null;
            }
        }
        if (actualValue instanceof String) {
            actualValue = ConditionContextHelper.foldToASCII((String) actualValue);
        }

        if (op == null) {
            return false;
        } else if (actualValue == null) {
            return op.equals("missing");
        } else if (op.equals("exists")) {
            return true;
        } else if (op.equals("equals")) {
            if (actualValue instanceof Collection) {
                for (Object o : ((Collection<?>) actualValue)) {
                    if (o instanceof String) {
                        o = ConditionContextHelper.foldToASCII((String) o);
                    }
                    if (compare(o, expectedValue, expectedValueDate, expectedValueInteger,
                            expectedValueDateExpr) == 0) {
                        return true;
                    }
                }
                return false;
            }
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) == 0;
        } else if (op.equals("notEquals")) {
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) != 0;
        } else if (op.equals("greaterThan")) {
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) > 0;
        } else if (op.equals("greaterThanOrEqualTo")) {
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) >= 0;
        } else if (op.equals("lessThan")) {
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) < 0;
        } else if (op.equals("lessThanOrEqualTo")) {
            return compare(actualValue, expectedValue, expectedValueDate, expectedValueInteger,
                    expectedValueDateExpr) <= 0;
        } else if (op.equals("between")) {
            List<?> expectedValuesInteger = (List<?>) condition.getParameter("propertyValuesInteger");
            List<?> expectedValuesDate = (List<?>) condition.getParameter("propertyValuesDate");
            List<?> expectedValuesDateExpr = (List<?>) condition.getParameter("propertyValuesDateExpr");
            return compare(actualValue, null,
                    (expectedValuesDate != null && expectedValuesDate.size() >= 1)
                            ? getDate(expectedValuesDate.get(0))
                            : null,
                    (expectedValuesInteger != null && expectedValuesInteger.size() >= 1)
                            ? (Integer) expectedValuesInteger.get(0)
                            : null,
                    (expectedValuesDateExpr != null && expectedValuesDateExpr.size() >= 1)
                            ? (String) expectedValuesDateExpr.get(0)
                            : null) >= 0
                    && compare(actualValue, null,
                            (expectedValuesDate != null && expectedValuesDate.size() >= 2)
                                    ? getDate(expectedValuesDate.get(1))
                                    : null,
                            (expectedValuesInteger != null && expectedValuesInteger.size() >= 2)
                                    ? (Integer) expectedValuesInteger.get(1)
                                    : null,
                            (expectedValuesDateExpr != null && expectedValuesDateExpr.size() >= 2)
                                    ? (String) expectedValuesDateExpr.get(1)
                                    : null) <= 0;
        } else if (op.equals("contains")) {
            return actualValue.toString().contains(expectedValue);
        } else if (op.equals("startsWith")) {
            return actualValue.toString().startsWith(expectedValue);
        } else if (op.equals("endsWith")) {
            return actualValue.toString().endsWith(expectedValue);
        } else if (op.equals("matchesRegex")) {
            return expectedValue != null
                    && Pattern.compile(expectedValue).matcher(actualValue.toString()).matches();
        } else if (op.equals("in") || op.equals("notIn") || op.equals("all")) {
            List<?> expectedValues = ConditionContextHelper
                    .foldToASCII((List<?>) condition.getParameter("propertyValues"));
            List<?> expectedValuesInteger = (List<?>) condition.getParameter("propertyValuesInteger");
            List<?> expectedValuesDate = (List<?>) condition.getParameter("propertyValuesDate");
            List<?> expectedValuesDateExpr = (List<?>) condition.getParameter("propertyValuesDateExpr");

            return compareMultivalue(actualValue, expectedValues, expectedValuesDate, expectedValuesInteger,
                    expectedValuesDateExpr, op);
        } else if (op.equals("isDay") && expectedValueDate != null) {
            return yearMonthDayDateFormat.format(getDate(actualValue))
                    .equals(yearMonthDayDateFormat.format(getDate(expectedValueDate)));
        } else if (op.equals("isNotDay") && expectedValueDate != null) {
            return !yearMonthDayDateFormat.format(getDate(actualValue))
                    .equals(yearMonthDayDateFormat.format(getDate(expectedValueDate)));
        }

        return false;
    }

    private Object getPropertyValue(Item item, String expression) throws Exception {
        ExpressionAccessor accessor = getPropertyAccessor(item, expression);
        return accessor != null ? accessor.get((OgnlContext) Ognl.createDefaultContext(null), item) : null;
    }

    private ExpressionAccessor getPropertyAccessor(Item item, String expression) throws Exception {
        ExpressionAccessor accessor = null;
        String clazz = item.getClass().getName();
        Map<String, ExpressionAccessor> expressions = expressionCache.get(clazz);
        if (expressions == null) {
            expressions = new HashMap<>();
            expressionCache.put(clazz, expressions);
        } else {
            accessor = expressions.get(expression);
        }
        if (accessor == null) {
            long time = System.nanoTime();
            Thread current = Thread.currentThread();
            ClassLoader contextCL = current.getContextClassLoader();
            try {
                current.setContextClassLoader(PropertyConditionEvaluator.class.getClassLoader());
                Node node = Ognl.compileExpression((OgnlContext) Ognl.createDefaultContext(null), item, expression);
                accessor = node.getAccessor();
            } finally {
                current.setContextClassLoader(contextCL);
            }
            if (accessor != null) {
                expressions.put(expression, accessor);
            } else {
                logger.warn("Unable to compile expression for {} and {}", clazz, expression);
            }
            time = System.nanoTime() - time;
            logger.info("Expression compilation for {} took {}", expression, time / 1000000L);
        }

        return accessor;
    }

    private Date getDate(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Date) {
            return ((Date) value);
        } else {
            DateMathParser parser = new DateMathParser(DateFieldMapper.Defaults.DATE_TIME_FORMATTER,
                    TimeUnit.MILLISECONDS);
            try {
                return new Date(parser.parse(value.toString(), new Callable<Long>() {
                    @Override
                    public Long call() throws Exception {
                        return System.currentTimeMillis();
                    }
                }));
            } catch (ElasticsearchParseException e) {
                logger.warn("unable to parse date " + value.toString(), e);
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private List<Object> getValueSet(Object expectedValue) {
        if (expectedValue instanceof List) {
            return (List<Object>) expectedValue;
        } else if (expectedValue instanceof Collection) {
            return new ArrayList<Object>((Collection<?>) expectedValue);
        } else {
            return Collections.singletonList(expectedValue);
        }
    }
}