Verify extension type before initializing reflectively-loaded classes

Cassandra resolves pluggable extensions by class name from configuration, schema, and tooling
inputs. These names were loaded with an initializing Class.forName(name) and type-checked only
afterward, so the named class ran its static initializer before its type was confirmed. After this
change such classes will be loaded without initialization, verified against the expected interface or
base class, and initialized only through normal use after validation.

A shared FBUtilities.classForNameWithoutInitialization helper and typed
instanceOrConstruct/construct overloads apply this to the configurable extension points loaded by
class name: the authentication, authorization, role-management, network and
internode-authenticator backends, the partitioner, audit logger, configuration loader, seed provider,
snitch, abstract types, secondary and custom indexes, compaction strategy, compressor, replication
strategy, SASI analyzers, key and cache providers, query handler, storage and stream hooks, tracing,
the JMX authorization proxy, MBeans, the monotonic clock, nodetool Sjk, triggers, the
sstableloader and stress class options, and diagnostic event classes (loaded without initialization
and checked against DiagnosticEvent, preserving the InvalidClassException contract and the existing
package restriction).

Regression tests confirm that an invalid-type load is rejected without initializing the target
class, and that valid implementations still resolve.

Hadoop client integration and hard-coded JDK and internal class probes are left unchanged.

patch by Jeremiah Jordan; reviewed by Stefan Miklosovic for CASSANDRA-21525
This commit is contained in:
Jeremiah Jordan 2026-06-23 13:49:14 -05:00 committed by Cassandra Build
parent 33c1dbe5b8
commit 26d7b166ba
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
40 changed files with 748 additions and 101 deletions

View File

@ -1,4 +1,5 @@
4.0.21
* Verify extension type before initializing reflectively-loaded classes (CASSANDRA-21525)
* Rename conflicting nodetool import --copy-data short option from -p to -cd (CASSANDRA-20214)
* Fix PasswordObfuscator failing to obfuscate certain passwords (CASSANDRA-21113)
* Fix negative memtable allocator ownership when an update is shadowed by an existing row deletion (CASSANDRA-21469)

View File

@ -95,7 +95,7 @@ public final class AuthConfig
// authenticator
if (conf.internode_authenticator != null)
DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator"));
DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator", IInternodeAuthenticator.class));
// network authorizer
INetworkAuthorizer networkAuthorizer = FBUtilities.newNetworkAuthorizer(conf.network_authorizer);

View File

@ -297,7 +297,7 @@ public class DatabaseDescriptor
String loaderClass = System.getProperty(Config.PROPERTY_PREFIX + "config.loader");
ConfigurationLoader loader = loaderClass == null
? new YamlConfigurationLoader()
: FBUtilities.construct(loaderClass, "configuration loading");
: FBUtilities.construct(loaderClass, "configuration loading", ConfigurationLoader.class);
Config config = loader.loadConfig();
if (!hasLoggedConfig)
@ -1036,8 +1036,9 @@ public class DatabaseDescriptor
}
try
{
Class<?> seedProviderClass = Class.forName(conf.seed_provider.class_name);
seedProvider = (SeedProvider)seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters);
Class<? extends SeedProvider> seedProviderClass =
FBUtilities.classForNameWithoutInitialization(conf.seed_provider.class_name, "seed provider", SeedProvider.class);
seedProvider = seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters);
}
// there are about 5 checked exceptions that could be thrown here.
catch (Exception e)
@ -1243,7 +1244,7 @@ public class DatabaseDescriptor
{
if (!snitchClassName.contains("."))
snitchClassName = "org.apache.cassandra.locator." + snitchClassName;
IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch");
IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch", IEndpointSnitch.class);
return dynamic ? new DynamicEndpointSnitch(snitch) : snitch;
}

View File

@ -53,7 +53,7 @@ public interface StorageHook
String className = System.getProperty("cassandra.storage_hook");
if (className != null)
{
return FBUtilities.construct(className, StorageHook.class.getSimpleName());
return FBUtilities.construct(className, StorageHook.class.getSimpleName(), StorageHook.class);
}
return new StorageHook()

View File

@ -353,8 +353,7 @@ public class TypeParser
private static AbstractType<?> getAbstractType(String compareWith) throws ConfigurationException
{
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith;
Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
try
{
Field field = typeClass.getDeclaredField("instance");
@ -367,10 +366,22 @@ public class TypeParser
}
}
private static AbstractType<?> getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException
private static Class<? extends AbstractType<?>> getAbstractTypeClass(String compareWith) throws ConfigurationException
{
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith;
Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
// Defer class initialization until after confirming this is an AbstractType. The static instance field
// access or getInstance(TypeParser) invocation below performs the initialization for valid types.
@SuppressWarnings("unchecked")
Class<? extends AbstractType<?>> typeClass =
(Class<? extends AbstractType<?>>) FBUtilities.classForNameWithoutInitialization(className,
"abstract-type",
AbstractType.class);
return typeClass;
}
private static AbstractType<?> getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException
{
Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
try
{
Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class);

View File

@ -129,6 +129,7 @@ public final class DiagnosticEventPersistence
LastEventIdBroadcaster.instance().setLastEventId(event.getClass().getName(), store.getLastEventId());
}
@SuppressWarnings("unchecked")
private Class<DiagnosticEvent> getEventClass(String eventClazz) throws ClassNotFoundException, InvalidClassException
{
// get class by eventClazz argument name
@ -136,12 +137,13 @@ public final class DiagnosticEventPersistence
if (!eventClazz.startsWith("org.apache.cassandra."))
throw new RuntimeException("Not a Cassandra event class: " + eventClazz);
Class<DiagnosticEvent> clazz = (Class<DiagnosticEvent>) Class.forName(eventClazz);
// Load without initialization so the type can be verified before the class's static initializer runs.
Class<?> clazz = Class.forName(eventClazz, false, DiagnosticEventPersistence.class.getClassLoader());
if (!(DiagnosticEvent.class.isAssignableFrom(clazz)))
throw new InvalidClassException("Event class must be of type DiagnosticEvent");
return clazz;
return (Class<DiagnosticEvent>) clazz.asSubclass(DiagnosticEvent.class);
}
private DiagnosticEventStore<Long> getStore(Class cls)

View File

@ -767,6 +767,11 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
return indexes.get(indexName);
}
static Class<? extends Index> loadIndexClass(String className)
{
return FBUtilities.classForNameWithoutInitialization(className, "Index", Index.class);
}
private Index createInstance(IndexMetadata indexDef)
{
Index newIndex;
@ -777,7 +782,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
assert !Strings.isNullOrEmpty(className);
try
{
Class<? extends Index> indexClass = FBUtilities.classForName(className, "Index");
Class<? extends Index> indexClass = loadIndexClass(className);
Constructor<? extends Index> ctor = indexClass.getConstructor(ColumnFamilyStore.class, IndexMetadata.class);
newIndex = ctor.newInstance(baseCfs, indexDef);
}

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -60,10 +61,10 @@ public class IndexMode
public final Mode mode;
public final boolean isAnalyzed, isLiteral;
public final Class analyzerClass;
public final Class<? extends AbstractAnalyzer> analyzerClass;
public final long maxCompactionFlushMemoryInBytes;
private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class analyzerClass, long maxMemBytes)
private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class<? extends AbstractAnalyzer> analyzerClass, long maxMemBytes)
{
this.mode = mode;
this.isLiteral = isLiteral;
@ -81,7 +82,7 @@ public class IndexMode
if (isAnalyzed)
{
if (analyzerClass != null)
analyzer = (AbstractAnalyzer) analyzerClass.newInstance();
analyzer = analyzerClass.newInstance();
else if (TOKENIZABLE_TYPES.contains(validator))
analyzer = new StandardAnalyzer();
}
@ -99,21 +100,14 @@ public class IndexMode
// validate that a valid analyzer class was provided if specified
if (indexOptions.containsKey(INDEX_ANALYZER_CLASS_OPTION))
{
Class<?> analyzerClass;
try
{
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
}
catch (ClassNotFoundException e)
{
throw new ConfigurationException(String.format("Invalid analyzer class option specified [%s]",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)));
}
Class<? extends AbstractAnalyzer> analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
"analyzer",
AbstractAnalyzer.class);
AbstractAnalyzer analyzer;
try
{
analyzer = (AbstractAnalyzer) analyzerClass.newInstance();
analyzer = analyzerClass.newInstance();
analyzer.validate(indexOptions, cd);
}
catch (InstantiationException | IllegalAccessException e)
@ -148,25 +142,30 @@ public class IndexMode
}
boolean isAnalyzed = false;
Class analyzerClass = null;
try
Class<? extends AbstractAnalyzer> analyzerClass = null;
if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null)
{
if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null)
try
{
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
"analyzer",
AbstractAnalyzer.class);
isAnalyzed = indexOptions.get(INDEX_ANALYZED_OPTION) == null
? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
}
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
catch (ConfigurationException e)
{
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
if (!(e.getCause() instanceof ClassNotFoundException))
throw e;
// Should not happen as we already validated we could instantiate an instance in validateAnalyzer().
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
}
}
catch (ClassNotFoundException e)
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
{
// should not happen as we already validated we could instantiate an instance in validateAnalyzer()
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
}
boolean isLiteral = false;

View File

@ -405,11 +405,11 @@ public abstract class AbstractReplicationStrategy
if ("org.apache.cassandra.locator.OldNetworkTopologyStrategy".equals(className)) // see CASSANDRA-16301
throw new ConfigurationException("The support for the OldNetworkTopologyStrategy has been removed in C* version 4.0. The keyspace strategy should be switch to NetworkTopologyStrategy");
Class<AbstractReplicationStrategy> strategyClass = FBUtilities.classForName(className, "replication strategy");
if (!AbstractReplicationStrategy.class.isAssignableFrom(strategyClass))
{
throw new ConfigurationException(String.format("Specified replication strategy class (%s) is not derived from AbstractReplicationStrategy", className));
}
@SuppressWarnings("unchecked")
Class<AbstractReplicationStrategy> strategyClass =
(Class<AbstractReplicationStrategy>) FBUtilities.classForNameWithoutInitialization(className,
"replication strategy",
AbstractReplicationStrategy.class);
return strategyClass;
}

View File

@ -277,15 +277,7 @@ public final class CompactionParams
String className = name.contains(".")
? name
: "org.apache.cassandra.db.compaction." + name;
Class<AbstractCompactionStrategy> strategyClass = FBUtilities.classForName(className, "compaction strategy");
if (!AbstractCompactionStrategy.class.isAssignableFrom(strategyClass))
{
throw new ConfigurationException(format("Compaction strategy class %s is not derived from AbstractReplicationStrategy",
className));
}
return strategyClass;
return FBUtilities.classForNameWithoutInitialization(className, "compaction strategy", AbstractCompactionStrategy.class);
}
/*

View File

@ -43,6 +43,7 @@ import org.apache.cassandra.io.compress.*;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.FBUtilities;
import static java.lang.String.format;
@ -273,7 +274,7 @@ public final class CompressionParams
return maxCompressedLength;
}
private static Class<?> parseCompressorClass(String className) throws ConfigurationException
private static Class<? extends ICompressor> parseCompressorClass(String className) throws ConfigurationException
{
if (className == null || className.isEmpty())
return null;
@ -281,15 +282,17 @@ public final class CompressionParams
className = className.contains(".") ? className : "org.apache.cassandra.io.compress." + className;
try
{
return Class.forName(className);
return FBUtilities.classForNameWithoutInitialization(className, "compression", ICompressor.class);
}
catch (Exception e)
catch (ConfigurationException e)
{
throw new ConfigurationException("Could not create Compression for type " + className, e);
if (e.getCause() instanceof ClassNotFoundException || e.getCause() instanceof NoClassDefFoundError)
throw new ConfigurationException("Could not create Compression for type " + className, e);
throw e;
}
}
private static ICompressor createCompressor(Class<?> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException
private static ICompressor createCompressor(Class<? extends ICompressor> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException
{
if (compressorClass == null)
{

View File

@ -117,9 +117,7 @@ public final class IndexMetadata
throw new ConfigurationException(String.format("Required option missing for index %s : %s",
name, IndexTarget.CUSTOM_INDEX_OPTION_NAME));
String className = options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME);
Class<Index> indexerClass = FBUtilities.classForName(className, "custom indexer");
if (!Index.class.isAssignableFrom(indexerClass))
throw new ConfigurationException(String.format("Specified Indexer class (%s) does not implement the Indexer interface", className));
Class<? extends Index> indexerClass = FBUtilities.classForNameWithoutInitialization(className, "custom indexer", Index.class);
validateCustomIndexOptions(table, indexerClass, options);
}
}

View File

@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.config.TransparentDataEncryptionOptions;
import org.apache.cassandra.utils.FBUtilities;
/**
* A factory for loading encryption keys from {@link KeyProvider} instances.
@ -70,9 +71,10 @@ public class CipherFactory
try
{
secureRandom = SecureRandom.getInstance("SHA1PRNG");
Class<KeyProvider> keyProviderClass = (Class<KeyProvider>)Class.forName(options.key_provider.class_name);
Constructor ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class);
keyProvider = (KeyProvider)ctor.newInstance(options);
Class<? extends KeyProvider> keyProviderClass =
FBUtilities.classForNameWithoutInitialization(options.key_provider.class_name, "key provider", KeyProvider.class);
Constructor<? extends KeyProvider> ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class);
keyProvider = ctor.newInstance(options);
}
catch (Exception e)
{

View File

@ -129,9 +129,11 @@ public class CacheService implements CacheServiceMBean
? DatabaseDescriptor.getRowCacheClassName() : "org.apache.cassandra.cache.NopCacheProvider";
try
{
Class<CacheProvider<RowCacheKey, IRowCacheEntry>> cacheProviderClass =
(Class<CacheProvider<RowCacheKey, IRowCacheEntry>>) Class.forName(cacheProviderClassName);
cacheProvider = cacheProviderClass.newInstance();
Class<? extends CacheProvider> cacheProviderClass =
FBUtilities.classForNameWithoutInitialization(cacheProviderClassName, "row cache provider", CacheProvider.class);
@SuppressWarnings("unchecked")
CacheProvider<RowCacheKey, IRowCacheEntry> typedCacheProvider = cacheProviderClass.newInstance();
cacheProvider = typedCacheProvider;
}
catch (Exception e)
{

View File

@ -107,7 +107,7 @@ public class ClientState
{
try
{
handler = FBUtilities.construct(customHandlerClass, "QueryHandler");
handler = FBUtilities.construct(customHandlerClass, "QueryHandler", QueryHandler.class);
logger.info("Using {} as query handler for native protocol queries (as requested with -Dcassandra.custom_query_handler_class)", customHandlerClass);
}
catch (Exception e)

View File

@ -35,7 +35,7 @@ public interface StreamHook
String className = System.getProperty("cassandra.stream_hook");
if (className != null)
{
return FBUtilities.construct(className, StreamHook.class.getSimpleName());
return FBUtilities.construct(className, StreamHook.class.getSimpleName(), StreamHook.class);
}
else
{

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.config.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.tools.BulkLoader.CmdLineOptions;
import org.apache.cassandra.utils.FBUtilities;
import com.datastax.driver.core.AuthProvider;
import com.datastax.driver.core.PlainTextAuthProvider;
@ -551,11 +552,12 @@ public class LoaderOptions
{
try
{
Class authProviderClass = Class.forName(authProviderName);
Constructor constructor = authProviderClass.getConstructor(String.class, String.class);
authProvider = (AuthProvider)constructor.newInstance(user, passwd);
Class<? extends AuthProvider> authProviderClass =
FBUtilities.classForNameWithoutInitialization(authProviderName, "auth provider", AuthProvider.class);
Constructor<? extends AuthProvider> constructor = authProviderClass.getConstructor(String.class, String.class);
authProvider = constructor.newInstance(user, passwd);
}
catch (ClassNotFoundException e)
catch (ConfigurationException e)
{
errorMsg("Unknown auth provider: " + e.getMessage(), getCmdLineOptions());
}
@ -582,9 +584,9 @@ public class LoaderOptions
{
try
{
authProvider = (AuthProvider)Class.forName(authProviderName).newInstance();
authProvider = FBUtilities.construct(authProviderName, "auth provider", AuthProvider.class);
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
catch (ConfigurationException e)
{
errorMsg("Unknown auth provider: " + e.getMessage(), getCmdLineOptions());
}

View File

@ -405,6 +405,7 @@ public class Sjk extends NodeToolCmd
List<Class<?>> result = new ArrayList<>();
try
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
for (String f : findFiles(path))
{
@ -412,7 +413,7 @@ public class Sjk extends NodeToolCmd
{
f = f.substring(0, f.length() - ".class".length());
f = f.replace('/', '.');
result.add(Class.forName(f));
result.add(Class.forName(f, false, cl));
}
}
return result;

View File

@ -119,7 +119,7 @@ public abstract class Tracing implements ExecutorLocal<TraceState>
{
try
{
tracing = FBUtilities.construct(customTracingClass, "Tracing");
tracing = FBUtilities.construct(customTracingClass, "Tracing", Tracing.class);
logger.info("Using {} as tracing queries (as requested with -Dcassandra.custom_tracing_class)", customTracingClass);
}
catch (Exception e)

View File

@ -32,6 +32,7 @@ import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.CassandraException;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TriggerMetadata;
@ -252,11 +253,26 @@ public class TriggerExecutor
}
}
public synchronized Class<? extends ITrigger> loadTriggerClass(String triggerClass) throws Exception
{
// Load without initialization so the type can be verified before the class's static initializer runs.
try
{
return FBUtilities.classForNameWithoutInitialization(triggerClass, "trigger", ITrigger.class, customClassLoader);
}
catch (ConfigurationException e)
{
if (e.getCause() instanceof ClassNotFoundException)
throw (ClassNotFoundException) e.getCause();
throw e;
}
}
public synchronized ITrigger loadTriggerInstance(String triggerClass) throws Exception
{
// double check.
if (cachedTriggers.get(triggerClass) != null)
return cachedTriggers.get(triggerClass);
return (ITrigger) customClassLoader.loadClass(triggerClass).getConstructor().newInstance();
return loadTriggerClass(triggerClass).getConstructor().newInstance();
}
}

View File

@ -634,28 +634,28 @@ public class FBUtilities
assert comparator.isPresent() : "Expected a comparator for local partitioner";
return new LocalPartitioner(comparator.get());
}
return FBUtilities.instanceOrConstruct(partitionerClassName, "partitioner");
return FBUtilities.instanceOrConstruct(partitionerClassName, "partitioner", IPartitioner.class);
}
public static IAuthorizer newAuthorizer(String className) throws ConfigurationException
{
if (!className.contains("."))
className = "org.apache.cassandra.auth." + className;
return FBUtilities.construct(className, "authorizer");
return FBUtilities.construct(className, "authorizer", IAuthorizer.class);
}
public static IAuthenticator newAuthenticator(String className) throws ConfigurationException
{
if (!className.contains("."))
className = "org.apache.cassandra.auth." + className;
return FBUtilities.construct(className, "authenticator");
return FBUtilities.construct(className, "authenticator", IAuthenticator.class);
}
public static IRoleManager newRoleManager(String className) throws ConfigurationException
{
if (!className.contains("."))
className = "org.apache.cassandra.auth." + className;
return FBUtilities.construct(className, "role manager");
return FBUtilities.construct(className, "role manager", IRoleManager.class);
}
public static INetworkAuthorizer newNetworkAuthorizer(String className)
@ -668,9 +668,9 @@ public class FBUtilities
{
className = "org.apache.cassandra.auth." + className;
}
return FBUtilities.construct(className, "network authorizer");
return FBUtilities.construct(className, "network authorizer", INetworkAuthorizer.class);
}
public static IAuditLogger newAuditLogger(String className, Map<String, String> parameters) throws ConfigurationException
{
if (!className.contains("."))
@ -678,8 +678,9 @@ public class FBUtilities
try
{
Class<?> auditLoggerClass = Class.forName(className);
return (IAuditLogger) auditLoggerClass.getConstructor(Map.class).newInstance(parameters);
Class<? extends IAuditLogger> auditLoggerClass =
FBUtilities.classForNameWithoutInitialization(className, "Audit logger", IAuditLogger.class);
return auditLoggerClass.getConstructor(Map.class).newInstance(parameters);
}
catch (Exception ex)
{
@ -694,7 +695,7 @@ public class FBUtilities
try
{
FBUtilities.classForName(className, "Audit logger");
FBUtilities.classForNameWithoutInitialization(className, "Audit logger", IAuditLogger.class);
}
catch (ConfigurationException e)
{
@ -704,6 +705,8 @@ public class FBUtilities
}
/**
* Loads and initializes a class.
*
* @return The Class for the given name.
* @param classname Fully qualified classname.
* @param readable Descriptive noun for the role the class plays.
@ -721,6 +724,53 @@ public class FBUtilities
}
}
/**
* Loads a class without initializing it, then verifies it extends or implements the expected base type.
*
* @return The Class for the given name.
* @param classname Fully qualified classname.
* @param readable Descriptive noun for the role the class plays.
* @param expectedType Required superclass or interface.
* @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}.
*/
public static <T> Class<? extends T> classForNameWithoutInitialization(String classname,
String readable,
Class<T> expectedType) throws ConfigurationException
{
return classForNameWithoutInitialization(classname, readable, expectedType, FBUtilities.class.getClassLoader());
}
/**
* Loads a class without initializing it, then verifies it extends or implements the expected base type.
*
* @return The Class for the given name.
* @param classname Fully qualified classname.
* @param readable Descriptive noun for the role the class plays.
* @param expectedType Required superclass or interface.
* @param classLoader ClassLoader to use.
* @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}.
*/
public static <T> Class<? extends T> classForNameWithoutInitialization(String classname,
String readable,
Class<T> expectedType,
ClassLoader classLoader) throws ConfigurationException
{
try
{
Class<?> klass = Class.forName(classname, false, classLoader);
if (!expectedType.isAssignableFrom(klass))
throw new ConfigurationException(String.format("Invalid %s class '%s': must extend or implement %s",
readable,
classname,
expectedType.getName()));
return klass.asSubclass(expectedType);
}
catch (ClassNotFoundException | NoClassDefFoundError e)
{
throw new ConfigurationException(String.format("Unable to find %s class '%s'", readable, classname), e);
}
}
/**
* Constructs an instance of the given class, which must have a no-arg or default constructor.
* @param classname Fully qualified classname.
@ -730,6 +780,25 @@ public class FBUtilities
public static <T> T instanceOrConstruct(String classname, String readable) throws ConfigurationException
{
Class<T> cls = FBUtilities.classForName(classname, readable);
return instanceOrConstruct(cls, classname, readable);
}
/**
* Constructs an instance of the given class, or gets its static {@code instance} field, after verifying the
* class without initializing it.
* @param classname Fully qualified classname.
* @param readable Descriptive noun for the role the class plays.
* @param expectedType Required superclass or interface.
* @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}.
*/
public static <T> T instanceOrConstruct(String classname, String readable, Class<T> expectedType) throws ConfigurationException
{
Class<? extends T> cls = FBUtilities.classForNameWithoutInitialization(classname, readable, expectedType);
return instanceOrConstruct(cls, classname, readable);
}
private static <T> T instanceOrConstruct(Class<? extends T> cls, String classname, String readable) throws ConfigurationException
{
try
{
Field instance = cls.getField("instance");
@ -754,7 +823,20 @@ public class FBUtilities
return construct(cls, classname, readable);
}
private static <T> T construct(Class<T> cls, String classname, String readable) throws ConfigurationException
/**
* Constructs an instance of the given class after verifying it without initializing it.
* @param classname Fully qualified classname.
* @param readable Descriptive noun for the role the class plays.
* @param expectedType Required superclass or interface.
* @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}.
*/
public static <T> T construct(String classname, String readable, Class<T> expectedType) throws ConfigurationException
{
Class<? extends T> cls = FBUtilities.classForNameWithoutInitialization(classname, readable, expectedType);
return construct(cls, classname, readable);
}
private static <T> T construct(Class<? extends T> cls, String classname, String readable) throws ConfigurationException
{
try
{

View File

@ -214,7 +214,7 @@ public class JMXServerUtils
String authzProxyClass = System.getProperty("cassandra.jmx.authorizer");
if (authzProxyClass != null)
{
final InvocationHandler handler = FBUtilities.construct(authzProxyClass, "JMX authz proxy");
final InvocationHandler handler = FBUtilities.construct(authzProxyClass, "JMX authz proxy", InvocationHandler.class);
final Class[] interfaces = { MBeanServerForwarder.class };
Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler);

View File

@ -77,7 +77,7 @@ public interface MBeanWrapper
return new PlatformMBeanWrapper();
}
}
return FBUtilities.construct(klass, "mbean");
return FBUtilities.construct(klass, "mbean", MBeanWrapper.class);
}
// Passing true for graceful will log exceptions instead of rethrowing them

View File

@ -85,7 +85,7 @@ public interface MonotonicClock
try
{
logger.debug("Using custom clock implementation: {}", sclock);
return (MonotonicClock) Class.forName(sclock).newInstance();
return FBUtilities.construct(sclock, "monotonic clock", MonotonicClock.class);
}
catch (Exception e)
{
@ -104,7 +104,8 @@ public interface MonotonicClock
try
{
logger.debug("Using custom clock implementation: {}", sclock);
Class<? extends MonotonicClock> clazz = (Class<? extends MonotonicClock>) Class.forName(sclock);
Class<? extends MonotonicClock> clazz =
FBUtilities.classForNameWithoutInitialization(sclock, "monotonic clock", MonotonicClock.class);
if (SystemClock.class.equals(clazz) && SystemClock.class.equals(precise.getClass()))
return precise;

View File

@ -34,8 +34,12 @@ import org.junit.Test;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -48,6 +52,25 @@ public class DatabaseDescriptorTest
DatabaseDescriptor.daemonInitialization();
}
@Test
public void testCreateEndpointSnitchWrongTypeRejectedWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> DatabaseDescriptor.createEndpointSnitch(false, ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + IEndpointSnitch.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testCreateEndpointSnitchValidClassResolves()
{
IEndpointSnitch snitch = DatabaseDescriptor.createEndpointSnitch(false, "SimpleSnitch");
assertThat(snitch).isNotNull();
}
// this came as a result of CASSANDRA-995
@Test
public void testConfigurationLoader() throws Exception

View File

@ -21,6 +21,8 @@ package org.apache.cassandra.db.marshal;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
@ -28,6 +30,8 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
public class TypeParserTest
{
@ -37,6 +41,17 @@ public class TypeParserTest
DatabaseDescriptor.daemonInitialization();
}
@Test
public void testRejectsNonAbstractTypeWithoutInitializing() throws SyntaxException
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> TypeParser.parse(ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + AbstractType.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testParse() throws ConfigurationException, SyntaxException
{

View File

@ -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.cassandra.diag;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
/**
* A minimal, valid {@link DiagnosticEvent} subclass used by {@code DiagnosticEventPersistenceTest} to confirm that
* the type-checked load path in {@link DiagnosticEventPersistence} resolves a correct subtype. Lives in the
* {@code org.apache.cassandra} namespace so it passes the persistence package-prefix guard.
*/
public final class ClassLoadingTestDiagnosticEvent extends DiagnosticEvent
{
public enum TestType { TEST }
@Override
public Enum<?> getType()
{
return TestType.TEST;
}
@Override
public Map<String, Serializable> toMap()
{
return Collections.emptyMap();
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.cassandra.diag;
import java.io.InvalidClassException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.junit.Test;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class DiagnosticEventPersistenceTest
{
/**
* Reflectively drives the (private) bespoke type-checked load path
* {@link DiagnosticEventPersistence#getEventClass(String)}.
*/
private static Class<?> getEventClass(String eventClazz) throws Throwable
{
Method method = DiagnosticEventPersistence.class.getDeclaredMethod("getEventClass", String.class);
method.setAccessible(true);
try
{
return (Class<?>) method.invoke(DiagnosticEventPersistence.instance(), eventClazz);
}
catch (InvocationTargetException e)
{
throw e.getCause();
}
}
@Test
public void testNonDiagnosticEventRejectedWithoutInitializing() throws Throwable
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> getEventClass(ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(InvalidClassException.class)
.hasMessageContaining("must be of type DiagnosticEvent");
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testValidDiagnosticEventSubclassResolves() throws Throwable
{
Class<?> resolved = getEventClass(ClassLoadingTestDiagnosticEvent.class.getName());
assertThat(resolved).isEqualTo(ClassLoadingTestDiagnosticEvent.class);
}
}

View File

@ -38,11 +38,15 @@ import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.KillerForTests;
import org.apache.cassandra.utils.concurrent.Refs;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -57,6 +61,17 @@ public class SecondaryIndexManagerTest extends CQLTester
TestingIndex.clear();
}
@Test
public void rejectsNonIndexClassWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> SecondaryIndexManager.loadIndexClass(ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + Index.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void creatingIndexMarksTheIndexAsBuilt() throws Throwable
{

View File

@ -30,10 +30,16 @@ import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.sasi.analyzer.AbstractAnalyzer;
import org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer;
import org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder.Mode;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class IndexModeTest
@ -184,8 +190,8 @@ public class IndexModeTest
{
ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance);
IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", "java.lang.Object"));
Assert.assertEquals(Object.class, result.analyzerClass);
IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", NonTokenizingAnalyzer.class.getName()));
Assert.assertEquals(NonTokenizingAnalyzer.class, result.analyzerClass);
Assert.assertTrue(result.isAnalyzed);
Assert.assertFalse(result.isLiteral);
Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes);
@ -197,16 +203,55 @@ public class IndexModeTest
{
ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance);
IndexMode result = IndexMode.getMode(cd, ImmutableMap.of("analyzer_class", "java.lang.Object",
IndexMode result = IndexMode.getMode(cd, ImmutableMap.of("analyzer_class", NonTokenizingAnalyzer.class.getName(),
"analyzed", "false"));
Assert.assertEquals(Object.class, result.analyzerClass);
Assert.assertEquals(NonTokenizingAnalyzer.class, result.analyzerClass);
Assert.assertFalse(result.isAnalyzed);
Assert.assertFalse(result.isLiteral);
Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes);
Assert.assertEquals(Mode.PREFIX, result.mode);
}
@Test
public void test_bytesType_rejectsNonAnalyzerWithoutInitializing()
{
ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance);
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", ClassLoadingTestNonAssignable.class.getName())))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + AbstractAnalyzer.class.getName());
Assert.assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void test_bytesType_missingAnalyzerFallsBack()
{
ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance);
IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", "does.not.ExistAnalyzer"));
Assert.assertNull(result.analyzerClass);
Assert.assertFalse(result.isAnalyzed);
Assert.assertFalse(result.isLiteral);
Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes);
Assert.assertEquals(Mode.PREFIX, result.mode);
}
@Test
public void test_validateAnalyzer_rejectsNonAnalyzerWithoutInitializing()
{
ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), UTF8Type.instance);
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> IndexMode.validateAnalyzer(Collections.singletonMap("analyzer_class", ClassLoadingTestNonAssignable.class.getName()), cd))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + AbstractAnalyzer.class.getName());
Assert.assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void test_bytesType_maxCompactionFlushMemoryInBytes()
{

View File

@ -0,0 +1,44 @@
/*
* 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.cassandra.schema;
import org.junit.Test;
import org.apache.cassandra.db.compaction.AbstractCompactionStrategy;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class CompactionParamsTest
{
@Test
public void testRejectsNonCompactionStrategyWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> CompactionParams.classFromName(ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + AbstractCompactionStrategy.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.cassandra.schema;
import java.util.Collections;
import org.junit.Test;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.compress.ICompressor;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class CompressionParamsTest
{
@Test
public void testRejectsNonCompressorWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> CompressionParams.fromMap(Collections.singletonMap(CompressionParams.CLASS,
ClassLoadingTestNonAssignable.class.getName())))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + ICompressor.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
}

View File

@ -20,10 +20,21 @@
*/
package org.apache.cassandra.schema;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class IndexMetadataTest
{
@ -33,4 +44,24 @@ public class IndexMetadataTest
Assert.assertEquals("aB4__idx", IndexMetadata.generateDefaultIndexName("a B-4@!_+"));
Assert.assertEquals("34_Ddd_F6_idx", IndexMetadata.generateDefaultIndexName("34_()Ddd", new ColumnIdentifier("#F%6*", true)));
}
@Test
public void testRejectsNonCustomIndexWithoutInitializing()
{
TableMetadata table = TableMetadata.builder("ks", "tbl")
.addPartitionKeyColumn("pk", UTF8Type.instance)
.build();
IndexMetadata index = IndexMetadata.fromSchemaMetadata("idx",
IndexMetadata.Kind.CUSTOM,
Collections.singletonMap(IndexTarget.CUSTOM_INDEX_OPTION_NAME,
ClassLoadingTestNonAssignable.class.getName()));
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> index.validate(table))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + Index.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
}

View File

@ -34,12 +34,34 @@ import org.junit.Before;
import org.junit.Test;
import org.apache.cassandra.config.TransparentDataEncryptionOptions;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class CipherFactoryTest
{
@Test
public void keyProviderWrongTypeRejectedWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
TransparentDataEncryptionOptions options = EncryptionContextGenerator.createEncryptionOptions();
options.key_provider.class_name = ClassLoadingTestNonAssignable.class.getName();
// CipherFactory wraps the load failure; the cause is the type-check ConfigurationException
assertThatThrownBy(() -> new CipherFactory(options))
.isInstanceOf(RuntimeException.class)
.hasCauseInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must extend or implement " + KeyProvider.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
// http://www.gutenberg.org/files/4300/4300-h/4300-h.htm
static final String ULYSSEUS = "Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a razor lay crossed. " +
"A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning air. He held the bowl aloft and intoned: " +

View File

@ -35,9 +35,12 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.TriggerMetadata;
import org.apache.cassandra.schema.Triggers;
import org.apache.cassandra.triggers.TriggerExecutorTest.SameKeySameCfTrigger;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@ -366,4 +369,28 @@ public class TriggerExecutorTest
return cmp != 0 ? cmp : m1.key().compareTo(m2.key());
}
}
@Test
public void nonTriggerClassRejectedWithoutInitializing() throws Exception
{
ClassLoadingTestSupport.assertNotInitialized(NonTrigger.class);
assertThatExceptionOfType(ConfigurationException.class)
.isThrownBy(() -> TriggerExecutor.instance.loadTriggerClass(NonTrigger.class.getName()))
.withMessageContaining("must extend or implement " + ITrigger.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(NonTrigger.class)).isFalse();
}
public static class NonTrigger
{
static
{
ClassLoadingTestSupport.markInitialized(NonTrigger.class);
}
public NonTrigger()
{
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.cassandra.utils;
public final class ClassLoadingTestNonAssignable
{
static
{
ClassLoadingTestSupport.markInitialized(ClassLoadingTestNonAssignable.class);
}
private ClassLoadingTestNonAssignable()
{
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.cassandra.utils;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class ClassLoadingTestSupport
{
private static final Set<String> initializedClasses = ConcurrentHashMap.newKeySet();
private ClassLoadingTestSupport()
{
}
public static void markInitialized(Class<?> initializedClass)
{
initializedClasses.add(initializedClass.getName());
}
public static boolean wasInitialized(Class<?> initializedClass)
{
return initializedClasses.contains(initializedClass.getName());
}
public static void assertNotInitialized(Class<?> initializedClass)
{
if (wasInitialized(initializedClass))
throw new AssertionError(initializedClass.getName() + " has already been initialized");
}
}

View File

@ -23,6 +23,7 @@ import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
@ -43,14 +44,75 @@ import org.junit.Test;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.dht.*;
import org.apache.cassandra.audit.IAuditLogger;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.exceptions.ConfigurationException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class FBUtilitiesTest
{
@Test
public void testTypedClassForNameRejectsWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.classForNameWithoutInitialization(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + Runnable.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testTypedConstructRejectsWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.construct(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + Runnable.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testTypedInstanceOrConstructRejectsWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.instanceOrConstruct(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + Runnable.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testNewAuditLoggerRejectsWrongTypeWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.newAuditLogger(ClassLoadingTestNonAssignable.class.getName(), Collections.emptyMap()))
.isInstanceOf(ConfigurationException.class)
.hasRootCauseInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must extend or implement " + IAuditLogger.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testIsAuditLoggerClassExistsRejectsWrongTypeWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertFalse(FBUtilities.isAuditLoggerClassExists(ClassLoadingTestNonAssignable.class.getName()));
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
assertTrue(FBUtilities.isAuditLoggerClassExists("NoOpAuditLogger"));
assertFalse(FBUtilities.isAuditLoggerClassExists("does.not.ExistAuditLogger"));
}
@Test
public void testCompareByteSubArrays()
{

View File

@ -28,6 +28,7 @@ import java.util.Map;
import com.google.common.base.Function;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.utils.FBUtilities;
/**
* For specifying replication options
@ -76,9 +77,9 @@ class OptionReplication extends OptionMulti
{
try
{
Class<?> clazz = Class.forName(fullname);
if (!AbstractReplicationStrategy.class.isAssignableFrom(clazz))
throw new IllegalArgumentException(clazz + " is not a replication strategy");
FBUtilities.classForNameWithoutInitialization(fullname,
"replication strategy",
AbstractReplicationStrategy.class);
strategy = fullname;
break;
} catch (Exception ignore)

View File

@ -31,6 +31,7 @@ import com.datastax.driver.core.PlainTextAuthProvider;
import com.datastax.driver.core.ProtocolOptions;
import com.datastax.driver.core.ProtocolVersion;
import org.apache.cassandra.stress.util.ResultLogger;
import org.apache.cassandra.utils.FBUtilities;
public class SettingsMode implements Serializable
{
@ -72,9 +73,10 @@ public class SettingsMode implements Serializable
{
try
{
Class<?> clazz = Class.forName(authProviderClassname);
if (!AuthProvider.class.isAssignableFrom(clazz))
throw new IllegalArgumentException(clazz + " is not a valid auth provider");
Class<? extends AuthProvider> clazz =
FBUtilities.classForNameWithoutInitialization(authProviderClassname,
"auth provider",
AuthProvider.class);
// check we can instantiate it
if (PlainTextAuthProvider.class.equals(clazz))
{