Merge branch 'cassandra-4.1' into cassandra-5.0

This commit is contained in:
Stefan Miklosovic 2026-07-20 20:42:57 +02:00
commit bd345213c1
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
59 changed files with 1152 additions and 118 deletions

View File

@ -11,6 +11,7 @@
Merged from 4.1:
* Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316)
Merged from 4.0:
* 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

@ -51,7 +51,7 @@ public final class AuthConfig
/* Authentication, authorization and role management backend, implementing IAuthenticator, I*Authorizer & IRoleManager */
IAuthenticator authenticator = authInstantiate(conf.authenticator, AllowAllAuthenticator.class);
IAuthenticator authenticator = authInstantiate(conf.authenticator, IAuthenticator.class, AllowAllAuthenticator.class);
// the configuration options regarding credentials caching are only guaranteed to
// work with PasswordAuthenticator, so log a message if some other authenticator
@ -70,7 +70,7 @@ public final class AuthConfig
// authorizer
IAuthorizer authorizer = authInstantiate(conf.authorizer, AllowAllAuthorizer.class);
IAuthorizer authorizer = authInstantiate(conf.authorizer, IAuthorizer.class, AllowAllAuthorizer.class);
if (!authenticator.requireAuthentication() && authorizer.requireAuthorization())
{
@ -82,7 +82,7 @@ public final class AuthConfig
// role manager
IRoleManager roleManager = authInstantiate(conf.role_manager, CassandraRoleManager.class);
IRoleManager roleManager = authInstantiate(conf.role_manager, IRoleManager.class, CassandraRoleManager.class);
if (authenticator instanceof PasswordAuthenticator && !(roleManager instanceof CassandraRoleManager))
throw new ConfigurationException(authenticator.getClass().getName() + " requires " + CassandraRoleManager.class.getName(), false);
@ -92,12 +92,13 @@ public final class AuthConfig
// authenticator
IInternodeAuthenticator internodeAuthenticator = authInstantiate(conf.internode_authenticator,
IInternodeAuthenticator.class,
AllowAllInternodeAuthenticator.class);
DatabaseDescriptor.setInternodeAuthenticator(internodeAuthenticator);
// network authorizer
INetworkAuthorizer networkAuthorizer = authInstantiate(conf.network_authorizer, AllowAllNetworkAuthorizer.class);
INetworkAuthorizer networkAuthorizer = authInstantiate(conf.network_authorizer, INetworkAuthorizer.class, AllowAllNetworkAuthorizer.class);
if (networkAuthorizer.requireAuthorization() && !authenticator.requireAuthentication())
{
@ -108,7 +109,7 @@ public final class AuthConfig
// cidr authorizer
ICIDRAuthorizer cidrAuthorizer = authInstantiate(conf.cidr_authorizer, AllowAllCIDRAuthorizer.class);
ICIDRAuthorizer cidrAuthorizer = authInstantiate(conf.cidr_authorizer, ICIDRAuthorizer.class, AllowAllCIDRAuthorizer.class);
if (cidrAuthorizer.requireAuthorization() && !authenticator.requireAuthentication())
{
@ -128,11 +129,11 @@ public final class AuthConfig
DatabaseDescriptor.getInternodeAuthenticator().validateConfiguration();
}
private static <T> T authInstantiate(ParameterizedClass authCls, Class<T> defaultCls) {
private static <T> T authInstantiate(ParameterizedClass authCls, Class<T> expectedType, Class<? extends T> defaultCls) {
if (authCls != null && authCls.class_name != null)
{
String authPackage = AuthConfig.class.getPackage().getName();
return ParameterizedClass.newInstance(authCls, List.of("", authPackage));
return ParameterizedClass.newInstance(authCls, List.of("", authPackage), expectedType);
}
// for now, this has to stay and can not be replaced by ParameterizedClass.newInstance as above

View File

@ -78,7 +78,8 @@ public class MutualTlsAuthenticator implements IAuthenticator
throw new ConfigurationException(message);
}
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
Arrays.asList("", AuthConfig.class.getPackage().getName()));
Arrays.asList("", AuthConfig.class.getPackage().getName()),
MutualTlsCertificateValidator.class);
checkMtlsConfigurationIsValid(DatabaseDescriptor.getRawConfig());
AuthCacheService.instance.register(identityCache);
}

View File

@ -87,7 +87,8 @@ public class MutualTlsInternodeAuthenticator implements IInternodeAuthenticator
}
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
Arrays.asList("", AuthConfig.class.getPackage().getName()));
Arrays.asList("", AuthConfig.class.getPackage().getName()),
MutualTlsCertificateValidator.class);
Config config = DatabaseDescriptor.getRawConfig();
checkInternodeMtlsConfigurationIsValid(config);

View File

@ -396,7 +396,7 @@ public class DatabaseDescriptor
String loaderClass = CONFIG_LOADER.getString();
ConfigurationLoader loader = loaderClass == null
? new YamlConfigurationLoader()
: FBUtilities.construct(loaderClass, "configuration loading");
: FBUtilities.construct(loaderClass, "configuration loading", ConfigurationLoader.class);
Config config = loader.loadConfig();
if (!hasLoggedConfig)
@ -1330,8 +1330,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)
@ -1666,7 +1667,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;
}
@ -1674,7 +1675,7 @@ public class DatabaseDescriptor
{
if (!detectorClassName.contains("."))
detectorClassName = "org.apache.cassandra.gms." + detectorClassName;
IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector");
IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector", IFailureDetector.class);
return detector;
}

View File

@ -64,7 +64,17 @@ public class ParameterizedClass
p.containsKey(PARAMETERS) ? (Map<String, String>)((List<?>)p.get(PARAMETERS)).get(0) : null);
}
/**
* Prefer {@link #newInstance(ParameterizedClass, List, Class)}: passing an {@code expectedType} verifies the
* resolved class is the intended extension type before it is instantiated. This overload performs no type
* check (it still loads without initialization, so a wrong class name cannot run its static initializer here).
*/
static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages)
{
return newInstance(parameterizedClass, searchPackages, null);
}
static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages, Class<K> expectedType)
{
Class<?> providerClass = null;
if (searchPackages == null || searchPackages.isEmpty())
@ -76,9 +86,12 @@ public class ParameterizedClass
if (!searchPackage.isEmpty() && !searchPackage.endsWith("."))
searchPackage = searchPackage + '.';
String name = searchPackage + parameterizedClass.class_name;
providerClass = Class.forName(name);
// Load without initialization so a wrong class name does not run its static initializer here. The
// type is verified below (once the search has resolved a class) and the class is only initialized
// later, when it is constructed.
providerClass = Class.forName(name, false, ParameterizedClass.class.getClassLoader());
}
catch (ClassNotFoundException e)
catch (ClassNotFoundException | NoClassDefFoundError e)
{
//no-op
}
@ -91,6 +104,13 @@ public class ParameterizedClass
throw new ConfigurationException(error);
}
// Verify the resolved class is the expected extension type before it is initialized/instantiated. Done once,
// after the package search, so a wrong-type match under an earlier search package does not abort the search
// before a valid class under a later package is found.
if (expectedType != null && !expectedType.isAssignableFrom(providerClass))
throw new ConfigurationException("Invalid parameterized class " + providerClass.getName() +
": must extend or implement " + expectedType.getName());
try
{
Constructor<?> mapConstructor = filterConstructor(providerClass, c -> c.getParameterTypes().length == 1 && c.getParameterTypes()[0].equals(Map.class));

View File

@ -55,7 +55,7 @@ public interface StorageHook
String className = STORAGE_HOOK.getString();
if (className != null)
{
return FBUtilities.construct(className, StorageHook.class.getSimpleName());
return FBUtilities.construct(className, StorageHook.class.getSimpleName(), StorageHook.class);
}
return new StorageHook()

View File

@ -65,7 +65,7 @@ public interface GuardrailsConfigProvider
*/
static GuardrailsConfigProvider build(String customImpl)
{
return FBUtilities.construct(customImpl, "custom guardrails config provider");
return FBUtilities.construct(customImpl, "custom guardrails config provider", GuardrailsConfigProvider.class);
}
/**

View File

@ -447,8 +447,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");
@ -461,10 +460,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

@ -868,6 +868,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;
@ -880,7 +885,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
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

@ -427,11 +427,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

@ -395,7 +395,10 @@ public class AutoRepairConfig implements Serializable
className = parameterizedClass.class_name.contains(".") ?
parameterizedClass.class_name :
"org.apache.cassandra.repair.autorepair." + parameterizedClass.class_name;
tokenRangeSplitterClass = FBUtilities.classForName(className, "token_range_splitter");
tokenRangeSplitterClass =
FBUtilities.classForNameWithoutInitialization(className,
"token_range_splitter",
IAutoRepairTokenRangeSplitter.class);
}
else
{

View File

@ -301,15 +301,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

@ -39,6 +39,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;
@ -247,7 +248,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;
@ -255,15 +256,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

@ -137,9 +137,7 @@ public final class IndexMetadata
// Get the fully qualified class name:
String className = getIndexClassName();
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

@ -228,19 +228,25 @@ public final class MemtableParams
try
{
Memtable.Factory factory;
Class<?> clazz = Class.forName(className);
Class<?> clazz = Class.forName(className, false, MemtableParams.class.getClassLoader());
final Map<String, String> parametersCopy = options.parameters != null
? new HashMap<>(options.parameters)
: new HashMap<>();
try
{
Method factoryMethod = clazz.getDeclaredMethod("factory", Map.class);
if (!Memtable.Factory.class.isAssignableFrom(factoryMethod.getReturnType()))
throw new ClassCastException("Memtable factory method on " + className +
" must return " + Memtable.Factory.class.getName());
factory = (Memtable.Factory) factoryMethod.invoke(null, parametersCopy);
}
catch (NoSuchMethodException e)
{
// continue with FACTORY field
Field factoryField = clazz.getDeclaredField("FACTORY");
if (!Memtable.Factory.class.isAssignableFrom(factoryField.getType()))
throw new ClassCastException("Memtable FACTORY field on " + className +
" must be of type " + Memtable.Factory.class.getName());
factory = (Memtable.Factory) factoryField.get(null);
}
if (!parametersCopy.isEmpty())

View File

@ -50,7 +50,8 @@ public class SchemaUpdateHandlerFactoryProvider implements Provider<SchemaUpdate
}
else
{
Class<SchemaUpdateHandlerFactory> suhFactoryClass = FBUtilities.classForName(suhFactoryClassName, "schema update handler factory");
Class<? extends SchemaUpdateHandlerFactory> suhFactoryClass =
FBUtilities.classForNameWithoutInitialization(suhFactoryClassName, "schema update handler factory", SchemaUpdateHandlerFactory.class);
try
{
return suhFactoryClass.newInstance();

View File

@ -105,7 +105,7 @@ public abstract class AbstractCryptoProvider
return;
}
FBUtilities.classForName(getProviderClassAsString(), "crypto provider");
FBUtilities.classForNameWithoutInitialization(getProviderClassAsString(), "crypto provider", Provider.class);
String providerName = getProviderName();
int providerPosition = getProviderPosition(providerName);

View File

@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.concurrent.ImmediateExecutor;
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

@ -147,9 +147,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

@ -118,7 +118,7 @@ public class ClientState
{
try
{
handler = FBUtilities.construct(customHandlerClass, "QueryHandler");
handler = FBUtilities.construct(customHandlerClass, "QueryHandler", QueryHandler.class);
logger.info("Using {} as a query handler for native protocol queries (as requested by the {} system property)",
customHandlerClass, CUSTOM_QUERY_HANDLER_CLASS.getKey());
}

View File

@ -37,7 +37,7 @@ public interface StreamHook
String className = STREAM_HOOK.getString();
if (className != null)
{
return FBUtilities.construct(className, StreamHook.class.getSimpleName());
return FBUtilities.construct(className, StreamHook.class.getSimpleName(), StreamHook.class);
}
else
{

View File

@ -50,6 +50,7 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.tools.BulkLoader.CmdLineOptions;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND;
@ -715,11 +716,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());
}
@ -746,9 +748,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

@ -116,7 +116,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
{
try
{
tracing = FBUtilities.construct(customTracingClass, "Tracing");
tracing = FBUtilities.construct(customTracingClass, "Tracing", Tracing.class);
logger.info("Using the {} class to trace queries (as requested by the {} system property)",
customTracingClass, CUSTOM_TRACING_CLASS.getKey());
}

View File

@ -31,6 +31,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.io.util.File;
import org.apache.cassandra.schema.TableId;
@ -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

@ -60,7 +60,7 @@ public interface Clock
try
{
outcome = "Using custom clock implementation: " + classname;
clock = (Clock) Class.forName(classname).newInstance();
clock = FBUtilities.construct(classname, "clock", Clock.class);
}
catch (Throwable t)
{

View File

@ -642,7 +642,7 @@ 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 IAuditLogger newAuditLogger(String className, Map<String, String> parameters) throws ConfigurationException
@ -652,8 +652,9 @@ public class FBUtilities
try
{
Class<?> auditLoggerClass = FBUtilities.classForName(className, "Audit logger");
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)
{
@ -668,12 +669,16 @@ public class FBUtilities
try
{
Class<?> sslContextFactoryClass = Class.forName(className);
return (ISslContextFactory) sslContextFactoryClass.getConstructor(Map.class).newInstance(parameters);
Class<? extends ISslContextFactory> sslContextFactoryClass =
FBUtilities.classForNameWithoutInitialization(className, "ISslContextFactory", ISslContextFactory.class);
return sslContextFactoryClass.getConstructor(Map.class).newInstance(parameters);
}
catch (Exception ex)
{
throw new ConfigurationException("Unable to create instance of ISslContextFactory for " + className, ex);
// Surface the underlying load failure (e.g. ClassNotFoundException) as the direct cause rather than the
// intermediate ConfigurationException that reports it.
Throwable cause = ex instanceof ConfigurationException && ex.getCause() != null ? ex.getCause() : ex;
throw new ConfigurationException("Unable to create instance of ISslContextFactory for " + className, cause);
}
}
@ -684,8 +689,9 @@ public class FBUtilities
if (!className.contains("."))
className = "org.apache.cassandra.security." + className;
Class<?> cryptoProviderClass = FBUtilities.classForName(className, "crypto provider class");
return (AbstractCryptoProvider) cryptoProviderClass.getConstructor(Map.class).newInstance(Collections.unmodifiableMap(parameters));
Class<? extends AbstractCryptoProvider> cryptoProviderClass =
FBUtilities.classForNameWithoutInitialization(className, "crypto provider class", AbstractCryptoProvider.class);
return cryptoProviderClass.getConstructor(Map.class).newInstance(Collections.unmodifiableMap(parameters));
}
catch (Exception e)
{
@ -698,6 +704,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.
@ -715,6 +723,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.
@ -724,6 +779,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");
@ -748,7 +822,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

@ -209,7 +209,7 @@ public class JMXServerUtils
String authzProxyClass = CASSANDRA_JMX_AUTHORIZER.getString();
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

@ -92,7 +92,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)
{
@ -111,7 +111,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

@ -0,0 +1,37 @@
/*
* 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.config;
/**
* Search-package fall-through probe. Shares its simple name ({@code ClassLoadingSearchProbe}) with
* {@link org.apache.cassandra.utils.ClassLoadingSearchProbe}, but unlike that one it IS a {@link Runnable}, so it
* represents the "correct type under a later search package" case. A wrong-type match under an earlier package must
* not abort the search before this class is found.
*/
public final class ClassLoadingSearchProbe implements Runnable
{
public ClassLoadingSearchProbe()
{
}
@Override
public void run()
{
}
}

View File

@ -40,8 +40,11 @@ import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.PathUtils;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.security.EncryptionContext;
import org.apache.cassandra.security.EncryptionContextGenerator;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import org.assertj.core.api.Assertions;
import org.mockito.MockedStatic;
@ -66,6 +69,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

@ -26,7 +26,10 @@ import org.junit.Test;
import org.apache.cassandra.auth.AllowAllAuthorizer;
import org.apache.cassandra.auth.IAuthorizer;
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.assertNull;
@ -98,4 +101,49 @@ public class ParameterizedClassTest
.hasMessageContaining("Simulated failure")
.isInstanceOf(ConfigurationException.class);
}
@Test
public void testTypedNewInstanceRejectsWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
ParameterizedClass parameterizedClass = new ParameterizedClass(ClassLoadingTestNonAssignable.class.getName());
assertThatThrownBy(() -> ParameterizedClass.newInstance(parameterizedClass, null, Runnable.class))
.hasMessageContaining("must extend or implement " + Runnable.class.getName())
.isInstanceOf(ConfigurationException.class);
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testTwoArgNewInstanceDoesNotInitializeAtLoadTime()
{
// The 2-arg overload performs no type check, but it must still load without running <clinit>.
// ClassLoadingTestNonAssignable has no usable constructor, so instantiation fails -- but only after the
// class has been loaded; loading must not have run its static initializer.
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
ParameterizedClass parameterizedClass = new ParameterizedClass(ClassLoadingTestNonAssignable.class.getName());
assertThatThrownBy(() -> ParameterizedClass.newInstance(parameterizedClass, null))
.isInstanceOf(ConfigurationException.class);
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testNewInstanceSearchPackageFallThroughDoesNotAbortOnWrongType()
{
// org.apache.cassandra.utils.ClassLoadingSearchProbe (wrong type, not a Runnable) resolves under the earlier
// search package, while org.apache.cassandra.config.ClassLoadingSearchProbe (correct type, a Runnable)
// resolves under the later one. A wrong-type match under the earlier package must not abort the search.
ClassLoadingTestSupport.assertNotInitialized(org.apache.cassandra.utils.ClassLoadingSearchProbe.class);
ParameterizedClass parameterizedClass = new ParameterizedClass("ClassLoadingSearchProbe");
Runnable instance = ParameterizedClass.newInstance(parameterizedClass,
List.of("org.apache.cassandra.utils",
"org.apache.cassandra.config"),
Runnable.class);
assertNotNull(instance);
assertThat(instance).isInstanceOf(org.apache.cassandra.config.ClassLoadingSearchProbe.class);
// The wrong-type class that was probed under the earlier package must not have been initialized.
assertThat(ClassLoadingTestSupport.wasInitialized(org.apache.cassandra.utils.ClassLoadingSearchProbe.class)).isFalse();
}
}

View File

@ -25,12 +25,26 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.GuardrailsOptions;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import org.assertj.core.api.Assertions;
import static java.lang.String.format;
public class GuardrailsConfigProviderTest extends GuardrailTester
{
@Test
public void testBuildWrongTypeRejectedWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
Assertions.assertThatThrownBy(() -> GuardrailsConfigProvider.build(ClassLoadingTestNonAssignable.class.getName()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + GuardrailsConfigProvider.class.getName());
Assertions.assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testBuildCustom() throws Throwable
{

View File

@ -22,6 +22,7 @@ import com.google.common.collect.Lists;
import org.junit.BeforeClass;
import org.junit.Test;
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;
@ -31,6 +32,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;
import java.util.function.Consumer;
@ -42,6 +45,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,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.diag;
import java.io.Serializable;
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 Map.of();
}
}

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

@ -39,11 +39,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 createSasiAfterSai()
{

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

@ -39,7 +39,11 @@ import org.apache.cassandra.config.ParameterizedClass;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.repair.autorepair.AutoRepairConfig.Options;
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.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -515,4 +519,17 @@ public class AutoRepairConfigTest extends CQLTester
assertEquals(new DurationSpec.IntSecondsBound("3h"), config.global_settings.repair_session_timeout);
assertEquals(new DurationSpec.IntSecondsBound("24h"), config.global_settings.min_repair_interval);
}
@Test
public void testTokenRangeSplitterWrongTypeRejectedWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
ParameterizedClass pc = new ParameterizedClass(ClassLoadingTestNonAssignable.class.getName(), Collections.emptyMap());
assertThatThrownBy(() -> AutoRepairConfig.newAutoRepairTokenRangeSplitter(repairType, pc))
.isInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must extend or implement " + IAutoRepairTokenRangeSplitter.class.getName());
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
}

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

@ -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.cassandra.schema;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
public final class MemtableFactoryInvalidFieldType
{
public static final Object FACTORY = new Object();
static
{
ClassLoadingTestSupport.markInitialized(MemtableFactoryInvalidFieldType.class);
}
private MemtableFactoryInvalidFieldType()
{
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.Map;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
public final class MemtableFactoryInvalidReturnType
{
static
{
ClassLoadingTestSupport.markInitialized(MemtableFactoryInvalidReturnType.class);
}
private MemtableFactoryInvalidReturnType()
{
}
public static Object factory(Map<String, String> parameters)
{
return new Object();
}
}

View File

@ -18,6 +18,8 @@
package org.apache.cassandra.schema;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
@ -28,7 +30,10 @@ import org.apache.cassandra.config.InheritingClass;
import org.apache.cassandra.config.ParameterizedClass;
import org.apache.cassandra.db.memtable.SkipListMemtableFactory;
import org.apache.cassandra.exceptions.ConfigurationException;
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;
@ -44,6 +49,46 @@ public class MemtableParamsTest
assertEquals(ImmutableMap.of("default", DEFAULT), map);
}
@Test
public void testInvalidFactoryMethodDoesNotInitializeClass() throws Exception
{
ClassLoadingTestSupport.assertNotInitialized(MemtableFactoryInvalidReturnType.class);
assertThatThrownBy(() -> getMemtableFactory(MemtableFactoryInvalidReturnType.class))
.isInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must return");
assertThat(ClassLoadingTestSupport.wasInitialized(MemtableFactoryInvalidReturnType.class)).isFalse();
}
@Test
public void testInvalidFactoryFieldDoesNotInitializeClass() throws Exception
{
ClassLoadingTestSupport.assertNotInitialized(MemtableFactoryInvalidFieldType.class);
assertThatThrownBy(() -> getMemtableFactory(MemtableFactoryInvalidFieldType.class))
.isInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must be of type");
assertThat(ClassLoadingTestSupport.wasInitialized(MemtableFactoryInvalidFieldType.class)).isFalse();
}
private static void getMemtableFactory(Class<?> memtableClass) throws Exception
{
Method method = MemtableParams.class.getDeclaredMethod("getMemtableFactory", ParameterizedClass.class);
method.setAccessible(true);
try
{
method.invoke(null, new ParameterizedClass(memtableClass.getName(), null));
}
catch (InvocationTargetException e)
{
if (e.getCause() instanceof ConfigurationException)
throw (ConfigurationException) e.getCause();
throw e;
}
}
@Test
public void testDefaultRemapped()
{

View File

@ -0,0 +1,73 @@
/*
* 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.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
import org.apache.cassandra.utils.ClassLoadingTestSupport;
import static org.apache.cassandra.config.CassandraRelevantProperties.SCHEMA_UPDATE_HANDLER_FACTORY_CLASS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class SchemaUpdateHandlerFactoryProviderTest
{
@Test
public void testDefaultFactoryWhenPropertyUnset()
{
assertThat(SchemaUpdateHandlerFactoryProvider.instance.get()).isSameAs(DefaultSchemaUpdateHandlerFactory.instance);
}
@Test
public void testConfiguredFactoryResolves()
{
try (WithProperties properties = new WithProperties().set(SCHEMA_UPDATE_HANDLER_FACTORY_CLASS, DefaultSchemaUpdateHandlerFactory.class.getName()))
{
assertThat(SchemaUpdateHandlerFactoryProvider.instance.get()).isInstanceOf(DefaultSchemaUpdateHandlerFactory.class);
}
}
@Test
public void testRejectsNonFactoryWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
try (WithProperties properties = new WithProperties().set(SCHEMA_UPDATE_HANDLER_FACTORY_CLASS, ClassLoadingTestNonAssignable.class.getName()))
{
assertThatThrownBy(() -> SchemaUpdateHandlerFactoryProvider.instance.get())
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + SchemaUpdateHandlerFactory.class.getName());
}
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
}
@Test
public void testUnknownFactoryClassRejected()
{
try (WithProperties properties = new WithProperties().set(SCHEMA_UPDATE_HANDLER_FACTORY_CLASS, "does.not.ExistFactory"))
{
assertThatThrownBy(() -> SchemaUpdateHandlerFactoryProvider.instance.get())
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("Unable to find schema update handler factory class");
}
}
}

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

@ -166,7 +166,7 @@ public class CryptoProviderTest
DatabaseDescriptor.getRawConfig().crypto_provider = new ParameterizedClass(DefaultCryptoProvider.class.getName(),
of("k1", "v1", "k2", "v2"));
fbUtilitiesMock.when(() -> FBUtilities.classForName(DefaultCryptoProvider.class.getName(), "crypto provider class"))
fbUtilitiesMock.when(() -> FBUtilities.classForNameWithoutInitialization(DefaultCryptoProvider.class.getName(), "crypto provider class", AbstractCryptoProvider.class))
.thenThrow(new RuntimeException("exception from test"));
fbUtilitiesMock.when(() -> FBUtilities.newCryptoProvider(anyString(), anyMap())).thenCallRealMethod();

View File

@ -34,9 +34,12 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.TriggerMetadata;
import org.apache.cassandra.schema.Triggers;
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;
@ -364,4 +367,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,33 @@
/*
* 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;
/**
* Search-package fall-through probe. Shares its simple name ({@code ClassLoadingSearchProbe}) with
* {@link org.apache.cassandra.config.ClassLoadingSearchProbe}, but is NOT a {@link Runnable}, so it represents the
* "wrong type under an earlier search package" case. Its static initializer records initialization so tests can
* confirm the search resolves without initializing this class.
*/
public final class ClassLoadingSearchProbe
{
static
{
ClassLoadingTestSupport.markInitialized(ClassLoadingSearchProbe.class);
}
}

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

@ -25,6 +25,7 @@ import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -45,6 +46,7 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.IAuditLogger;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.marshal.AbstractType;
@ -58,11 +60,16 @@ import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.OrderPreservingPartitioner;
import org.apache.cassandra.dht.RandomPartitioner;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.security.AbstractCryptoProvider;
import org.apache.cassandra.security.ISslContextFactory;
import static org.apache.cassandra.utils.FBUtilities.parseKernelVersion;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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.fail;
public class FBUtilitiesTest
@ -70,6 +77,73 @@ public class FBUtilitiesTest
public static final Logger LOGGER = LoggerFactory.getLogger(FBUtilitiesTest.class);
@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 testNewSslContextFactoryRejectsWrongTypeWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.newSslContextFactory(ClassLoadingTestNonAssignable.class.getName(), Collections.emptyMap()))
.isInstanceOf(ConfigurationException.class)
.hasStackTraceContaining("must extend or implement " + ISslContextFactory.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@Test
public void testNewCryptoProviderRejectsWrongTypeWithoutInitializing()
{
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
assertThatThrownBy(() -> FBUtilities.newCryptoProvider(ClassLoadingTestNonAssignable.class.getName(), Collections.emptyMap()))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("must extend or implement " + AbstractCryptoProvider.class.getName());
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
}
@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

@ -32,6 +32,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;
import static java.lang.String.format;
import static org.apache.cassandra.stress.settings.SettingsCredentials.CQL_PASSWORD_PROPERTY_KEY;
@ -89,9 +90,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))
{