Skip to content

Commit

Permalink
Moved database-core to core and added credential reader
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim203 committed Feb 11, 2024
1 parent 2d85131 commit aad5cb6
Show file tree
Hide file tree
Showing 20 changed files with 279 additions and 52 deletions.
1 change: 0 additions & 1 deletion ap/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
dependencies {
implementation(projects.core)
implementation(projects.databaseCore)

implementation(projects.databaseSql)

Expand Down
2 changes: 1 addition & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
dependencies {
implementation(projects.databaseCore)
compileOnlyApi(libs.hikari.cp)
compileOnly(libs.checker.qual)

testImplementation(projects.databaseSql)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 GeyserMC <https://geysermc.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/DatabaseUtils
*/
package org.geysermc.databaseutils;

import static org.geysermc.databaseutils.util.StringUtils.nullToEmpty;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Properties;
import org.geysermc.databaseutils.sql.SqlDialect;

final class CredentialsFileHandler {
public DatabaseConfig handle(SqlDialect dialect, Path credentialsFile) {
DatabaseConfig config = defaultValuesFor(dialect);
if (credentialsFile != null) {
if (Files.exists(credentialsFile)) {
config = readConfig(credentialsFile);
} else {
createConfig(config, credentialsFile);
}
}
return config;
}

private DatabaseConfig readConfig(Path credentialsFile) {
var properties = new Properties();
try (var reader = Files.newBufferedReader(credentialsFile)) {
properties.load(reader);
} catch (IOException exception) {
throw new IllegalStateException("Failed to load credentials!", exception);
}
return new DatabaseConfig(
properties.getProperty("url"),
properties.getProperty("username"),
properties.getProperty("password"),
Integer.parseInt(properties.getProperty("connectionPoolSize")));
}

private void createConfig(DatabaseConfig defaults, Path toStore) {
// not using properties here so that the values aren't escaped and there isn't a timestamp comment
var lines = new ArrayList<String>();
lines.add("# Database configuration");
lines.add("url=" + defaults.url());
lines.add("username=" + nullToEmpty(defaults.username()));
lines.add("password=" + nullToEmpty(defaults.password()));
lines.add("connectionPoolSize=" + defaults.connectionPoolSize());
try {
Files.write(toStore, lines);
} catch (IOException exception) {
throw new IllegalStateException("Failed to save credential defaults!", exception);
}
}

private DatabaseConfig defaultValuesFor(SqlDialect dialect) {
return switch (dialect) {
case H2 -> configFor("jdbc:h2:./database", "sa");
case SQL_SERVER -> configFor("jdbc:sqlserver://localhost;encrypt=true;integratedSecurity=true;");
case MYSQL -> configFor("jdbc:mysql://localhost/database");
case ORACLE_DATABASE -> configFor("jdbc:oracle:thin:@//localhost/service");
case POSTGRESQL -> configFor("jdbc:postgresql://localhost/database");
case SQLITE -> configFor("jdbc:sqlite:./database");
};
}

private DatabaseConfig configFor(String url) {
return configFor(url, null);
}

private DatabaseConfig configFor(String url, String username) {
// default pool size is 5
return new DatabaseConfig(url, username, null, 5);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
public abstract class Database {
protected ExecutorService service;

public void start(DatabaseConfig config, ExecutorService service) {
this.service = service;
public void start(DatabaseContext context) {
this.service = context.service();
}

public abstract void stop();
Expand Down
39 changes: 39 additions & 0 deletions core/src/main/java/org/geysermc/databaseutils/DatabaseConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2024 GeyserMC <https://geysermc.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/DatabaseUtils
*/
package org.geysermc.databaseutils;

public record DatabaseConfig(String url, String username, String password, int connectionPoolSize) {
public DatabaseConfig {
if (username != null && (username.isEmpty() || "null".equals(username))) {
username = null;
}
if (password != null && (password.isEmpty() || "null".equals(password))) {
password = null;
}

if (url == null || url.isEmpty()) throw new IllegalArgumentException("url cannot be null or empty");
if (connectionPoolSize <= 0) throw new IllegalArgumentException("connectionPoolSize has to be at least 1");
}
}
62 changes: 62 additions & 0 deletions core/src/main/java/org/geysermc/databaseutils/DatabaseContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 GeyserMC <https://geysermc.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/DatabaseUtils
*/
package org.geysermc.databaseutils;

import java.util.concurrent.ExecutorService;
import org.geysermc.databaseutils.codec.TypeCodecRegistry;
import org.geysermc.databaseutils.sql.SqlDialect;

public record DatabaseContext(
String url,
String username,
String password,
String poolName,
int connectionPoolSize,
SqlDialect dialect,
ExecutorService service,
TypeCodecRegistry registry) {
public DatabaseContext {
if (poolName == null || poolName.isEmpty())
throw new IllegalArgumentException("poolName cannot be null or empty");
if (dialect == null) throw new IllegalArgumentException("dialect cannot be null");
}

public DatabaseContext(
DatabaseConfig config,
String poolName,
SqlDialect dialect,
ExecutorService service,
TypeCodecRegistry registry) {
this(
config.url(),
config.username(),
config.password(),
poolName,
config.connectionPoolSize(),
dialect,
service,
registry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,13 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.databaseutils.codec.TypeCodecRegistry;

final class DatabaseLoader {
@SuppressWarnings({"unchecked"})
@NonNull StartResult startDatabase(DatabaseConfig config, ExecutorService service, TypeCodecRegistry registry) {
@NonNull StartResult startDatabase(DatabaseContext context) {
var database = DatabaseRegistry.firstPresentDatabase();
if (database == null) {
throw new IllegalStateException("Couldn't find any present database");
Expand All @@ -59,11 +58,11 @@ final class DatabaseLoader {
throw new RuntimeException("Something went wrong with the generated database implementation", exception);
}

if (hasAsync && service == null) {
if (hasAsync && context.service() == null) {
throw new IllegalStateException("Database has async methods but no ExecutorService was provided!");
}

database.start(config, service);
database.start(context);

try {
createEntitiesMethod.invoke(null, database);
Expand All @@ -73,7 +72,7 @@ final class DatabaseLoader {

var repositories = new ArrayList<IRepository<?>>();
for (var repositoryCreator : repositoryCreators) {
repositories.add(repositoryCreator.apply(database, registry));
repositories.add(repositoryCreator.apply(database, context.registry()));
}

return new StartResult(database, repositories);
Expand Down
62 changes: 47 additions & 15 deletions core/src/main/java/org/geysermc/databaseutils/DatabaseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,30 @@
*/
package org.geysermc.databaseutils;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.ExecutorService;
import org.geysermc.databaseutils.codec.TypeCodec;
import org.geysermc.databaseutils.codec.TypeCodecRegistry;
import org.geysermc.databaseutils.sql.SqlDialect;

public class DatabaseUtils {
private final TypeCodecRegistry registry;
private final DatabaseConfig config;
private final ExecutorService executorService;
private final DatabaseContext context;

private Database database = null;
private List<IRepository<?>> repositories;

private DatabaseUtils(TypeCodecRegistry registry, DatabaseConfig config, ExecutorService executorService) {
this.registry = registry;
this.config = config;
this.executorService = executorService;
private DatabaseUtils(DatabaseContext context) {
this.context = context;
}

public static Builder builder() {
return new Builder();
}

public List<IRepository<?>> start() {
var result = new DatabaseLoader().startDatabase(config, executorService, registry);
var result = new DatabaseLoader().startDatabase(context);
this.database = result.database();
this.repositories = result.repositories();
return result.repositories();
Expand Down Expand Up @@ -81,8 +79,12 @@ public static class Builder {
private String username;
private String password;
private String poolName;
private int connectionPoolSize;
private int connectionPoolSize = 0;
private SqlDialect dialect;

private Path credentialsFile;
private boolean useDefaultCredentials = true;

private ExecutorService executorService;

private Builder() {}
Expand Down Expand Up @@ -164,6 +166,27 @@ public Builder dialect(SqlDialect dialect) {
return this;
}

public Path credentialsFile() {
return credentialsFile;
}

public Builder credentialsFile(Path credentialsFile) {
if (credentialsFile != null && Files.exists(credentialsFile) && !Files.isRegularFile(credentialsFile)) {
throw new IllegalArgumentException("credentialsFile has to be a file, not a directory!");
}
this.credentialsFile = credentialsFile;
return this;
}

public boolean useDefaultCredentials() {
return useDefaultCredentials;
}

public Builder useDefaultCredentials(boolean useDefaultCredentials) {
this.useDefaultCredentials = useDefaultCredentials;
return this;
}

public ExecutorService executorService() {
return executorService;
}
Expand All @@ -174,12 +197,21 @@ public Builder executorService(ExecutorService executorService) {
}

public DatabaseUtils build() {
return new DatabaseUtils(
registry,
config != null
? config
: new DatabaseConfig(uri, username, password, poolName, connectionPoolSize, dialect),
executorService);
if (credentialsFile != null && !useDefaultCredentials) {
throw new IllegalStateException(
"Cannot use credentialsFile in combination with not using default credentials");
}

var actual = config;
if (credentialsFile != null) {
actual = new CredentialsFileHandler().handle(dialect, credentialsFile);
} else if (useDefaultCredentials) {
actual = new CredentialsFileHandler().handle(dialect, null);
} else if (config == null) {
actual = new DatabaseConfig(uri, username, password, connectionPoolSize);
}

return new DatabaseUtils(new DatabaseContext(actual, poolName, dialect, executorService, registry));
}
}
}
Loading

0 comments on commit aad5cb6

Please sign in to comment.