/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2016 ForgeRock AS. */ package org.opends.server.config; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.el.ELContext; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import org.forgerock.util.Reject; import de.odysseus.el.ExpressionFactoryImpl; import de.odysseus.el.util.RootPropertyResolver; import de.odysseus.el.util.SimpleContext; import de.odysseus.el.util.SimpleResolver; /** * A Unified Expression Language read-only expression. Creating an expression is the equivalent to * compiling it. Once created, an expression can be evaluated with an optional set of bindings. Expressions are * thread safe. * * @param * expected result type */ final class Expression { /** Context used when compiling expressions and evaluating expressions that don't have bindings. */ private static final ELContext EL_CONTEXT = getELContext0(null); /** Factory for compiling expressions. */ private static final ExpressionFactory FACTORY = new ExpressionFactoryImpl(); /** * Compiles the provided expression and evaluates it without any bindings. * * @param * Expected result type * @param expression * The expression to compile. * @param expectedType * The expected result type of the expression. * @return The result of the expression evaluation. * @throws IllegalArgumentException * If the expression could not be compiled, evaluated, or the returned value did not have the expected * type. */ public static T eval(final String expression, final Class expectedType) { return compile(expression, expectedType).eval(); } /** * Compiles the provided expression and evaluates it using the provided bindings. * * @param * Expected result type * @param expression * The expression to compile. * @param expectedType * The expected result type of the expression. * @param bindings * The bindings, which may be empty or {@code null}. * @return The result of the expression evaluation. * @throws IllegalArgumentException * If the expression could not be compiled, evaluated, or the returned value did not have the expected * type. */ public static T eval(final String expression, final Class expectedType, final Map bindings) { return compile(expression, expectedType).eval(bindings); } /** The expected type of this expression. */ private final Class expectedType; /** The compiled EL expression. */ private final ValueExpression valueExpression; /** * Compiles the provided expression string. * * @param * Expected result type * @param expression * The expression to compile. * @param expectedType * The expected result type of the expression. * @return The compiled expression. * @throws IllegalArgumentException * If the expression was not syntactically correct or contained unrecognized functions. */ public static Expression compile(final String expression, final Class expectedType) { Reject.ifNull(expression, "expression must not be null"); Reject.ifNull(expectedType, "expectedType must not be null"); try { final ValueExpression valueExpression = FACTORY.createValueExpression(EL_CONTEXT, expression, expectedType); return new Expression<>(expectedType, valueExpression); } catch (final ELException e) { throw new IllegalArgumentException(e); } } private Expression(final Class expectedType, final ValueExpression valueExpression) { this.expectedType = expectedType; this.valueExpression = valueExpression; } /** * Evaluates this expression without any bindings. * * @return The result of the expression evaluation. * @throws IllegalArgumentException * If the expression could not be evaluated or the returned value did not have the expected type. */ public T eval() { return eval(Collections.emptyMap()); } /** * Evaluates this expression using the provided bindings. * * @param bindings * The bindings, which may be empty or {@code null}. * @return The result of the expression evaluation. * @throws IllegalArgumentException * If the expression could not be evaluated or the returned value did not have the expected type. */ public T eval(final Map bindings) { try { return expectedType.cast(valueExpression.getValue(getELContext(bindings))); } catch (final ELException e) { throw new IllegalArgumentException(e); } } @Override public String toString() { return valueExpression.getExpressionString(); } private static ELContext getELContext(final Map bindings) { if (bindings == null || bindings.isEmpty()) { return EL_CONTEXT; } return getELContext0(bindings); } private static ELContext getELContext0(final Map bindings) { final SimpleResolver resolver = new SimpleResolver(false); final RootPropertyResolver rootPropertyResolver = resolver.getRootPropertyResolver(); rootPropertyResolver.setProperty("env", Collections.unmodifiableMap(System.getenv())); rootPropertyResolver.setProperty("system", Collections.unmodifiableMap(System.getProperties())); if (bindings != null) { for (final Map.Entry binding : bindings.entrySet()) { rootPropertyResolver.setProperty(binding.getKey(), binding.getValue()); } } final SimpleContext context = new SimpleContext(resolver); for (final Map.Entry function : getPublicStaticMethods(Functions.class).entrySet()) { context.setFunction("", function.getKey(), function.getValue()); } return context; } private static Map getPublicStaticMethods(final Class target) { final Map methods = new HashMap<>(); for (final Method method : target.getMethods()) { if (Modifier.isStatic(method.getModifiers())) { method.setAccessible(true); methods.put(method.getName(), method); } } return methods; } }