/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2007-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.threshd; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; import org.opennms.core.utils.LogUtils; import org.opennms.netmgt.config.threshd.Expression; /** * * @author <a href="mailto:jeffg@opennms.org">Jeff Gehlbach</a> * @author <a href="mailto:cmiskell@opennms.org">Craig Miskell</a> */ public class ExpressionConfigWrapper extends BaseThresholdDefConfigWrapper { /** * This class is used to sniff all of the variable names that a script tries * to use out of the ScriptContext during a call to eval(). This will allow * us to construct a list of required parameters for the script expression. */ private static class BindingsSniffer extends HashMap<String,Object> implements JexlContext { /** * */ private static final long serialVersionUID = 5595028572061424206L; private final Set<String> m_sniffedKeys = new HashSet<String>(); private final String[] ignoreTheseKeys = new String[] { "math" }; public Object get(String key) { LogUtils.tracef(this, "Bindings.get(%s)", key); m_sniffedKeys.add(((String)key).intern()); return super.get(key); } public boolean has(String key) { LogUtils.tracef(this, "Bindings.containsKey(%s)", key); m_sniffedKeys.add(((String)key).intern()); return super.containsKey(key); } public void set(String key, Object value) { LogUtils.tracef(this, "Bindings.set(%s, %s)", key, value.toString()); m_sniffedKeys.add(((String)key).intern()); super.put(key, value); } public Set<String> getSniffedKeys() { LogUtils.tracef(this, "Bindings.getSniffedKeys(%s)"); m_sniffedKeys.removeAll(Arrays.asList(ignoreTheseKeys)); return Collections.unmodifiableSet(m_sniffedKeys); } } private final Expression m_expression; private final Collection<String> m_datasources; public ExpressionConfigWrapper(Expression expression) throws ThresholdExpressionException { super(expression); m_expression = expression; // Fetch an instance of the JEXL script engine JexlEngine expressionParser = new JexlEngine(); BindingsSniffer sniffer = new BindingsSniffer(); sniffer.put("math", new MathBinding()); sniffer.put("datasources", new HashMap<String,Double>()); // To workaround NMS-5019 // Test parsing of the expression and collect the variable names by using // a Bindings instance that sniffs all of the variable names try { expressionParser.createExpression(m_expression.getExpression()).evaluate(sniffer); } catch (Throwable e) { throw new ThresholdExpressionException("Could not parse threshold expression:" + e.getMessage(), e); } m_datasources = sniffer.getSniffedKeys(); } @Override public String getDatasourceExpression() { return m_expression.getExpression(); } @Override public Collection<String> getRequiredDatasources() { return m_datasources; } /** * This class provides an instance that gives access to the {@link java.lang.Math} functions. * You can access this variable in your expressions by using the <code>math</code> variable * (ie. <code>math.abs(-1)</code>). */ public static class MathBinding { public double abs(double a) { return Math.abs(a); } public float abs(float a) { return Math.abs(a); } public int abs(int a) { return Math.abs(a); } public long abs(long a) { return Math.abs(a); } public double acos(double a) { return Math.acos(a); } public double asin(double a) { return Math.asin(a); } public double atan(double a) { return Math.atan(a); } public double atan2(double a, double b) { return Math.atan2(a, b); } public double cbrt(double a) { return Math.cbrt(a); } public double ceil(double a) { return Math.ceil(a); } public double cos(double a) { return Math.cos(a); } public double cosh(double a) { return Math.cosh(a); } public double exp(double a) { return Math.exp(a); } public double expm1(double a) { return Math.expm1(a); } public double floor(double a) { return Math.floor(a); } public double hypot(double a, double b) { return Math.hypot(a, b); } public double IEEEremainder(double a, double b) { return Math.IEEEremainder(a, b); } public double log(double a) { return Math.log(a); } public double log10(double a) { return Math.log10(a); } public double log1p(double a) { return Math.log1p(a); } public double max(double a, double b) { return Math.max(a, b); } public float max(float a, float b) { return Math.max(a, b); } public int max(int a, int b) { return Math.max(a, b); } public long max(long a, long b) { return Math.max(a, b); } public double min(double a, double b) { return Math.min(a, b); } public float min(float a, float b) { return Math.min(a, b); } public int min(int a, int b) { return Math.min(a, b); } public long min(long a, long b) { return Math.min(a, b); } public double pow(double a, double b) { return Math.pow(a, b); } public double random() { return Math.random(); } public double rint(double a) { return Math.rint(a); } public long round(double a) { return Math.round(a); } public int round(float a) { return Math.round(a); } public double signum(double a) { return Math.signum(a); } public float signum(float a) { return Math.signum(a); } public double sin(double a) { return Math.sin(a); } public double sinh(double a) { return Math.sinh(a); } public double sqrt(double a) { return Math.sqrt(a); } public double tan(double a) { return Math.tan(a); } public double tanh(double a) { return Math.tanh(a); } public double toDegrees(double a) { return Math.toDegrees(a); } public double toRadians(double a) { return Math.toRadians(a); } public double ulp(double a) { return Math.ulp(a); } public float ulp(float a) { return Math.ulp(a); } } @Override public double evaluate(Map<String, Double> values) throws ThresholdExpressionException { // Add all of the variable values to the script context BindingsSniffer context = new BindingsSniffer(); context.putAll(values); context.set("datasources", new HashMap<String, Double>(values)); // To workaround NMS-5019 context.put("math", new MathBinding()); double result = Double.NaN; try { // Fetch an instance of the JEXL script engine to evaluate the script expression Object resultObject = new JexlEngine().createExpression(m_expression.getExpression()).evaluate(context); result = Double.parseDouble(resultObject.toString()); } catch (Throwable e) { throw new ThresholdExpressionException("Error while evaluating expression "+m_expression.getExpression()+": " + e.getMessage(), e); } return result; } }