This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-350 in repository https://gitbox.apache.org/repos/asf/commons-bsf.git
commit 03b69f1681f82d0916682b18d74a420a628370a3 Author: Henrib <hbies...@gmail.com> AuthorDate: Wed Apr 16 19:15:21 2025 +0200 - Updated JEXL dependency to JEXL 3.5.0; - added tests; --- pom.xml | 6 +- .../org/apache/bsf/engines/jexl/JEXLEngine.java | 165 +++++++++++++++++---- .../org/apache/bsf/engines/JavascriptTest.java | 26 ++-- src/test/java/org/apache/bsf/engines/JexlTest.java | 52 +++++++ 4 files changed, 206 insertions(+), 43 deletions(-) diff --git a/pom.xml b/pom.xml index df23e99..38ba820 100644 --- a/pom.xml +++ b/pom.xml @@ -201,9 +201,9 @@ <scope>test</scope> </dependency> <dependency> - <groupId>commons-jexl</groupId> - <artifactId>commons-jexl</artifactId> - <version>1.1</version> + <groupId>org.apache.commons</groupId> + <artifactId>commons-jexl3</artifactId> + <version>3.5.0</version> </dependency> <dependency> <groupId>rhino</groupId> diff --git a/src/main/java/org/apache/bsf/engines/jexl/JEXLEngine.java b/src/main/java/org/apache/bsf/engines/jexl/JEXLEngine.java index c9671db..5b07b6c 100644 --- a/src/main/java/org/apache/bsf/engines/jexl/JEXLEngine.java +++ b/src/main/java/org/apache/bsf/engines/jexl/JEXLEngine.java @@ -16,20 +16,32 @@ */ package org.apache.bsf.engines.jexl; +import java.io.BufferedReader; import java.io.File; -import java.lang.reflect.Method; +import java.io.IOException; +import java.io.InputStreamReader; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Map; +import java.util.Objects; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import org.apache.bsf.BSFDeclaredBean; import org.apache.bsf.BSFEngine; import org.apache.bsf.BSFException; import org.apache.bsf.BSFManager; import org.apache.bsf.util.BSFEngineImpl; -import org.apache.commons.jexl.JexlContext; -import org.apache.commons.jexl.JexlHelper; -import org.apache.commons.jexl.Script; -import org.apache.commons.jexl.ScriptFactory; +import org.apache.bsf.util.BSFFunctions; +import org.apache.commons.jexl3.JexlContext; +import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.jexl3.JexlException; +import org.apache.commons.jexl3.JexlExpression; +import org.apache.commons.jexl3.JexlInfo; +import org.apache.commons.jexl3.JexlScript; +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.introspection.JexlPermissions; /** * {@link BSFEngine} for Commons JEXL. Requires Commons JEXL version 1.1 or later. @@ -37,10 +49,47 @@ import org.apache.commons.jexl.ScriptFactory; * @see <a href="http://commons.apache.org/jexl/">Commons JEXL</a> */ public class JEXLEngine extends BSFEngineImpl { - + private static JexlPermissions BSF_PERMISSIONS = JexlPermissions.RESTRICTED; + /** The engine. */ + private JexlEngine engine; + /** The declared bean */ + private Map<String, Object> vars; /** The backing JexlContext for this engine. */ private JexlContext jc; + /** + * Sets the JEXL engine permissions. + * @param permissions the permissions + */ + public static void setPermissions(JexlPermissions permissions) { + BSF_PERMISSIONS = permissions; + } + + public static class BSFContext implements JexlContext { + private final Map<String, Object> map; + + BSFContext(Map<String, Object> vars) { + this.map = vars; + } + + public void clear() { + this.map.clear(); + } + + public Object get(String name) { + return this.map.get(name); + } + + public boolean has(String name) { + return this.map.containsKey(name); + } + + public void set(String name, Object value) { + this.map.put(name, value); + } + } + + /** * Initialize the JEXL engine by creating a JexlContext and populating it with the declared beans. * @@ -51,11 +100,17 @@ public class JEXLEngine extends BSFEngineImpl { */ public void initialize(final BSFManager mgr, final String lang, final Vector declaredBeans) throws BSFException { super.initialize(mgr, lang, declaredBeans); - jc = JexlHelper.createContext(); + vars = new ConcurrentHashMap<>(); + jc = new BSFContext(vars); for (int i = 0; i < declaredBeans.size(); i++) { final BSFDeclaredBean bean = (BSFDeclaredBean) declaredBeans.elementAt(i); - jc.getVars().put(bean.name, bean.bean); + vars.put(bean.name, bean.bean); } + vars.put("java.lang.System.out", java.lang.System.out); + vars.put("java.lang.System.in", java.lang.System.in); + vars.put("java.lang.System.err", java.lang.System.err); + vars.put("bsf", new BSFFunctions(mgr, this)); + engine = new JexlBuilder().cache(32).permissions(BSF_PERMISSIONS).create(); } /** @@ -63,8 +118,9 @@ public class JEXLEngine extends BSFEngineImpl { */ public void terminate() { if (jc != null) { - jc.getVars().clear(); + vars.clear(); jc = null; + engine = null; } } @@ -75,7 +131,7 @@ public class JEXLEngine extends BSFEngineImpl { * @throws BSFException For any exception that occurs while trying to declare the bean. */ public void declareBean(final BSFDeclaredBean bean) throws BSFException { - jc.getVars().put(bean.name, bean.bean); + vars.put(bean.name, bean.bean); } /** @@ -85,7 +141,7 @@ public class JEXLEngine extends BSFEngineImpl { * @throws BSFException For any exception that occurs while trying to undeclare the bean. */ public void undeclareBean(final BSFDeclaredBean bean) throws BSFException { - jc.getVars().remove(bean.name); + vars.remove(bean.name); } /** @@ -101,23 +157,27 @@ public class JEXLEngine extends BSFEngineImpl { if (expr == null) { return null; } + final JexlInfo info = new JexlInfo( + fileName != null ? fileName : expr.toString(), + Math.max(lineNo, 1), + Math.max(colNo, 1)); try { - Script jExpr = null; + JexlExpression jExpr; if (expr instanceof File) { - jExpr = ScriptFactory.createScript((File) expr); + jExpr = engine.createExpression(info, readSource(info, (File) expr)); } else if (expr instanceof URL) { - jExpr = ScriptFactory.createScript((URL) expr); + jExpr = engine.createExpression(info, readSource(info, (URL) expr)); } else { - jExpr = ScriptFactory.createScript((String) expr); + jExpr = engine.createExpression(info, (String) expr); } - return jExpr.execute(jc); + return jExpr.evaluate(jc); } catch (final Exception e) { throw new BSFException(BSFException.REASON_EXECUTION_ERROR, "Exception from Commons JEXL:\n" + e.getMessage(), e); } } /** - * Executes the script as a JEXL {@link Script}. + * Executes the script as a JEXL {@link JexlScript}. * * @param fileName The file name, if it is available. * @param lineNo The line number, if it is available. @@ -129,14 +189,18 @@ public class JEXLEngine extends BSFEngineImpl { if (script == null) { return; } + final JexlInfo info = new JexlInfo( + fileName != null ? fileName : script.toString(), + Math.max(lineNo, 1), + Math.max(colNo, 1)); try { - Script jExpr = null; + JexlScript jExpr; if (script instanceof File) { - jExpr = ScriptFactory.createScript((File) script); + jExpr = engine.createScript(info, readSource(info, (File) script)); } else if (script instanceof URL) { - jExpr = ScriptFactory.createScript((URL) script); + jExpr = engine.createScript(info, readSource(info, (URL) script)); } else { - jExpr = ScriptFactory.createScript((String) script); + jExpr = engine.createScript(info, (String) script); } jExpr.execute(jc); } catch (final Exception e) { @@ -166,17 +230,64 @@ public class JEXLEngine extends BSFEngineImpl { * @return The result of the call. * @throws BSFException For any exception that occurs while making the call. */ - public Object call(final Object object, final String name, final Object[] args) throws BSFException { + public Object call(Object object, final String name, final Object[] args) throws BSFException { try { - final Class[] types = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - types[i] = args[i].getClass(); + if (object == null) { + object = vars.get(name); } - final Method m = object.getClass().getMethod(name, types); - return m.invoke(object, args); + if (object instanceof JexlScript) { + return ((JexlScript) object).execute(jc, args); + } + return engine.invokeMethod(object, name, args); } catch (final Exception e) { throw new BSFException(BSFException.REASON_EXECUTION_ERROR, "Exception from JEXLEngine:\n" + e.getMessage(), e); } } + /** + * Reads a JEXL source from a File. + * + * @param info the script source info + * @param file the script file + * @return the source + */ + protected String readSource(JexlInfo info, final File file) { + Objects.requireNonNull(file, "file"); + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + return toString(reader); + } catch (final IOException xio) { + throw new JexlException(info, "could not read source File", xio); + } + } + + /** + * Reads a JEXL source from an URL. + * + * @param info the script source info + * @param url the script url + * @return the source + */ + protected String readSource(JexlInfo info, final URL url) { + Objects.requireNonNull(url, "url"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + return toString(reader); + } catch (final IOException xio) { + throw new JexlException(info, "could not read source URL", xio); + } + } + /** + * Creates a string from a reader. + * + * @param reader to be read. + * @return the contents of the reader as a String. + * @throws IOException on any error reading the reader. + */ + protected static String toString(final BufferedReader reader) throws IOException { + final StringBuilder buffer = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + buffer.append(line).append('\n'); + } + return buffer.toString(); + } } diff --git a/src/test/java/org/apache/bsf/engines/JavascriptTest.java b/src/test/java/org/apache/bsf/engines/JavascriptTest.java index 374b9e4..a093ad3 100644 --- a/src/test/java/org/apache/bsf/engines/JavascriptTest.java +++ b/src/test/java/org/apache/bsf/engines/JavascriptTest.java @@ -25,7 +25,7 @@ import org.apache.bsf.BSFException; * Test class for the Rhino language engine. */ public class JavascriptTest extends BSFEngineTestCase { - private BSFEngine javascriptEngine; + protected BSFEngine engine; public JavascriptTest(final String name) { super(name); @@ -35,7 +35,7 @@ public class JavascriptTest extends BSFEngineTestCase { super.setUp(); try { - javascriptEngine = bsfManager.loadScriptingEngine("javascript"); + engine = bsfManager.loadScriptingEngine("javascript"); } catch (final Exception e) { fail(failMessage("Failure attempting to load javascript engine", e)); } @@ -43,7 +43,7 @@ public class JavascriptTest extends BSFEngineTestCase { public void testExec() { try { - javascriptEngine.exec("Test.js", 0, 0, "java.lang.System.out.print " + "(\"PASSED\");"); + engine.exec("Test.js", 0, 0, "java.lang.System.out.print " + "(\"PASSED\");"); } catch (final Exception e) { fail(failMessage("exec() test failed", e)); } @@ -55,7 +55,7 @@ public class JavascriptTest extends BSFEngineTestCase { Double retval = null; try { - retval = Double.valueOf((javascriptEngine.eval("Test.js", 0, 0, "1 + 1;").toString())); + retval = Double.valueOf((engine.eval("Test.js", 0, 0, "1 + 1").toString())); } catch (final Exception e) { fail(failMessage("eval() test failed", e)); } @@ -68,8 +68,8 @@ public class JavascriptTest extends BSFEngineTestCase { Double retval = null; try { - javascriptEngine.exec("Test.js", 0, 0, "function addOne (f) {\n return f + 1;\n}"); - retval = Double.valueOf((javascriptEngine.call(null, "addOne", args).toString())); + engine.exec("Test.js", 0, 0, "function addOne (f) {\n return f + 1;\n}"); + retval = Double.valueOf((engine.call(null, "addOne", args).toString())); } catch (final Exception e) { fail(failMessage("call() test failed", e)); } @@ -79,7 +79,7 @@ public class JavascriptTest extends BSFEngineTestCase { public void testIexec() { try { - javascriptEngine.iexec("Test.js", 0, 0, "java.lang.System.out.print " + "(\"PASSED\");"); + engine.iexec("Test.js", 0, 0, "java.lang.System.out.print " + "(\"PASSED\")"); } catch (final Exception e) { fail(failMessage("iexec() test failed", e)); } @@ -91,7 +91,7 @@ public class JavascriptTest extends BSFEngineTestCase { Double retval = null; try { - retval = Double.valueOf((bsfManager.eval("javascript", "Test.js", 0, 0, "1 + 1;")).toString()); + retval = Double.valueOf((bsfManager.eval("javascript", "Test.js", 0, 0, "1 + 1")).toString()); } catch (final Exception e) { fail(failMessage("BSFManager eval() test failed", e)); } @@ -103,7 +103,7 @@ public class JavascriptTest extends BSFEngineTestCase { Object retval = null; try { - retval = javascriptEngine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\");"); + retval = engine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\")"); } catch (final Exception e) { fail(failMessage("Test of BSFManager availability failed", e)); } @@ -117,7 +117,7 @@ public class JavascriptTest extends BSFEngineTestCase { try { bsfManager.registerBean("foo", foo); - bar = (Double) javascriptEngine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\");"); + bar = (Double) engine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\")"); } catch (final Exception e) { fail(failMessage("registerBean() test failed", e)); } @@ -132,7 +132,7 @@ public class JavascriptTest extends BSFEngineTestCase { try { bsfManager.registerBean("foo", foo); bsfManager.unregisterBean("foo"); - bar = (Double) javascriptEngine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\");"); + bar = (Double) engine.eval("Test.js", 0, 0, "bsf.lookupBean(\"foo\")"); } catch (final Exception e) { fail(failMessage("unregisterBean() test failed", e)); } @@ -146,7 +146,7 @@ public class JavascriptTest extends BSFEngineTestCase { try { bsfManager.declareBean("foo", foo, Double.class); - bar = (Double) javascriptEngine.eval("Test.js", 0, 0, "foo + 1;"); + bar = (Double) engine.eval("Test.js", 0, 0, "foo + 1"); } catch (final Exception e) { fail(failMessage("declareBean() test failed", e)); } @@ -161,7 +161,7 @@ public class JavascriptTest extends BSFEngineTestCase { try { bsfManager.declareBean("foo", foo, Double.class); bsfManager.undeclareBean("foo"); - bar = (Double) javascriptEngine.eval("Test.js", 0, 0, "foo + 1"); + bar = (Double) engine.eval("Test.js", 0, 0, "foo + 1"); } catch (final BSFException bsfE) { // Do nothing. This is the expected case. } catch (final Exception e) { diff --git a/src/test/java/org/apache/bsf/engines/JexlTest.java b/src/test/java/org/apache/bsf/engines/JexlTest.java new file mode 100644 index 0000000..bd4636f --- /dev/null +++ b/src/test/java/org/apache/bsf/engines/JexlTest.java @@ -0,0 +1,52 @@ +/* + * 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.bsf.engines; + +import org.apache.bsf.engines.jexl.JEXLEngine; +import org.apache.commons.jexl3.introspection.JexlPermissions; + +public class JexlTest extends JavascriptTest { + public JexlTest(String name) { + super(name); + } + + public void setUp() { + super.setUp(); + try { + JEXLEngine.setPermissions(JexlPermissions.UNRESTRICTED); + engine = bsfManager.loadScriptingEngine("jexl"); + } catch (final Exception e) { + fail(failMessage("Failure attempting to load jexl engine", e)); + } +} + + + public void testCall() { + final Object[] args = { Double.valueOf(1) }; + Double retval = null; + + try { + engine.exec("Test.js", 0, 0, "addOne = (f) -> {\n return f + 1;\n}"); + retval = Double.valueOf((engine.call(null, "addOne", args).toString())); + } catch (final Exception e) { + fail(failMessage("call() test failed", e)); + } + + assertEquals(Double.valueOf(2), retval); + } +}