1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 10:38:19 +08:00

feat(sql): Try to implement sql source

This commit is contained in:
2025-02-27 13:32:12 +08:00
parent f74d5d29f9
commit 844cbfab53
18 changed files with 526 additions and 159 deletions
@@ -4,7 +4,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Objects;
/**
@@ -30,6 +29,7 @@ public abstract class ValueType<T> {
public static final ValueType<Character> CHAR = ofPrimitiveType(Character.class);
public static final ValueType<Character> CHAR_TYPE = ofPrimitiveType(char.class);
public static final ValueType<?>[] PRIMITIVE_TYPES = {
STRING, INTEGER, LONG, DOUBLE, FLOAT, BOOLEAN, BYTE, SHORT, CHAR,
INTEGER_TYPE, LONG_TYPE, DOUBLE_TYPE, FLOAT_TYPE, BOOLEAN_TYPE, BYTE_TYPE, SHORT_TYPE, CHAR_TYPE
@@ -157,7 +157,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName());
sb.append(raw.getClass().getName());
sb.append('<');
Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) {
@@ -166,6 +166,7 @@ public abstract class ValueType<T> {
}
sb.append(args[i].getTypeName());
}
sb.append('>');
return sb.toString();
}
return type.getTypeName();
@@ -6,9 +6,11 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -18,6 +20,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -51,7 +55,7 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options;
}
public <O> O option(@NotNull ConfigurationOption<O> option) {
public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
@@ -66,14 +70,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) {
return extractMetadata(type, Objects::nonNull);
}
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type, @NotNull Predicate<M> filter) {
Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type);
if (data != null) metas.put(entry.getKey(), data);
if (filter.test(data)) metas.put(entry.getKey(), data);
}
return Collections.unmodifiableMap(metas);
}
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() {
return this.adapters;
}
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface ConfigInitializeHandler<T> {
public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() {
return (provider, path, value) -> {
static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value, instace) -> {
};
}
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path, @NotNull T value) throws Exception;
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path,
@NotNull T value, @Nullable V instance) throws Exception;
default ConfigInitializeHandler<T> andThen(ConfigInitializeHandler<T> after) {
return (provider, path, value) -> {
whenInitialize(provider, path, value);
after.whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value, instance) -> {
whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value, instance);
};
}
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) {
return (provider, path, value) -> {
before.whenInitialize(provider, path, value);
whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value, instance);
};
}
@@ -21,18 +21,18 @@ import java.util.function.Function;
public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer;
protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
}
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer;
this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer;
}
@@ -44,34 +44,34 @@ public class ConfigurationInitializer {
return pathGenerator;
}
public ConfigInitializeHandler<Field> fieldInitializer() {
return fieldInitializer;
public ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer() {
return valueInitializer;
}
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = fieldInitializer;
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.valueInitializer = fieldInitializer;
}
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() {
public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return classInitializer;
}
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = classInitializer;
}
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(fieldInitializer);
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.valueInitializer = this.valueInitializer.andThen(fieldInitializer);
}
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = this.classInitializer.andThen(classInitializer);
}
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> {
appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -81,7 +81,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> {
appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -123,7 +123,7 @@ public class ConfigurationInitializer {
@Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(holder, parentPath, root.getClass(), configField);
try {
this.classInitializer.whenInitialize(holder, path, root.getClass());
this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) {
e.printStackTrace();
}
@@ -140,7 +140,7 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField);
try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz);
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) {
e.printStackTrace();
}
@@ -169,15 +169,15 @@ public class ConfigurationInitializer {
String path = getFieldPath(holder, parent, field);
if (path == null) return;
value.initialize(holder, path);
holder.metadata(path).set(StandardMeta.UNIT, true); // Mark the minimal config value unit.
try {
this.fieldInitializer.whenInitialize(holder, path, field);
} catch (Exception e) {
e.printStackTrace();
}
holder.metadata(path).set(StandardMeta.VALUE, value); // Mark the minimal config value unit.
if (holder.option(StandardOptions.SET_DEFAULTS)) {
value.setDefault(); // Set default value.
}
try {
this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) {
e.printStackTrace();
}
if (holder.option(StandardOptions.PRELOAD)) {
value.get(); // Preload the value by calling #get method.
}
@@ -1,10 +1,12 @@
package cc.carm.lib.configuration.source.meta;
import cc.carm.lib.configuration.value.ConfigValue;
public interface StandardMeta {
/**
* To mark the {@link cc.carm.lib.configuration.value.ConfigValue} as a minimal unit path.
* To mark the {@link ConfigValue} instance of specific path.
*/
ConfigurationMetadata<Boolean> UNIT = ConfigurationMetadata.of(false);
ConfigurationMetadata<ConfigValue<?>> VALUE = ConfigurationMetadata.of();
}
@@ -20,7 +20,6 @@ public class ValueManifest<T> {
protected @NotNull Supplier<@Nullable T> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null);
}
@@ -93,12 +92,12 @@ public class ValueManifest<T> {
public @NotNull String path() {
if (path != null) return path;
else throw new IllegalStateException("No section path provided.");
else throw new IllegalStateException("No section path provided for Value(" + type() + ").");
}
public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.holder;
throw new IllegalStateException("Value does not have a provider.");
throw new IllegalStateException("Value(" + type() + ") does not have a provider.");
}
public @NotNull ConfigureSource<?, ?, ?> config() {
@@ -118,7 +117,7 @@ public class ValueManifest<T> {
}
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
};
}
@@ -7,7 +7,7 @@ import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration {
public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver")
@HeaderComments({
@@ -16,18 +16,18 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver",
})
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver"
);
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
protected static String buildJDBC() {
static String buildJDBC() {
return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get());
}
@@ -81,6 +81,16 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
for (Map.Entry<String, Object> entry : this.data.entrySet()) {
if (entry.getValue() instanceof AbstractMapSection<?>) {
output.put(entry.getKey(), ((AbstractMapSection<?>) entry.getValue()).rawMap());
} else if (entry.getValue() instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof AbstractMapSection<?>) {
list.add(((AbstractMapSection<?>) obj).rawMap());
} else {
list.add(obj);
}
}
output.put(entry.getKey(), list);
} else {
output.put(entry.getKey(), entry.getValue());
}
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
*
* @return the version of the configuration field
*/
@Range(from = 0, to = Integer.MAX_VALUE)
@Range(from = 0, to = 65535)
int value() default 0;
}
@@ -1,13 +0,0 @@
package cc.carm.lib.configuration.option;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface VersionedOptions {
/**
* Whether to set newer defaults when a {@link cc.carm.lib.configuration.value.ConfigValue}'s marked version
* is newer than the current in storage.
*/
ConfigurationOption<Boolean> RESET_NEWER_DEFAULTS = ConfigurationOption.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.commentable;
package cc.carm.lib.configuration.versioned;
import cc.carm.lib.configuration.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
}
static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value);
initializer.appendFieldInitializer((holder, path, field, value) -> {
ConfigVersion annotation = field.getAnnotation(ConfigVersion.class);
if (annotation == null || value == null) return;
int currentVersion = annotation.value();
int savedVersion = holder.metadata(path).get(VERSION, 0);
if (currentVersion == savedVersion) return;
if (currentVersion > savedVersion) { // This values updated.
value.setDefault(true); // Mark as default value, force write.
}
holder.metadata(path).set(VERSION, currentVersion);
});
}
}
+1 -1
View File
@@ -27,7 +27,7 @@
<module>providers/yaml</module>
<module>providers/gson</module>
<!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>-->
<module>providers/sql</module>
<module>providers/mongodb</module>
<module>demo</module>
+4 -4
View File
@@ -10,10 +10,10 @@ CREATE TABLE IF NOT EXISTS conf
`path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath)
`type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...)
`value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式)
`inline_comment` TEXT, # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT, # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT, # 配置项的描述,本质是部注释
`version` INT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`inline_comment` TEXT comment 'usage', # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT comment 'description', # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT comment 'example', # 配置项的参考,本质是部注释
`version` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`namespace`, `path`)
+20 -5
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.8</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -15,7 +15,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<log4j.version>2.24.3</log4j.version>
<deps.mysql-driver.version>8.0.26</deps.mysql-driver.version>
<deps.log4j.version>2.24.3</deps.log4j.version>
</properties>
<artifactId>easyconfiguration-sql</artifactId>
@@ -77,21 +78,35 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
<version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
<version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${deps.mysql-driver.version}</version>
<scope>test</scope>
</dependency>
@@ -0,0 +1,146 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.easysql.api.SQLManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.HashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SQLConfigFactory extends ConfigurationFactory<SQLSource, ConfigurationHolder<SQLSource>, SQLConfigFactory> {
public static SQLConfigFactory from(@NotNull Supplier<@NotNull SQLManager> managerSupplier) {
return new SQLConfigFactory(managerSupplier);
}
public static SQLConfigFactory from(@NotNull SQLManager manager) {
return from(() -> manager);
}
protected @NotNull Supplier<SQLManager> managerSupplier;
protected Supplier<Gson> gsonSupplier = () -> SQLSource.DEFAULT_GSON;
protected HashMap<Integer, SQLValueResolver<?>> resolvers = new HashMap<>(SQLValueResolver.STANDARD_RESOLVERS);
protected String tableName = "configs";
protected String namespace = "default";
public SQLConfigFactory(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
}
@Override
protected SQLConfigFactory self() {
return this;
}
public SQLConfigFactory manager(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
return self();
}
public SQLConfigFactory manager(@NotNull SQLManager manager) {
return manager(() -> manager);
}
public SQLConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public SQLConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
public SQLConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public SQLConfigFactory resolver(@Range(from = 0, to = 255) int type, @NotNull SQLValueResolver<?> resolver) {
this.resolvers.put(type, resolver);
return self();
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser) {
return resolver(id, ValueType.of(clazz), parser);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser) {
return resolver(id, new SQLValueResolver<T>(type) {
@Override
public @NotNull T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return parser.handle(data);
}
});
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<Object, String> serializer) {
return resolver(id, ValueType.of(clazz), parser, serializer);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<Object, String> serializer) {
return resolver(id, new SQLValueResolver<T>(type) {
@Override
public @NotNull T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return parser.handle(data);
}
@Override
public @NotNull String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) throws Exception {
return serializer.handle(value);
}
});
}
public SQLConfigFactory tableName(@NotNull String tableName) {
this.tableName = tableName;
return self();
}
public SQLConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return self();
}
@Override
public @NotNull ConfigurationHolder<SQLSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
SQLManager manager = this.managerSupplier.get();
if (manager == null) throw new NullPointerException("No SQLManager instance provided.");
Commentable.registerMeta(this.initializer);
return new ConfigurationHolder<SQLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final SQLSource source = new SQLSource(
this, System.currentTimeMillis(),
gson, manager, resolvers, tableName, namespace
);
@Override
public @NotNull SQLSource config() {
return source;
}
};
}
}
@@ -1,53 +1,63 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLQuery;
import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.enums.IndexType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.*;
public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSource> {
public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Object>, SQLSource> {
protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.create();
.serializeNulls().disableHtmlEscaping().create();
protected final @NotNull Gson gson;
protected final @NotNull SQLManager sqlManager;
protected final @NotNull String namespace;
protected final @NotNull SQLTable table;
protected final @NotNull Set<String> updated = new HashSet<>();
protected MemorySection rootSection;
protected final @NotNull Map<Integer, SQLValueResolver<?>> resolvers;
protected SourcedSection rootSection;
public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis,
@NotNull Gson gson, @NotNull SQLManager sqlManager, @NotNull String tableName, @NotNull String namespace) {
@NotNull Gson gson, @NotNull SQLManager sqlManager,
@NotNull Map<Integer, SQLValueResolver<?>> resolvers,
@NotNull String tableName, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.gson = gson;
this.sqlManager = sqlManager;
this.resolvers = resolvers;
this.namespace = namespace;
this.table = SQLTable.of(tableName, builder -> {
builder.addColumn("namespace", "VARCHAR(32) NOT NULL");
builder.addColumn("path", "VARCHAR(96) NOT NULL");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("value", "TEXT");
builder.addColumn("inline_comment", "TEXT");
builder.addColumn("header_comments", "MEDIUMTEXT");
builder.addColumn("footer_comments", "MEDIUMTEXT");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0");
builder.addColumn("create_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
builder.addColumn("update_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
builder.addColumn("create_time", "TIMESTAMP NOT NULL");
builder.addColumn("update_time", "TIMESTAMP NOT NULL");
builder.setIndex(
IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(),
@@ -55,82 +65,131 @@ public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSour
);
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
});
try {
this.table.create(this.sqlManager);
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected SQLSource self() {
protected @NotNull SQLSource self() {
return this;
}
public @NotNull Gson gson() {
return gson;
}
@Override
public @NotNull Map<?, ?> original() {
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull MemorySection section() {
public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
}
@Override
public void save() throws Exception {
if (this.updated.isEmpty()) return; // Nothing to save
LocalDateTime time = LocalDateTime.now(); // Update time
List<Object[]> dataValues = new ArrayList<>();
Date date = new Date(); // Update time
List<Object[]> values = new ArrayList<>();
SourcedSection section = section();
Map<String, ConfigValue<?>> values = holder().registeredValues();
for (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {
@NotNull String path = entry.getKey();
@NotNull ConfigValue<?> value = entry.getValue();
for (String path : this.updated) {
Object value = get(path);
try {
int typeID = typeOf(entry.getValue());
String data = null;
// if (value instanceof SQLConfigWrapper) {
// value = getSourceMap(((SQLConfigWrapper) value).getSource());
// }
//
// SQLValueResolver<?> type = SQLValueTypes.get(value.getClass());
// if (type != null) {
// values.add(new Object[]{
// this.namespace, path, date,
// type.getID(), type.serializeObject(value),
// getComments().getInlineComment(path),
// GSON.toJson(getComments().getHeaderComment(path))
// });
// }
if (value != null) {
if (value instanceof SourcedSection) {
data = serialize(typeID, ((SourcedSection) value).rawMap());
} else if (value instanceof List) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) value) {
if (obj instanceof SourcedSection) {
list.add(((SourcedSection) obj).rawMap());
} else {
list.add(obj);
}
}
data = serialize(typeID, list);
} else {
data = serialize(typeID, value);
}
}
this.updated.clear();
int version = holder().metadata(path).get(VersionedMetaTypes.VERSION, 0);
dataValues.add(new Object[]{
namespace, path, time, version, typeID, data,
Commentable.getInlineComment(holder(), path),
gson.toJson(Commentable.getHeaderComments(holder(), path)),
gson.toJson(Commentable.getFooterComments(holder(), path))
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
this.table.createReplaceBatch()
.setColumnNames("namespace", "path", "update_time", "type", "value", "inline_comment", "header_comments")
.setAllParams(values)
.execute();
.setColumnNames(
"namespace", "path", "update_time", "version", "type", "value",
"inline_comment", "header_comments", "footer_comments"
).setAllParams(dataValues).execute();
}
@Override
protected void onReload() throws Exception {
LinkedHashMap<String, Object> values = new LinkedHashMap<>();
// try (SQLQuery query = this.table.createQuery()
// .addCondition("namespace", namespace)
// .build().execute()) {
// ResultSet rs = query.getResultSet();
// while (rs.next()) {
// String path = rs.getString("path");
// int type = rs.getInt("type");
// try {
// SQLValueResolver<?> resolver = SQLValueTypes.get(type);
// if (resolver == null) throw new IllegalStateException("No resolver for type #" + type);
// String value = rs.getString("value");
// values.put(path, resolver.resolve(value));
//
// loadInlineComment(path, rs.getString("inline_comment"));
// loadHeaderComment(path, rs.getString("header_comments"));
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// }
//
// this.rootConfiguration = new SQLConfigWrapper(this, values);
Map<String, Object> loaded = new LinkedHashMap<>();
try (SQLQuery query = this.table.createQuery()
.addCondition("namespace", namespace)
.build().execute()) {
ResultSet rs = query.getResultSet();
while (rs.next()) {
String path = rs.getString("path");
if (path == null) continue; // Path should be not null
int ver = rs.getInt("version");
try {
Object val = parse(rs.getInt("type"), rs.getString("value"));
System.out.println("Path <" + path + "> Value = " + val);
loaded.put(path, val);
if (ver != 0) {
holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
this.rootSection = SourcedSection.root(this, loaded);
}
protected @Nullable Object parse(int type, String value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.resolve(holder(), value);
}
protected @Nullable String serialize(int type, @NotNull Object value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.serialize(holder(), value);
}
protected int typeOf(@NotNull ValueType<?> value) {
return this.resolvers.entrySet().stream()
.filter(entry -> entry.getValue().isTypeOf(value))
.findFirst().map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value));
}
}
@@ -0,0 +1,103 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class SQLValueResolver<T> {
public static final @NotNull SQLValueResolver<String> STRING = SQLValueResolver.of(ValueType.STRING, s -> s);
public static final @NotNull SQLValueResolver<Byte> BYTE = SQLValueResolver.of(ValueType.BYTE, Byte::parseByte);
public static final @NotNull SQLValueResolver<Short> SHORT = SQLValueResolver.of(ValueType.SHORT, Short::parseShort);
public static final @NotNull SQLValueResolver<Integer> INTEGER = SQLValueResolver.of(ValueType.INTEGER, Integer::parseInt);
public static final @NotNull SQLValueResolver<Long> LONG = SQLValueResolver.of(ValueType.LONG, Long::parseLong);
public static final @NotNull SQLValueResolver<Float> FLOAT = SQLValueResolver.of(ValueType.FLOAT, Float::parseFloat);
public static final @NotNull SQLValueResolver<Double> DOUBLE = SQLValueResolver.of(ValueType.DOUBLE, Double::parseDouble);
public static final @NotNull SQLValueResolver<Boolean> BOOLEAN = SQLValueResolver.of(ValueType.BOOLEAN, Boolean::parseBoolean);
public static final @NotNull SQLValueResolver<Character> CHAR = SQLValueResolver.of(ValueType.CHAR, s -> s.charAt(0));
public static final @NotNull SQLValueResolver<List<?>> LIST = new SQLValueResolver<List<?>>(new ValueType<List<?>>() {
}) {
@Override
public @Nullable List<?> resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return holder.config().gson().fromJson(data, List.class);
}
@Override
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) {
return holder.config().gson().toJson(value);
}
};
public static final @NotNull SQLValueResolver<Map<?, ?>> MAP = new SQLValueResolver<Map<?, ?>>(new ValueType<Map<?, ?>>() {
}) {
@Override
public @Nullable Map<?, ?> resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return holder.config().gson().fromJson(data, LinkedHashMap.class);
}
@Override
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) {
return holder.config().gson().toJson(value);
}
};
public static final @NotNull Map<Integer, SQLValueResolver<?>> STANDARD_RESOLVERS = Collections.unmodifiableMap(standards());
static Map<Integer, SQLValueResolver<?>> standards() {
Map<Integer, SQLValueResolver<?>> map = new LinkedHashMap<>();
map.put(0, STRING);
map.put(1, BYTE);
map.put(2, SHORT);
map.put(3, INTEGER);
map.put(4, LONG);
map.put(5, FLOAT);
map.put(6, DOUBLE);
map.put(7, BOOLEAN);
map.put(8, CHAR);
map.put(10, LIST);
map.put(11, MAP);
return map;
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type, @NotNull DataFunction<String, V> resolver) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return resolver.handle(data);
}
};
}
protected final @NotNull ValueType<T> type;
protected SQLValueResolver(@NotNull ValueType<T> type) {
this.type = type;
}
public @NotNull ValueType<T> getType() {
return type;
}
public boolean isTypeOf(@NotNull Class<?> clazz) {
return type.isSubtypeOf(clazz);
}
public boolean isTypeOf(@NotNull ValueType<?> valueType) {
return valueType.equals(type);
}
public abstract @Nullable T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception;
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) throws Exception {
return String.valueOf(value);
}
}
@@ -1,27 +1,45 @@
package config;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.sql.SQLConfigFactory;
import cc.carm.lib.easysql.EasySQL;
import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.beecp.BeeDataSourceConfig;
import org.junit.Test;
import java.io.File;
public class SQLConfigTest {
@Test
public void test() {
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/sql.json")).build();
gsonHolder.initialize(DatabaseConfiguration.class);
BeeDataSourceConfig config = new BeeDataSourceConfig();
config.setDriverClassName("org.h2.Driver");
config.setJdbcUrl("jdbc:h2:file:target/test;DB_CLOSE_DELAY=-1;MODE=MySQL;");
config.setDriverClassName(DatabaseConfiguration.DRIVER_NAME.resolve());
config.setJdbcUrl(DatabaseConfiguration.buildJDBC());
config.setUsername(DatabaseConfiguration.USERNAME.resolve());
config.setPassword(DatabaseConfiguration.PASSWORD.resolve());
SQLManager manager = EasySQL.createManager(config);
manager.setDebugMode(true);
// SQLConfigProvider provider = EasyConfiguration.from(manager, "conf_test", "TESTING");
//
// ConfigurationTest.testDemo(provider);
// ConfigurationTest.testInner(provider);
//
// ConfigurationTest.save(provider);
//
// EasySQL.shutdownManager(manager);
ConfigurationHolder<?> holder = SQLConfigFactory.from(manager)
.tableName("test_configs")
.namespace("testing")
.build();
ConfigurationTest.testDemo(holder);
ConfigurationTest.testInner(holder);
ConfigurationTest.save(holder);
EasySQL.shutdownManager(manager);
}
}