This is an automated email from the ASF dual-hosted git repository.

zabetak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite-avatica.git


The following commit(s) were added to refs/heads/main by this push:
     new 9e13fa95d [CALCITE-7610] Improve type checking when instantiating 
plugins
9e13fa95d is described below

commit 9e13fa95d6b86f669dfaa8801c5faea6911870c0
Author: Stamatis Zampetakis <[email protected]>
AuthorDate: Thu Jun 18 16:55:15 2026 +0200

    [CALCITE-7610] Improve type checking when instantiating plugins
---
 .../org/apache/calcite/avatica/AvaticaUtils.java   | 43 +++++++++++++++----
 .../main/java/org/apache/calcite/avatica/Meta.java |  2 +-
 .../remote/AvaticaHttpClientFactoryImpl.java       |  3 +-
 .../avatica/remote/BearerTokenProviderFactory.java |  3 +-
 .../apache/calcite/avatica/AuthorNoInitPojo.java   | 26 ++++++++++++
 .../org/apache/calcite/avatica/AuthorPojo.java     | 24 +++++++++++
 .../calcite/avatica/CursorFactoryProtoTest.java    | 45 ++++++++++++++++++++
 .../calcite/avatica/InvalidStaticInitializer.java  | 34 +++++++++++++++
 .../InvalidStaticInitializerWithInstanceField.java | 30 ++++++++++++++
 .../InvalidStaticInitializerWithStaticField.java   | 30 ++++++++++++++
 .../remote/AvaticaHttpClientFactoryTest.java       |  8 ++--
 .../remote/BearerTokenProviderFactoryTest.java     | 12 +++---
 .../calcite/avatica/test/AvaticaUtilsTest.java     | 48 ++++++++++++++++------
 13 files changed, 276 insertions(+), 32 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java 
b/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
index ef27e8269..820fb1613 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
@@ -25,6 +25,8 @@
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.sql.PreparedStatement;
@@ -229,10 +231,13 @@ public static <T> T instantiatePlugin(Class<T> 
pluginClass,
         int i = className.indexOf('#');
         left = className.substring(0, i);
         right = className.substring(i + 1);
-        //noinspection unchecked
-        final Class<T> clazz = (Class) Class.forName(left);
+        final Class<?> clazz = Class.forName(left, false, 
AvaticaUtils.class.getClassLoader());
         final Field field;
         field = clazz.getField(right);
+        if (!isValidPluginField(field, pluginClass)) {
+          throw new RuntimeException(
+              "Property '" + className + "' not valid for plugin type " + 
pluginClass.getName());
+        }
         final Object fieldValue = field.get(null);
         if (fieldValue instanceof ThreadLocal) {
           value = ((ThreadLocal<?>) fieldValue).get();
@@ -241,12 +246,13 @@ public static <T> T instantiatePlugin(Class<T> 
pluginClass,
         }
         return pluginClass.cast(value);
       }
-      //noinspection unchecked
-      final Class<T> clazz = (Class) Class.forName(className);
+      final Class<?> clazz = Class.forName(className, false, 
AvaticaUtils.class.getClassLoader());
       try {
-        // We assume that if there is an INSTANCE field it is static and
-        // has the right type.
         final Field field = clazz.getField("INSTANCE");
+        if (!isValidPluginField(field, pluginClass)) {
+          throw new RuntimeException(
+              "Property '" + className + "' not valid for plugin type " + 
pluginClass.getName());
+        }
         value = field.get(null);
         return pluginClass.cast(value);
       } catch (NoSuchFieldException e) {
@@ -256,7 +262,7 @@ public static <T> T instantiatePlugin(Class<T> pluginClass,
         throw new RuntimeException("Property '" + className
             + "' not valid for plugin type " + pluginClass.getName());
       }
-      return clazz.getConstructor().newInstance();
+      return pluginClass.cast(clazz.getConstructor().newInstance());
     } catch (ClassNotFoundException e) {
       throw new RuntimeException("Property '" + className
           + "' not valid as '" + className + "' not found in the classpath", 
e);
@@ -280,6 +286,29 @@ public static <T> T instantiatePlugin(Class<T> pluginClass,
     }
   }
 
+  /**
+   * Returns whether the specified field is valid for the instantiation of a 
plugin.
+   * <p> The field is valid if it can be assigned directly to the expected 
{@code clazz} type or,
+   * it is a {@link ThreadLocal} with a parameterized type that can be 
assigned {@code clazz}.
+   * @param field the field to check for validity
+   * @param clazz the expected type after the instantiation of the plugin
+   * @return whether the specified field is valid for the instantiation of a 
plugin.
+   */
+  private static boolean isValidPluginField(Field field, Class<?> clazz) {
+    if (clazz.isAssignableFrom(field.getType())) {
+      return true;
+    }
+    if (ThreadLocal.class.isAssignableFrom(field.getType())) {
+      Type genericType = field.getGenericType();
+      if (genericType instanceof ParameterizedType) {
+        Type[] types = ((ParameterizedType) 
genericType).getActualTypeArguments();
+        return types.length == 1 && types[0] instanceof Class && 
clazz.isAssignableFrom(
+            (Class<?>) types[0]);
+      }
+    }
+    return false;
+  }
+
   /** Reads the contents of an input stream and returns as a string. */
   public static String readFully(InputStream inputStream) throws IOException {
     return readFully(inputStream, new UnsynchronizedBuffer(1024));
diff --git a/core/src/main/java/org/apache/calcite/avatica/Meta.java 
b/core/src/main/java/org/apache/calcite/avatica/Meta.java
index a4f45c6bc..92dec1f18 100644
--- a/core/src/main/java/org/apache/calcite/avatica/Meta.java
+++ b/core/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -722,7 +722,7 @@ public static CursorFactory fromProto(Common.CursorFactory 
proto) {
 
       if (proto.hasField(CLASS_NAME_DESCRIPTOR)) {
         try {
-          clz = Class.forName(proto.getClassName());
+          clz = Class.forName(proto.getClassName(), false, 
CursorFactory.class.getClassLoader());
         } catch (ClassNotFoundException e) {
           throw new RuntimeException(e);
         }
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
index 445848979..a4ee4a231 100644
--- 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
+++ 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
@@ -162,7 +162,8 @@ private AvaticaHttpClient instantiateClient(String 
className, URL url) {
     try {
       // Ensure that the given class is actually a subclass of 
AvaticaHttpClient
       Class<? extends AvaticaHttpClient> clz =
-          Class.forName(className).asSubclass(AvaticaHttpClient.class);
+          Class.forName(className, false, 
AvaticaHttpClientFactoryImpl.class.getClassLoader())
+              .asSubclass(AvaticaHttpClient.class);
       Constructor<? extends AvaticaHttpClient> constructor = 
clz.getConstructor(URL.class);
       client = constructor.newInstance(Objects.requireNonNull(url));
     } catch (Exception e) {
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactory.java
 
b/core/src/main/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactory.java
index f6dda63b7..c639b1a12 100644
--- 
a/core/src/main/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactory.java
+++ 
b/core/src/main/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactory.java
@@ -44,7 +44,8 @@ private static BearerTokenProvider 
instantiateTokenProvider(String className) {
 
     try {
       Class<? extends BearerTokenProvider> clz =
-              Class.forName(className).asSubclass(BearerTokenProvider.class);
+          Class.forName(className, false, 
BearerTokenProviderFactory.class.getClassLoader())
+              .asSubclass(BearerTokenProvider.class);
       Constructor<? extends BearerTokenProvider> constructor = 
clz.getConstructor();
       tokenProvider = constructor.newInstance();
     } catch (Exception e) {
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/AuthorNoInitPojo.java 
b/core/src/test/java/org/apache/calcite/avatica/AuthorNoInitPojo.java
new file mode 100644
index 000000000..8fdeebda4
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/avatica/AuthorNoInitPojo.java
@@ -0,0 +1,26 @@
+/*
+ * 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.calcite.avatica;
+
+@SuppressWarnings("unused")
+public class AuthorNoInitPojo extends InvalidStaticInitializer {
+
+  public long id;
+  public String fname;
+  public String lname;
+  public int age;
+}
diff --git a/core/src/test/java/org/apache/calcite/avatica/AuthorPojo.java 
b/core/src/test/java/org/apache/calcite/avatica/AuthorPojo.java
new file mode 100644
index 000000000..c7ced7c78
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/avatica/AuthorPojo.java
@@ -0,0 +1,24 @@
+/*
+ * 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.calcite.avatica;
+
+public class AuthorPojo {
+  public long id;
+  public String fname;
+  public String lname;
+  public int age;
+}
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/CursorFactoryProtoTest.java 
b/core/src/test/java/org/apache/calcite/avatica/CursorFactoryProtoTest.java
new file mode 100644
index 000000000..8c3d82cbc
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/avatica/CursorFactoryProtoTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.calcite.avatica;
+
+import org.apache.calcite.avatica.proto.Common;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Unit tests for {@link org.apache.calcite.avatica.Meta.CursorFactory} proto 
conversions.
+ */
+public class CursorFactoryProtoTest {
+
+  @Test
+  public void testFromProtoAvoidsClassInitialization() {
+    Meta.CursorFactory baseFactory = 
Meta.CursorFactory.create(Meta.Style.RECORD, AuthorPojo.class,
+        Arrays.asList("id", "fname", "lname", "age"));
+    Common.CursorFactory.Builder builder = Common.CursorFactory.newBuilder();
+    builder.mergeFrom(baseFactory.toProto());
+    builder.setClassName("org.apache.calcite.avatica.AuthorNoInitPojo");
+    Common.CursorFactory newProto = builder.build();
+    Meta.CursorFactory newFactory = Meta.CursorFactory.fromProto(newProto);
+    assertNotNull(newFactory);
+    assertEquals(baseFactory.fieldNames, newFactory.fieldNames);
+  }
+}
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializer.java 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializer.java
new file mode 100644
index 000000000..787f2ef8d
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.calcite.avatica;
+
+/**
+ * An invalid class that always fail if initialized. The class may be 
sub-classed to cover
+ * test cases where a static initializer is not allowed to be triggered. All 
the classes in the
+ * hierarchy are called by reflection thus appear as unused.
+ */
+@SuppressWarnings("unused")
+public class InvalidStaticInitializer {
+
+  static {
+    throwError();
+  }
+
+  private static void throwError() {
+    throw new AssertionError("Static initializer must not be triggered");
+  }
+}
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithInstanceField.java
 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithInstanceField.java
new file mode 100644
index 000000000..6350dce75
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithInstanceField.java
@@ -0,0 +1,30 @@
+/*
+ * 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.calcite.avatica;
+
+/**
+ * A class with an invalid initializer and a static INSTANCE field. Certain 
reflection utils,
+ * rely on the presence of a static INSTANCE field to create an instance of a 
class.
+ */
+@SuppressWarnings("unused")
+public class InvalidStaticInitializerWithInstanceField extends 
InvalidStaticInitializer {
+  public static final String INSTANCE = "INSTANCE_VALUE";
+
+  private InvalidStaticInitializerWithInstanceField() {
+    throw new AssertionError("Must not be instantiated");
+  }
+}
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithStaticField.java
 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithStaticField.java
new file mode 100644
index 000000000..60f5f7ec6
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/avatica/InvalidStaticInitializerWithStaticField.java
@@ -0,0 +1,30 @@
+/*
+ * 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.calcite.avatica;
+
+/**
+ * A class with an invalid initializer and static field. Certain reflection 
utils,
+ * can create instances by accessing fields with a specified name.
+ */
+@SuppressWarnings("unused")
+public class InvalidStaticInitializerWithStaticField extends 
InvalidStaticInitializer {
+  public static final String FIELD_A = "FIELD_A";
+
+  private InvalidStaticInitializerWithStaticField() {
+    throw new AssertionError("Must not be instantiated");
+  }
+}
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryTest.java
 
b/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryTest.java
index 7dae31401..17b31532f 100644
--- 
a/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryTest.java
+++ 
b/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryTest.java
@@ -26,6 +26,7 @@
 import java.net.URL;
 import java.util.Properties;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -57,16 +58,15 @@ public class AvaticaHttpClientFactoryTest {
         client instanceof AvaticaHttpClientImpl);
   }
 
-  @Test(expected = RuntimeException.class) public void testInvalidHttpClient() 
throws Exception {
+  @Test public void testInvalidHttpClient() throws Exception {
     Properties props = new Properties();
     props.setProperty(BuiltInConnectionProperty.HTTP_CLIENT_IMPL.name(),
-        Properties.class.getName()); // Properties is intentionally *not* a 
valid class
+        "org.apache.calcite.avatica.InvalidStaticInitializer");
     URL url = new URI("http://localhost:8765";).toURL();
     ConnectionConfig config = new ConnectionConfigImpl(props);
     AvaticaHttpClientFactory httpClientFactory = new 
AvaticaHttpClientFactoryImpl();
 
-    // This should throw since the Properties class is invalid
-    httpClientFactory.getClient(url, config, null);
+    assertThrows(RuntimeException.class, () -> 
httpClientFactory.getClient(url, config, null));
   }
 }
 
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactoryTest.java
 
b/core/src/test/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactoryTest.java
index 601181a71..270be4d87 100644
--- 
a/core/src/test/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactoryTest.java
+++ 
b/core/src/test/java/org/apache/calcite/avatica/remote/BearerTokenProviderFactoryTest.java
@@ -39,6 +39,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 public class BearerTokenProviderFactoryTest {
@@ -123,13 +124,14 @@ public void testCustomBearerTokenInvalid() throws 
Exception {
   }
 
 
-  @Test(expected = RuntimeException.class)
-  public void testInvalidBearerToken() throws Exception {
+  @Test
+  public void testInvalidBearerToken() {
     Properties props = new Properties();
-    props.setProperty(BuiltInConnectionProperty.HTTP_CLIENT_IMPL.name(),
-            Properties.class.getName()); // Properties is intentionally *not* 
a valid class
+    props.setProperty(BuiltInConnectionProperty.TOKEN_PROVIDER_CLASS.name(),
+        "org.apache.calcite.avatica.InvalidStaticInitializer");
     ConnectionConfig config = new ConnectionConfigImpl(props);
-    BearerTokenProviderFactory.getBearerTokenProvider(config);
+    assertThrows(RuntimeException.class,
+        () -> BearerTokenProviderFactory.getBearerTokenProvider(config));
   }
 
   public static class TestTokenProvider implements BearerTokenProvider {
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/test/AvaticaUtilsTest.java 
b/core/src/test/java/org/apache/calcite/avatica/test/AvaticaUtilsTest.java
index 355702256..39f0f0bad 100644
--- a/core/src/test/java/org/apache/calcite/avatica/test/AvaticaUtilsTest.java
+++ b/core/src/test/java/org/apache/calcite/avatica/test/AvaticaUtilsTest.java
@@ -44,6 +44,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 /**
@@ -95,8 +96,8 @@ public class AvaticaUtilsTest {
       fail("expected error, got " + s2);
     } catch (Throwable e) {
       assertThat(e.getMessage(),
-          is("Property 'java.math.BigInteger#ONE' not valid as "
-              + "cannot convert java.math.BigInteger to java.lang.String"));
+          is("Property 'java.math.BigInteger#ONE' not valid "
+              + "for plugin type java.lang.String"));
     }
 
     // No default constructor or INSTANCE member
@@ -141,8 +142,7 @@ public class AvaticaUtilsTest {
     } catch (Throwable e) {
       assertThat(e.getMessage(),
           is("Property 'org.apache.calcite.avatica.test.AvaticaUtilsTest"
-              + "#STRING_THREAD_LOCAL' not valid as cannot convert 
java.lang.String "
-              + "to java.lang.Integer"));
+              + "#STRING_THREAD_LOCAL' not valid for plugin type 
java.lang.Integer"));
     } finally {
       STRING_THREAD_LOCAL.remove();
     }
@@ -158,8 +158,7 @@ public class AvaticaUtilsTest {
     } catch (Throwable e) {
       assertThat(e.getMessage(),
           is("Property 'org.apache.calcite.avatica.test.AvaticaUtilsTest"
-              + "#STRING_THREAD_LOCAL' not valid as cannot convert "
-              + "java.lang.String to java.math.BigDecimal[]"));
+              + "#STRING_THREAD_LOCAL' not valid for plugin type 
[Ljava.math.BigDecimal;"));
     } finally {
       STRING_THREAD_LOCAL.remove();
     }
@@ -175,9 +174,8 @@ public class AvaticaUtilsTest {
     } catch (Throwable e) {
       assertThat(e.getMessage(),
           is("Property 'org.apache.calcite.avatica.test.AvaticaUtilsTest"
-              + "#STRING_THREAD_LOCAL' not valid as cannot convert "
-              + "java.lang.String to "
-              + "org.apache.calcite.avatica.test.AvaticaUtilsTest.Weight"));
+              + "#STRING_THREAD_LOCAL' not valid for plugin type "
+              + "org.apache.calcite.avatica.test.AvaticaUtilsTest$Weight"));
     } finally {
       STRING_THREAD_LOCAL.remove();
     }
@@ -192,8 +190,7 @@ public class AvaticaUtilsTest {
     } catch (Throwable e) {
       assertThat(e.getMessage(),
           is("Property 'org.apache.calcite.avatica.test.AvaticaUtilsTest"
-              + "#STRING_THREAD_LOCAL' not valid as cannot convert "
-              + "java.lang.String to float"));
+              + "#STRING_THREAD_LOCAL' not valid for plugin type float"));
     } finally {
       STRING_THREAD_LOCAL.remove();
     }
@@ -208,13 +205,38 @@ public class AvaticaUtilsTest {
     } catch (Throwable e) {
       assertThat(e.getMessage(),
           is("Property 'org.apache.calcite.avatica.test.AvaticaUtilsTest"
-              + "#FLOAT_THREAD_LOCAL' not valid as cannot convert "
-              + "java.lang.Float to float"));
+              + "#FLOAT_THREAD_LOCAL' not valid for plugin type float"));
     } finally {
       FLOAT_THREAD_LOCAL.remove();
     }
   }
 
+  @Test public void testInstantiatePluginViaConstructorWithUnloadedClass() {
+    RuntimeException e = assertThrows(RuntimeException.class,
+        () -> AvaticaUtils.instantiatePlugin(Integer.class,
+            "org.apache.calcite.avatica.InvalidStaticInitializer"));
+    assertThat(e.getMessage(), is("Property 
'org.apache.calcite.avatica.InvalidStaticInitializer' "
+        + "not valid for plugin type java.lang.Integer"));
+  }
+
+  @Test public void testInstantiatePluginViaNamedFieldWithUnloadedClass() {
+    RuntimeException e = assertThrows(RuntimeException.class,
+        () -> AvaticaUtils.instantiatePlugin(Integer.class,
+            
"org.apache.calcite.avatica.InvalidStaticInitializerWithStaticField#FIELD_A"));
+    assertThat(e.getMessage(),
+        is("Property 
'org.apache.calcite.avatica.InvalidStaticInitializerWithStaticField#FIELD_A' "
+            + "not valid for plugin type java.lang.Integer"));
+  }
+
+  @Test public void testInstantiatePluginViaINSTANCEFieldWithUnloadedClass() {
+    RuntimeException e = assertThrows(RuntimeException.class,
+        () -> AvaticaUtils.instantiatePlugin(Integer.class,
+            
"org.apache.calcite.avatica.InvalidStaticInitializerWithInstanceField"));
+    assertThat(e.getMessage(),
+        is("Property 
'org.apache.calcite.avatica.InvalidStaticInitializerWithInstanceField' "
+            + "not valid for plugin type java.lang.Integer"));
+  }
+
   /** Unit test for
    * {@link org.apache.calcite.avatica.AvaticaUtils#unique(java.lang.String)}. 
*/
   @Test public void testUnique() {

Reply via email to