You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/07/24 10:43:42 UTC
[commons-jexl] branch master updated: JEXL-376: ensure a class is exported before adding it to ClassMap; - added ClassTool utility class to capture Java9+ 'backport' of essential methods;
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 <he...@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");