Skip to content

Commit

Permalink
feat(api)!: Move TelestionVerticle configuration logic into a dedic…
Browse files Browse the repository at this point in the history
…ated strategy class and improve overall stability

BREAKING-CHANGE: Remove the second constructor from `TelestionVerticle` that allowed to skip loading loading the default config (`public TelestionVerticle(boolean skipDefaultConfigLoading)`)
BREAKING-CHANGE: In the `TelestionVerticle`, `getGenericConfig()` is now called `getUntypedConfig()`
BREAKING-CHANGE: In the `TelestionVerticle`, `getGenericDefaultConfig()` is now called `getUntypedDefaultConfig()`
BREAKING-CHANGE: In the `TelestionVerticle`, `Class<T> getConfigType()` was moved to `Class<T> VerticleConfigStrategy.getConfigType(Class<X extends TelestionVerticle<T>> clazz)`
  • Loading branch information
pklaschka committed Dec 23, 2022
1 parent e41e1db commit 8871be5
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;

/**
Expand All @@ -21,88 +18,16 @@
* Ludwig Richter (@fussel178)
*/
public abstract class TelestionVerticle<T extends TelestionConfiguration> extends AbstractVerticle {
/**
* The default verticle configuration in a generic format.
*/
private JsonObject defaultGenericConfig = new JsonObject();
/**
* The default verticle configuration in the Configuration type format.<p>
* Is <code>null</code> when no type via {@link #getConfigType()} is given.
*/
private T defaultConfig;

/**
* The verticle configuration in a generic format.
*/
private JsonObject genericConfig = new JsonObject();
/**
* The verticle configuration in the Configuration type format.<p>
* Is <code>null</code> when no type via {@link #getConfigType()} is given.
*/
private T config;

/**
* The default logger instance.
*/
protected final Logger logger = LoggerFactory.getLogger(getClass());

/**
* Get the Configuration Class type from the inheriting class.
*
* @return the Configuration Class type
*/
@SuppressWarnings("unchecked")
protected Class<T> getConfigType() {
try {
String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
Class<?> clazz = Class.forName(className);
//noinspection unchecked
return (Class<T>) clazz;
} catch (Exception e) {
logger.warn("Cannot get Class type from generic: {}", e.getMessage());
return null;
}
}

/**
* Creates a new Telestion verticle and tries to load the default configuration
* from the specified configuration class.
*
* @param skipDefaultConfigLoading when {@code true} the loading of the default configuration is skipped
*/
public TelestionVerticle(boolean skipDefaultConfigLoading) {
if (skipDefaultConfigLoading) {
return;
}
var configType = getConfigType();
if (Objects.isNull(configType)) {
return;
}

try {
var defaultConfig = configType.getConstructor().newInstance();
this.defaultConfig = defaultConfig;
this.defaultGenericConfig = defaultConfig.toJsonObject();
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
// no default configuration on configuration class found, ignoring
logger.info("No default configuration found for {}. " +
"Expected constructor with no arguments to exist on {}. " +
"Continuing without default configuration.",
getClass().getSimpleName(), getConfigType().getSimpleName());
}
}

/**
* Same as {@link TelestionVerticle#TelestionVerticle(boolean)}
* but enables loading of default configuration if possible.
*/
public TelestionVerticle() {
this(false);
}
private VerticleConfigStrategy<T> config;

@Override
public final void start(Promise<Void> startPromise) throws Exception {
updateConfigs();
//noinspection unchecked
this.config = new VerticleConfigStrategy<T>(super.config(), VerticleConfigStrategy.getConfigType(getClass()));
// put general startup steps here
onStart(startPromise);
}
Expand Down Expand Up @@ -178,18 +103,26 @@ public void onStop(Promise<Void> stopPromise) throws Exception {
* <p>
* This is the synchronous part to the {@link #onStop(Promise)} method.
*/
@SuppressWarnings("RedundantThrows")
public void onStop() throws Exception {
}

/**
* @see VerticleConfigStrategy#getDefaultConfig()
*/
public T getDefaultConfig() {
assertConfigStrategyObjectAvailable();
return config.getDefaultConfig();
}

/**
* Set the default verticle configuration and update the verticle configuration.
*
* @param defaultConfig the new default verticle configuration
*/
public void setDefaultConfig(JsonObject defaultConfig) {
this.defaultGenericConfig = defaultConfig;
this.defaultConfig = mapToConfiguration(defaultConfig);
updateConfigs();
//noinspection unchecked
this.config = new VerticleConfigStrategy<T>(super.config(), defaultConfig, VerticleConfigStrategy.getConfigType(getClass()));
}

/**
Expand All @@ -198,76 +131,56 @@ public void setDefaultConfig(JsonObject defaultConfig) {
* @param defaultConfig the new default verticle configuration
*/
public void setDefaultConfig(T defaultConfig) {
this.defaultConfig = defaultConfig;
this.defaultGenericConfig = defaultConfig.toJsonObject();
updateConfigs();
setDefaultConfig(defaultConfig.toJsonObject());
}

/**
* Get the default verticle configuration in the Configuration type format.<p>
* Returns <code>null</code> when no type via {@link #getConfigType()} is given.
*
* @return the default verticle configuration
*/
public T getDefaultConfig() {
return defaultConfig;
}

/**
* Get the default verticle configuration in a generic format.
*
* @return the default verticle configuration
* @see VerticleConfigStrategy#getUntypedDefaultConfig()
*/
public JsonObject getGenericDefaultConfig() {
return defaultGenericConfig;
public JsonObject getUntypedDefaultConfig() {
assertConfigStrategyObjectAvailable();
return config.getUntypedDefaultConfig();
}

/**
* Get the verticle configuration in the Configuration type format.<p>
* Returns <code>null</code> when no type via {@link #getConfigType()} is given.
*
* @return the verticle configuration
* @see VerticleConfigStrategy#getConfig()
*/
public T getConfig() {
return config;
assertConfigStrategyObjectAvailable();
return config.getConfig();
}

/**
* Get the verticle configuration in a generic format.
*
* @return the verticle configuration
* @see VerticleConfigStrategy#getUntypedConfig()
*/
public JsonObject getGenericConfig() {
return genericConfig;
public JsonObject getUntypedConfig() {
assertConfigStrategyObjectAvailable();
return config.getUntypedConfig();
}

/**
* Block the usage of <code>config()</code> in inheriting classes.
*
* @return the verticle configuration from vertx merged with the default configuration
* @deprecated The config() method is deprecated when extending {@link TelestionVerticle}.
* Please use {@link #getConfig()} instead.
*/
@Override
public final JsonObject config() {
return defaultGenericConfig.mergeIn(super.config());
}

/**
* Update the config representations based on the default verticle configuration.
*/
private void updateConfigs() {
genericConfig = config();
config = mapToConfiguration(genericConfig);
logger.warn("The config() method is deprecated. Please use getConfig() instead.");
return super.config();
}

/**
* Map a generic JSON object to the Configuration type.<p>
* Returns <code>null</code> when no type via {@link #getConfigType()} is given.
* Throws an error if the config strategy object is not available. This is the case if developers try to access the
* configuration before the verticle is started.
*
* @param object the generic JSON object to map
* @return the JSON object in the Configuration type format
* @throws IllegalStateException if the config strategy object is not available
*/
private T mapToConfiguration(JsonObject object) {
var type = getConfigType();
return type != null ? object.mapTo(type) : null;
private void assertConfigStrategyObjectAvailable() {
if (Objects.isNull(config)) {
throw new IllegalStateException("Trying to access config before it was initialized." +
" You can only access the config in onStart() or later.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package de.wuespace.telestion.api.verticle;

import io.vertx.core.json.JsonObject;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;

/**
* A strategy to handle the configuration of .
* @param <T> the type of the configuration
*
* @author Ludwig Richter (@fussel178), Pablo Klaschka (@pklaschka)
*/
public class VerticleConfigStrategy<T extends TelestionConfiguration> {
private final T config;
private final JsonObject untypedConfig;
private final T defaultConfig;
private final JsonObject untypedDefaultConfig;

/**
* Creates a new configuration strategy for a verticle.
* The default configuration gets inferred from the {@code configType} parameter.
* The inferred default configuration is used to fill the configuration with default values.
*
* @param untypedConfig the untyped configuration
* @param configType the type of the configuration
*/
public VerticleConfigStrategy(JsonObject untypedConfig, Class<T> configType) {
this(untypedConfig, getDefaultUntypedConfig(configType), configType);
}

/**
* Creates a new configuration strategy for a verticle.
* The {@code untypedDefaultConfig} parameter is used to fill the configuration with default values.
* @param untypedConfig the untyped configuration
* @param untypedDefaultConfig the default untyped configuration
* @param configType the type of the configuration
*/
public VerticleConfigStrategy(JsonObject untypedConfig, JsonObject untypedDefaultConfig, Class<T> configType) {
assertConfigTypeNonNull(configType);

this.untypedConfig = untypedConfig;
this.untypedDefaultConfig = untypedDefaultConfig;
this.defaultConfig = Objects.isNull(untypedDefaultConfig) ? null : untypedDefaultConfig.mapTo(configType);

var combined = new JsonObject().mergeIn(untypedDefaultConfig).mergeIn(untypedConfig);
this.config = combined.mapTo(configType);
}

/**
* Returns the configuration type of the verticle class.
* @param clazz the verticle class extending {@link TelestionVerticle}
* @return the configuration type class name of the verticle class
* @param <K> the configuration type of the verticle class
* @param <T> the verticle class extending {@link TelestionVerticle}
*/
@SuppressWarnings("unchecked")
public static <K extends TelestionConfiguration, T extends TelestionVerticle<K>> Class<K> getConfigType(Class<T> clazz) {
try {
var genericSuperclass = (ParameterizedType) clazz.getGenericSuperclass();
var configurationTypeArgument = genericSuperclass.getActualTypeArguments()[0];
var className = configurationTypeArgument.getTypeName();
Class<?> configClass = Class.forName(className);
return (Class<K>) configClass;
} catch (Exception e) {
return null;
}
}

/**
* Returns the default configuration of the verticle class.
* @param configType the configuration type of the verticle class
* @return the default configuration of the verticle class. {@code null} if the configuration class has no default constructor.
* @param <T> the configuration type of the verticle class
*/
private static <T extends TelestionConfiguration> JsonObject getDefaultUntypedConfig(Class<T> configType) {
try {
var defaultConfig = configType.getConstructor().newInstance();
return defaultConfig.toJsonObject();
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException | NullPointerException e) {
return null;
}
}

/**
* Get the verticle configuration in the Configuration type format.
*
* @return the verticle configuration
*/
public T getConfig() {
assertConfigObjectNonNull(config);
return config;
}

/**
* Get the verticle configuration in a generic format.
*
* @return the verticle configuration
*/
public JsonObject getUntypedConfig() {
assertConfigObjectNonNull(untypedConfig);
return untypedConfig;
}

/**
* Get the default verticle configuration in the Configuration type format.
*
* @return the default verticle configuration
*/
public T getDefaultConfig() {
assertConfigObjectNonNull(defaultConfig);
return defaultConfig;
}

/**
* Get the default verticle configuration in a generic format.
*
* @return the default verticle configuration
*/
public JsonObject getUntypedDefaultConfig() {
assertConfigObjectNonNull(untypedDefaultConfig);
return untypedDefaultConfig;
}

/**
* Asserts that the configuration is not null.
* @param object the object to check
* @throws IllegalStateException if the object is null
*/
private void assertConfigObjectNonNull(Object object) {
if (Objects.isNull(object)) {
throw new IllegalStateException("Trying to access config that is null");
}
}

/**
* Asserts that the configuration type is not null.
* @param configType the configuration type to check
* @throws IllegalStateException if the configuration type is null
*/
private void assertConfigTypeNonNull(Class<T> configType) {
if (Objects.isNull(configType)) {
throw new IllegalArgumentException("Config type must not be null");
}
}
}

0 comments on commit 8871be5

Please sign in to comment.