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

dlmarion pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/2.1 by this push:
     new 293e304945 Validate that classloader context is valid when property is 
set (#3678)
293e304945 is described below

commit 293e30494548d6eca247df9b68cef3c613cd8066
Author: Dave Marion <dlmar...@apache.org>
AuthorDate: Thu Aug 10 10:13:54 2023 -0400

    Validate that classloader context is valid when property is set (#3678)
    
    Added method to ContextClassLoaderFactory that is called when
    the table.context.class.loader property is set to validate that the
    context name is valid. Default implementation returns true if the
    implementation returns a non-null ClassLoader and does not throw
    an exception. The new method is also called from
    ClassLoaderUtil.getClassLoader(String) and will return the non-context
    aware Accumulo ClassLoader if isValid returns false.
    
    
    Co-authored-by: Ed Coleman <edcole...@apache.org>
---
 .../accumulo/core/classloader/ClassLoaderUtil.java | 18 +++++++--
 .../DefaultContextClassLoaderFactory.java          | 19 ++++++++++
 .../core/spi/common/ContextClassLoaderFactory.java | 26 +++++++++++++
 .../core/classloader/URLClassLoaderFactory.java    | 44 +++++++++++++++++-----
 .../org/apache/accumulo/server/util/PropUtil.java  | 15 +++++---
 .../accumulo/server/util/SystemPropUtil.java       | 19 ++++++----
 .../classloader/vfs/AccumuloVFSClassLoader.java    |  2 +-
 .../start/classloader/vfs/ContextManager.java      | 10 ++++-
 .../apache/accumulo/test/shell/ShellServerIT.java  | 25 ++++++++++++
 9 files changed, 152 insertions(+), 26 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java 
b/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
index 3a5d0ebda3..8fc7a85cc3 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
@@ -75,13 +75,25 @@ public class ClassLoaderUtil {
 
   @SuppressWarnings("deprecation")
   public static ClassLoader getClassLoader(String context) {
-    if (context != null && !context.isEmpty()) {
-      return FACTORY.getClassLoader(context);
-    } else {
+    try {
+      if (FACTORY.isValid(context)) {
+        return FACTORY.getClassLoader(context);
+      } else {
+        return 
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getClassLoader();
+      }
+    } catch (RuntimeException e) {
       return 
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getClassLoader();
     }
   }
 
+  public static boolean isValidContext(String context) {
+    try {
+      return FACTORY.isValid(context);
+    } catch (RuntimeException e) {
+      return false;
+    }
+  }
+
   public static <U> Class<? extends U> loadClass(String context, String 
className,
       Class<U> extension) throws ClassNotFoundException {
     return getClassLoader(context).loadClass(className).asSubclass(extension);
diff --git 
a/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
 
b/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
index 984e86d0b8..695aa3e473 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
@@ -20,6 +20,7 @@ package org.apache.accumulo.core.classloader;
 
 import static java.util.concurrent.TimeUnit.MINUTES;
 
+import java.io.IOException;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
@@ -95,8 +96,26 @@ public class DefaultContextClassLoaderFactory implements 
ContextClassLoaderFacto
   @SuppressWarnings("deprecation")
   @Override
   public ClassLoader getClassLoader(String contextName) {
+    if (contextName == null || contextName.isEmpty()) {
+      throw new IllegalArgumentException("contextName must have a value");
+    }
     return org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader
         .getContextClassLoader(contextName);
   }
 
+  @SuppressWarnings("deprecation")
+  @Override
+  public boolean isValid(String contextName) {
+    if (contextName == null || contextName.isEmpty()) {
+      return false;
+    }
+    try {
+      return 
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getContextManager()
+          .isKnownContext(contextName);
+    } catch (IOException e) {
+      LOG.error("Error checking that context is valid, context: " + 
contextName, e);
+      return false;
+    }
+  }
+
 }
diff --git 
a/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
 
b/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
index 7f423f3ead..c713653670 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.core.spi.common;
 
+import org.slf4j.LoggerFactory;
+
 /**
  * The ContextClassLoaderFactory provides a mechanism for various Accumulo 
components to use a
  * custom ClassLoader for specific contexts, such as loading table iterators. 
This factory is
@@ -65,4 +67,28 @@ public interface ContextClassLoaderFactory {
    * @return the class loader for the given contextName
    */
   ClassLoader getClassLoader(String contextName);
+
+  /**
+   * Validate that the contextName is supported by the 
ContextClassLoaderFactory implementation
+   *
+   * @param contextName the name of the context that represents a class loader 
that is managed by
+   *        this factory (can be null)
+   * @return true if valid, false otherwise
+   * @since 2.1.2
+   */
+  default boolean isValid(String contextName) {
+    try {
+      ClassLoader cl = getClassLoader(contextName);
+      if (cl == null) {
+        LoggerFactory.getLogger(ContextClassLoaderFactory.class)
+            .info("Null ClassLoader returned for context: {}", contextName);
+        return false;
+      }
+      return true;
+    } catch (RuntimeException e) {
+      LoggerFactory.getLogger(ContextClassLoaderFactory.class).info(
+          "Error thrown getting ClassLoader for context: {}, msg: {}", 
contextName, e.getMessage());
+      return false;
+    }
+  }
 }
diff --git 
a/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
 
b/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
index c97a7d231e..07ebfc5dfe 100644
--- 
a/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
+++ 
b/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
@@ -21,26 +21,52 @@ package org.apache.accumulo.core.classloader;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.stream.Stream;
 
 import org.apache.accumulo.core.spi.common.ContextClassLoaderFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 // test implementation
 public class URLClassLoaderFactory implements ContextClassLoaderFactory {
 
+  private static final Logger LOG = 
LoggerFactory.getLogger(URLClassLoaderFactory.class);
   private static final String COMMA = ",";
 
+  private URL[] parseURLsFromContextName(String contextName) throws 
MalformedURLException {
+    String[] parts = contextName.split(COMMA);
+    URL[] urls = new URL[parts.length];
+    for (int i = 0; i < parts.length; i++) {
+      urls[i] = new URL(parts[i]);
+    }
+    return urls;
+  }
+
   @Override
   public ClassLoader getClassLoader(String contextName) {
+    if (contextName == null || contextName.isEmpty()) {
+      throw new IllegalArgumentException("contextName must have a value");
+    }
     // The context name is the classpath.
-    URL[] urls = Stream.of(contextName.split(COMMA)).map(p -> {
-      try {
-        return new URL(p);
-      } catch (MalformedURLException e) {
-        throw new IllegalArgumentException("Error creating URL from classpath 
segment: " + p, e);
-      }
-    }).toArray(URL[]::new);
-    return URLClassLoader.newInstance(urls);
+    try {
+      URL[] urls = parseURLsFromContextName(contextName);
+      return URLClassLoader.newInstance(urls);
+    } catch (MalformedURLException e) {
+      throw new IllegalArgumentException("Error creating URL from contextName: 
" + contextName, e);
+    }
+  }
+
+  @Override
+  public boolean isValid(String contextName) {
+    if (contextName == null || contextName.isEmpty()) {
+      return false;
+    }
+    try {
+      parseURLsFromContextName(contextName);
+      return true;
+    } catch (MalformedURLException e) {
+      LOG.error("Error creating URL from contextName: " + contextName, e);
+      return false;
+    }
   }
 
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
index ae20ac67c4..85e03ea1c2 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
@@ -21,6 +21,7 @@ package org.apache.accumulo.server.util;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.accumulo.core.classloader.ClassLoaderUtil;
 import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.conf.store.PropStoreKey;
@@ -38,7 +39,7 @@ public final class PropUtil {
    */
   public static void setProperties(final ServerContext context, final 
PropStoreKey<?> propStoreKey,
       final Map<String,String> properties) throws IllegalArgumentException {
-    PropUtil.validateProperties(context, propStoreKey, properties);
+    PropUtil.validateProperties(propStoreKey, properties);
     context.getPropStore().putAll(propStoreKey, properties);
   }
 
@@ -50,13 +51,12 @@ public final class PropUtil {
   public static void replaceProperties(final ServerContext context,
       final PropStoreKey<?> propStoreKey, final long version, final 
Map<String,String> properties)
       throws IllegalArgumentException {
-    PropUtil.validateProperties(context, propStoreKey, properties);
+    PropUtil.validateProperties(propStoreKey, properties);
     context.getPropStore().replaceAll(propStoreKey, version, properties);
   }
 
-  protected static void validateProperties(final ServerContext context,
-      final PropStoreKey<?> propStoreKey, final Map<String,String> properties)
-      throws IllegalArgumentException {
+  protected static void validateProperties(final PropStoreKey<?> propStoreKey,
+      final Map<String,String> properties) throws IllegalArgumentException {
     for (Map.Entry<String,String> prop : properties.entrySet()) {
       if (!Property.isValidProperty(prop.getKey(), prop.getValue())) {
         String exceptionMessage = "Invalid property for : ";
@@ -65,6 +65,11 @@ public final class PropUtil {
         }
         throw new IllegalArgumentException(exceptionMessage + propStoreKey + " 
name: "
             + prop.getKey() + ", value: " + prop.getValue());
+      } else if 
(prop.getKey().equals(Property.TABLE_CLASSLOADER_CONTEXT.getKey())
+          && 
!Property.TABLE_CLASSLOADER_CONTEXT.getDefaultValue().equals(prop.getValue())
+          && !ClassLoaderUtil.isValidContext(prop.getValue())) {
+        throw new IllegalArgumentException(
+            "Unable to resolve classloader for context: " + prop.getValue());
       }
     }
   }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
index 219a6cee03..0bb2e8a8e3 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
@@ -36,17 +36,19 @@ public class SystemPropUtil {
 
   public static void setSystemProperty(ServerContext context, String property, 
String value)
       throws IllegalArgumentException {
-    context.getPropStore().putAll(SystemPropKey.of(context),
-        Map.of(validateSystemProperty(property, value), value));
+    final SystemPropKey key = SystemPropKey.of(context);
+    context.getPropStore().putAll(key, Map.of(validateSystemProperty(key, 
property, value), value));
   }
 
   public static void modifyProperties(ServerContext context, long version,
       Map<String,String> properties) throws IllegalArgumentException {
+    final SystemPropKey key = SystemPropKey.of(context);
     final Map<String,
-        String> checkedProperties = properties.entrySet().stream().collect(
-            Collectors.toMap(entry -> validateSystemProperty(entry.getKey(), 
entry.getValue()),
+        String> checkedProperties = properties.entrySet().stream()
+            .collect(Collectors.toMap(
+                entry -> validateSystemProperty(key, entry.getKey(), 
entry.getValue()),
                 Map.Entry::getValue));
-    context.getPropStore().replaceAll(SystemPropKey.of(context), version, 
checkedProperties);
+    context.getPropStore().replaceAll(key, version, checkedProperties);
   }
 
   public static void removeSystemProperty(ServerContext context, String 
property) {
@@ -64,8 +66,8 @@ public class SystemPropUtil {
     context.getPropStore().removeProperties(SystemPropKey.of(context), 
List.of(property));
   }
 
-  private static String validateSystemProperty(String property, final String 
value)
-      throws IllegalArgumentException {
+  private static String validateSystemProperty(SystemPropKey key, String 
property,
+      final String value) throws IllegalArgumentException {
     // Retrieve the replacement name for this property, if there is one.
     // Do this before we check if the name is a valid zookeeper name.
     final var original = property;
@@ -85,6 +87,9 @@ public class SystemPropUtil {
       log.trace("Encountered error setting zookeeper property", iae);
       throw iae;
     }
+    if (Property.isValidTablePropertyKey(property)) {
+      PropUtil.validateProperties(key, Map.of(property, value));
+    }
 
     // Find the property taking prefix into account
     Property foundProp = null;
diff --git 
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
 
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
index 55aa86c554..d7626025e7 100644
--- 
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
+++ 
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
@@ -424,7 +424,7 @@ public class AccumuloVFSClassLoader {
     }
   }
 
-  private static synchronized ContextManager getContextManager() throws 
IOException {
+  public static synchronized ContextManager getContextManager() throws 
IOException {
     if (contextManager == null) {
       getClassLoader();
       contextManager = new ContextManager(generateVfs(), 
AccumuloVFSClassLoader::getClassLoader);
diff --git 
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
 
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
index c57c38c4c0..d571dba6c6 100644
--- 
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
+++ 
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
@@ -31,7 +31,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Deprecated
-class ContextManager {
+public class ContextManager {
 
   private static final Logger log = 
LoggerFactory.getLogger(ContextManager.class);
 
@@ -158,6 +158,14 @@ class ContextManager {
     this.config = config;
   }
 
+  public boolean isKnownContext(String contextName) {
+    ContextConfig cconfig = config.getContextConfig(contextName);
+    if (cconfig == null) {
+      return false;
+    }
+    return true;
+  }
+
   public ClassLoader getClassLoader(String contextName) throws 
FileSystemException {
 
     ContextConfig cconfig = config.getContextConfig(contextName);
diff --git 
a/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java 
b/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
index fae2e6531f..1122b13338 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
@@ -50,6 +50,7 @@ import java.util.regex.Pattern;
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.IteratorSetting;
 import org.apache.accumulo.core.client.Scanner;
 import org.apache.accumulo.core.client.sample.RowColumnSampler;
@@ -2257,4 +2258,28 @@ public class ShellServerIT extends SharedMiniClusterBase 
{
       System.setProperty("accumulo.properties", orgProps);
     }
   }
+
+  @Test
+  public void failOnInvalidClassloaderContestTest() throws Exception {
+
+    final String[] names = getUniqueNames(3);
+    final String table1 = names[0];
+    final String namespace1 = names[1];
+    final String table2 = namespace1 + "." + names[2];
+
+    ts.exec("createtable " + table1, true);
+    ts.exec("createnamespace " + namespace1, true);
+    ts.exec("createtable " + table2, true);
+
+    ts.exec("config -s table.class.loader.context=invalid", false,
+        AccumuloException.class.getName(), true);
+    ts.exec("config -s table.class.loader.context=invalid -ns " + namespace1, 
false,
+        AccumuloException.class.getName(), true);
+    ts.exec("config -s table.class.loader.context=invalid -t " + table1, false,
+        AccumuloException.class.getName(), true);
+    ts.exec("config -s table.class.loader.context=invalid -t " + table2, false,
+        AccumuloException.class.getName(), true);
+
+  }
+
 }

Reply via email to