From d4890ea48fae973bd6602ce8f25fc7125ef96d9c Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Fri, 23 Dec 2022 14:30:26 +0100 Subject: [PATCH] feat(api)!: Move `TelestionVerticle` configuration logic into a dedicated 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 getConfigType()` was moved to `Class VerticleConfigStrategy.getConfigType(Class> clazz)` --- .../api/verticle/TelestionVerticle.java | 163 ++++-------------- .../api/verticle/VerticleConfigStrategy.java | 148 ++++++++++++++++ 2 files changed, 186 insertions(+), 125 deletions(-) create mode 100644 telestion-api/src/main/java/de/wuespace/telestion/api/verticle/VerticleConfigStrategy.java diff --git a/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/TelestionVerticle.java b/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/TelestionVerticle.java index 06cf032c..330c9388 100644 --- a/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/TelestionVerticle.java +++ b/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/TelestionVerticle.java @@ -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; /** @@ -21,88 +18,16 @@ * Ludwig Richter (@fussel178) */ public abstract class TelestionVerticle extends AbstractVerticle { - /** - * The default verticle configuration in a generic format. - */ - private JsonObject defaultGenericConfig = new JsonObject(); - /** - * The default verticle configuration in the Configuration type format.

- * Is null 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.

- * Is null 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 getConfigType() { - try { - String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName(); - Class clazz = Class.forName(className); - //noinspection unchecked - return (Class) 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 config; @Override public final void start(Promise startPromise) throws Exception { - updateConfigs(); + //noinspection unchecked + this.config = new VerticleConfigStrategy(super.config(), VerticleConfigStrategy.getConfigType(getClass())); // put general startup steps here onStart(startPromise); } @@ -178,18 +103,26 @@ public void onStop(Promise stopPromise) throws Exception { *

* 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(super.config(), defaultConfig, VerticleConfigStrategy.getConfigType(getClass())); } /** @@ -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.

- * Returns null 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.

- * Returns null 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 config() 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.

- * Returns null 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."); + } } } diff --git a/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/VerticleConfigStrategy.java b/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/VerticleConfigStrategy.java new file mode 100644 index 00000000..b90fb27b --- /dev/null +++ b/telestion-api/src/main/java/de/wuespace/telestion/api/verticle/VerticleConfigStrategy.java @@ -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 the type of the configuration + * + * @author Ludwig Richter (@fussel178), Pablo Klaschka (@pklaschka) + */ +public class VerticleConfigStrategy { + 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 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 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 the configuration type of the verticle class + * @param the verticle class extending {@link TelestionVerticle} + */ + @SuppressWarnings("unchecked") + public static > Class getConfigType(Class clazz) { + try { + var genericSuperclass = (ParameterizedType) clazz.getGenericSuperclass(); + var configurationTypeArgument = genericSuperclass.getActualTypeArguments()[0]; + var className = configurationTypeArgument.getTypeName(); + Class configClass = Class.forName(className); + return (Class) 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 the configuration type of the verticle class + */ + private static JsonObject getDefaultUntypedConfig(Class 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 configType) { + if (Objects.isNull(configType)) { + throw new IllegalArgumentException("Config type must not be null"); + } + } +}