This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push: new a641dc43 JEXL-376: ensure a class is exported before adding it to ClassMap; - added ClassTool utility class to capture Java9+ 'backport' of essential methods; a641dc43 is described below commit a641dc4325a4526c537bf142e2983ef3c3b9ea84 Author: henrib <hen...@apache.org> AuthorDate: Sun Jul 24 12:43:30 2022 +0200 JEXL-376: ensure a class is exported before adding it to ClassMap; - added ClassTool utility class to capture Java9+ 'backport' of essential methods; --- .../jexl3/internal/introspection/ClassMap.java | 12 +- .../jexl3/internal/introspection/ClassTool.java | 131 +++++++++++++++++++++ .../jexl3/internal/introspection/Permissions.java | 70 +---------- .../jexl3/internal/introspection/Uberspect.java | 2 +- .../java/org/apache/commons/jexl3/ScriptTest.java | 100 ++++++++++++++-- .../internal/introspection/PermissionsTest.java | 12 +- src/test/scripts/httpPost.jexl | 53 +++++++++ 7 files changed, 286 insertions(+), 94 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java index 2be0b0f4..ba68aaf0 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java @@ -110,7 +110,7 @@ final class ClassMap { } @Override public Set<Entry<MethodKey, Method>> entrySet() { - return null; + return Collections.emptySet(); } @Override public Method get(Object name) { return CACHE_MISS; @@ -221,11 +221,7 @@ final class ClassMap { if (methodList != null) { cacheEntry = methodKey.getMostSpecificMethod(methodList); } - if (cacheEntry == null) { - byKey.put(methodKey, CACHE_MISS); - } else { - byKey.put(methodKey, cacheEntry); - } + byKey.put(methodKey, cacheEntry == null? CACHE_MISS : cacheEntry); } catch (final MethodKey.AmbiguousException ae) { // that's a miss :-) byKey.put(methodKey, CACHE_MISS); @@ -248,7 +244,7 @@ final class ClassMap { */ private static void create(final ClassMap cache, final JexlPermissions permissions, Class<?> clazz, final Log log) { // - // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start + // Build a list of all elements in the class hierarchy. This one is bottom-first; we start // with the actual declaring class and its interfaces and then move up (superclass etc.) until we // hit java.lang.Object. That is important because it will give us the methods of the declaring class // which might in turn be abstract further up the tree. @@ -256,7 +252,7 @@ final class ClassMap { // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions. // for (Class<?> classToReflect = clazz; classToReflect != null; classToReflect = classToReflect.getSuperclass()) { - if (Modifier.isPublic(classToReflect.getModifiers())) { + if (Modifier.isPublic(classToReflect.getModifiers()) && ClassTool.isExported(classToReflect)) { populateWithClass(cache, permissions, classToReflect, log); } final Class<?>[] interfaces = classToReflect.getInterfaces(); diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java new file mode 100644 index 00000000..fb531b35 --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java @@ -0,0 +1,131 @@ +/* + * 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.commons.jexl3.internal.introspection; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * Utility for Java9+ backport in Java8 of class and module related methods. + */ +class ClassTool { + /** The Class.getModule() method. */ + private static final MethodHandle GET_MODULE; + /** The Class.getPackageName() method. */ + private static final MethodHandle GET_PKGNAME; + /** The Module.isExported(String packageName) method. */ + private static final MethodHandle IS_EXPORTED; + static { + final MethodHandles.Lookup LOOKUP= MethodHandles.lookup(); + MethodHandle getModule = null; + MethodHandle getPackageName = null; + MethodHandle isExported = null; + try { + Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module"); + if (modulec != null) { + getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec)); + if (getModule != null) { + getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class)); + if (getPackageName != null) { + isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class)); + } + } + } + } catch (Exception e) { + // ignore all + } + GET_MODULE = getModule; + GET_PKGNAME = getPackageName; + IS_EXPORTED = isExported; + } + + /** + * Checks whether a class is exported by its module (java 9+). + * The code performs the following sequence through reflection (since the same jar can run + * on a Java8 or Java9+ runtime and the module features does not exist on 8). + * <code> + * Module module = declarator.getModule(); + * return module.isExported(declarator.getPackageName()); + * </code> + * This is required since some classes and methods may not be exported thus not callable through + * reflection. + * @param declarator the class + * @return true if class is exported or no module support exists + */ + static boolean isExported(Class<?> declarator) { + if (IS_EXPORTED != null) { + try { + final Object module = GET_MODULE.invoke(declarator); + if (module != null) { + final String pkgName = (String) GET_PKGNAME.invoke(declarator); + return (Boolean) IS_EXPORTED.invoke(module, pkgName); + } + } catch (Throwable e) { + // ignore + } + } + return true; + } + + /** + * Gets the package name of a class (class.getPackage() may return null). + * @param clz the class + * @return the class package name + */ + static String getPackageName(Class<?> clz) { + String pkgName = ""; + if (clz != null) { + // use native if we can + if (GET_PKGNAME != null) { + try { + return (String) GET_PKGNAME.invoke(clz); + } catch(Throwable xany) { + return ""; + } + } + // remove array + Class<?> clazz = clz; + while(clazz.isArray()) { + clazz = clazz.getComponentType(); + } + // mimic getPackageName() + if (clazz.isPrimitive()) { + return "java.lang"; + } + // remove enclosing + Class<?> walk = clazz.getEnclosingClass(); + while(walk != null) { + clazz = walk; + walk = walk.getEnclosingClass(); + } + Package pkg = clazz.getPackage(); + // pkg may be null for unobvious reasons + if (pkg == null) { + String name = clazz.getName(); + int dot = name.lastIndexOf('.'); + if (dot > 0) { + pkgName = name.substring(0, dot); + } + } else { + pkgName = pkg.getName(); + } + } + return pkgName; + } + +} diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java index 11342571..582bce3e 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java @@ -17,8 +17,6 @@ package org.apache.commons.jexl3.internal.introspection; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -52,21 +50,6 @@ import org.apache.commons.jexl3.introspection.JexlPermissions; * not be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p> */ public class Permissions implements JexlPermissions { - /** - * Java9 introduced Class.getPackageName(), use it if it exists. - */ - private static final MethodHandle GETPKGNAME = getPackageNameHandle(); - static MethodHandle getPackageNameHandle() { - MethodHandle mh; - try { - Method m = Class.class.getMethod("getPackageName"); - mh = MethodHandles.lookup().unreflect(m); - } catch(Exception xm) { - mh = null; - } - return mh; - } - /** * Equivalent of @NoJexl on a class in a package. */ @@ -260,53 +243,6 @@ public class Permissions implements JexlPermissions { return allowed == null? Collections.emptySet() : Collections.unmodifiableSet(allowed); } - - /** - * Gets the package name of a class (class.getPackage() may return null). - * @param clz the class - * @return the class package name - */ - static String getPackageName(Class<?> clz) { - String pkgName = ""; - if (clz != null) { - // use native if we can - if (GETPKGNAME != null) { - try { - return (String) GETPKGNAME.invokeWithArguments(clz); - } catch(Throwable xany) { - return ""; - } - } - // remove array - Class<?> clazz = clz; - while(clazz.isArray()) { - clazz = clazz.getComponentType(); - } - // mimic getPackageName() - if (clazz.isPrimitive()) { - return "java.lang"; - } - // remove enclosing - Class<?> walk = clazz.getEnclosingClass(); - while(walk != null) { - clazz = walk; - walk = walk.getEnclosingClass(); - } - Package pkg = clazz.getPackage(); - // pkg may be null for unobvious reasons - if (pkg == null) { - String name = clazz.getName(); - int dot = name.lastIndexOf('.'); - if (dot > 0) { - pkgName = name.substring(0, dot); - } - } else { - pkgName = pkg.getName(); - } - } - return pkgName; - } - /** * Gets the package constraints. * @param packageName the package name @@ -324,7 +260,7 @@ public class Permissions implements JexlPermissions { * @return the class constraints instance, not-null. */ private NoJexlClass getNoJexl(Class<?> clazz) { - String pkgName = getPackageName(clazz); + String pkgName = ClassTool.getPackageName(clazz); NoJexlPackage njp = getNoJexlPackage(pkgName); if (njp != null) { NoJexlClass njc = njp.getNoJexl(clazz); @@ -341,7 +277,7 @@ public class Permissions implements JexlPermissions { * @return true if allowed, false otherwise */ private boolean wildcardAllow(Class<?> clazz) { - return wildcardAllow(allowed, getPackageName(clazz)); + return wildcardAllow(allowed, ClassTool.getPackageName(clazz)); } /** @@ -393,7 +329,7 @@ public class Permissions implements JexlPermissions { if (nojexl != null) { return true; } - NoJexlPackage njp = packages.get(getPackageName(clazz)); + NoJexlPackage njp = packages.get(ClassTool.getPackageName(clazz)); return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz)); } diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java index 589a84d7..8e8c3e37 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java @@ -69,7 +69,7 @@ public class Uberspect implements JexlUberspect { /** * The map from arithmetic classes to overloaded operator sets. * <p> - * This keeps track of which operator methods are overloaded per JexlArithemtic classes + * This map keeps track of which operator methods are overloaded per JexlArithmetic classes * allowing a fail fast test during interpretation by avoiding seeking a method when there is none. */ private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap; diff --git a/src/test/java/org/apache/commons/jexl3/ScriptTest.java b/src/test/java/org/apache/commons/jexl3/ScriptTest.java index 6c7ed1b3..fb9f0e65 100644 --- a/src/test/java/org/apache/commons/jexl3/ScriptTest.java +++ b/src/test/java/org/apache/commons/jexl3/ScriptTest.java @@ -16,8 +16,14 @@ */ package org.apache.commons.jexl3; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; import java.net.URL; + import org.junit.Assert; import org.junit.Test; @@ -25,10 +31,11 @@ import org.junit.Test; * Tests for JexlScript * @since 1.1 */ -@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"}) +@SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"}) public class ScriptTest extends JexlTestCase { static final String TEST1 = "src/test/scripts/test1.jexl"; static final String TEST_ADD = "src/test/scripts/testAdd.jexl"; + static final String TEST_JSON = "src/test/scripts/httpPost.jexl"; // test class for testScriptUpdatesContext // making this class private static will cause the test to fail. @@ -55,7 +62,7 @@ public class ScriptTest extends JexlTestCase { * Test creating a script from spaces. */ @Test - public void testSpacesScript() throws Exception { + public void testSpacesScript() { final String code = " "; final JexlScript s = JEXL.createScript(code); Assert.assertNotNull(s); @@ -65,37 +72,106 @@ public class ScriptTest extends JexlTestCase { * Test creating a script from a string. */ @Test - public void testSimpleScript() throws Exception { + public void testSimpleScript() { final String code = "while (x < 10) x = x + 1;"; final JexlScript s = JEXL.createScript(code); final JexlContext jc = new MapContext(); - jc.set("x", new Integer(1)); + jc.set("x",1); final Object o = s.execute(jc); - Assert.assertEquals("Result is wrong", new Integer(10), o); + Assert.assertEquals("Result is wrong", 10, o); Assert.assertEquals("getText is wrong", code, s.getSourceText()); } @Test - public void testScriptFromFile() throws Exception { + public void testScriptJsonFromFileJexl() { + final File testScript = new File(TEST_JSON); + final JexlScript s = JEXL.createScript(testScript); + final JexlContext jc = new MapContext(); + jc.set("httpr", new HttpPostRequest()); + Object result = s.execute(jc); + Assert.assertNotNull(result); + Assert.assertEquals("{ \"id\": 101}", result); + } + + @Test + public void testScriptJsonFromFileJava() { + final String testScript ="httpr.execute('https://jsonplaceholder.typicode.com/posts', null)"; + final JexlScript s = JEXL.createScript(testScript); + final JexlContext jc = new MapContext(); + jc.set("httpr", new HttpPostRequest()); + Object result = s.execute(jc); + Assert.assertNotNull(result); + Assert.assertEquals("{ \"id\": 101}", result); + } + + /** + * An object to call from. + */ + public static class HttpPostRequest { + public static String execute(String url, String data) throws IOException { + return httpPostRequest(url, data); + } + } + + /** + * HTTP post. + * @param sURL the url + * @param jsonData some json data + * @return the result + * @throws IOException + */ + private static String httpPostRequest(String sURL, String jsonData) throws IOException { + URL url = new java.net.URL(sURL); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Accept", "application/json"); + // send data + if ( jsonData != null ) { + con.setRequestProperty("Content-Type", "application/json; utf-8"); + con.setDoOutput(true); + + OutputStream outputStream = con.getOutputStream(); + byte[] input = jsonData.getBytes("utf-8"); + outputStream.write(input, 0, input.length); + } + // read response + int responseCode = con.getResponseCode(); + InputStream inputStream = null; + inputStream = con.getInputStream(); + StringBuffer response = new java.lang.StringBuffer(); + if (inputStream != null) { + try (BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream))) { + String inputLine = ""; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + } + } + return response.toString(); + } + + + @Test + public void testScriptFromFile() { final File testScript = new File(TEST1); final JexlScript s = JEXL.createScript(testScript); final JexlContext jc = new MapContext(); jc.set("out", System.out); final Object result = s.execute(jc); Assert.assertNotNull("No result", result); - Assert.assertEquals("Wrong result", new Integer(7), result); + Assert.assertEquals("Wrong result", 7, result); } @Test - public void testArgScriptFromFile() throws Exception { + public void testArgScriptFromFile() { final File testScript = new File(TEST_ADD); final JexlScript s = JEXL.createScript(testScript,"x", "y"); final JexlContext jc = new MapContext(); jc.set("out", System.out); final Object result = s.execute(jc, 13, 29); Assert.assertNotNull("No result", result); - Assert.assertEquals("Wrong result", new Integer(42), result); + Assert.assertEquals("Wrong result", 42, result); } @Test @@ -106,7 +182,7 @@ public class ScriptTest extends JexlTestCase { jc.set("out", System.out); final Object result = s.execute(jc); Assert.assertNotNull("No result", result); - Assert.assertEquals("Wrong result", new Integer(7), result); + Assert.assertEquals("Wrong result", 7, result); } @Test @@ -117,11 +193,11 @@ public class ScriptTest extends JexlTestCase { jc.set("out", System.out); final Object result = s.execute(jc, 13, 29); Assert.assertNotNull("No result", result); - Assert.assertEquals("Wrong result", new Integer(42), result); + Assert.assertEquals("Wrong result", 42, result); } @Test - public void testScriptUpdatesContext() throws Exception { + public void testScriptUpdatesContext() { final String jexlCode = "resultat.setCode('OK')"; final JexlExpression e = JEXL.createExpression(jexlCode); final JexlScript s = JEXL.createScript(jexlCode); diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java index fe8d9c8b..d516601c 100644 --- a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java +++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java @@ -188,19 +188,19 @@ public class PermissionsTest { @Test public void testGetPackageName() { final String PKG = "org.apache.commons.jexl3.internal.introspection"; - String pkg = Permissions.getPackageName(Outer.class); + String pkg = ClassTool.getPackageName(Outer.class); Assert.assertEquals(PKG, pkg); - pkg = Permissions.getPackageName(Outer.Inner.class); + pkg = ClassTool.getPackageName(Outer.Inner.class); Assert.assertEquals(PKG, pkg); Outer[] oo = new Outer[0]; - pkg = Permissions.getPackageName(oo.getClass()); + pkg = ClassTool.getPackageName(oo.getClass()); Assert.assertEquals(PKG, pkg); Outer.Inner[] ii = new Outer.Inner[0]; - pkg = Permissions.getPackageName(ii.getClass()); + pkg = ClassTool.getPackageName(ii.getClass()); Assert.assertEquals(PKG, pkg); - pkg = Permissions.getPackageName(Process.class); + pkg = ClassTool.getPackageName(Process.class); Assert.assertEquals("java.lang", pkg); - pkg = Permissions.getPackageName(Integer.TYPE); + pkg = ClassTool.getPackageName(Integer.TYPE); Assert.assertEquals("java.lang", pkg); } diff --git a/src/test/scripts/httpPost.jexl b/src/test/scripts/httpPost.jexl new file mode 100644 index 00000000..5f106203 --- /dev/null +++ b/src/test/scripts/httpPost.jexl @@ -0,0 +1,53 @@ +/* + * 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. + */ +//------------------------------------------------------------------- +// send a POST Request +//------------------------------------------------------------------- + +var httpPostRequest = (sURL, jsonData) -> { + var url = new("java.net.URL", sURL); + var con = url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Accept", "application/json"); + + // send data + if ( jsonData != null ) { + con.setRequestProperty("Content-Type", "application/json; utf-8"); + con.setDoOutput(true); + + var outputStream = con.getOutputStream(); + var input = jsonData.getBytes("utf-8"); + outputStream.write(input, 0, size(input)); + } + + // read response + var responseCode = con.getResponseCode(); + var inputStream = null; + inputStream = con.getInputStream(); + var response = new("java.lang.StringBuffer"); + if (inputStream != null) { + var in = new("java.io.BufferedReader", new("java.io.InputStreamReader", inputStream)); + var inputLine = ""; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + } + response.toString(); +} + +httpPostRequest("https://jsonplaceholder.typicode.com/posts");