- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 21
GH-1147 Make UserManager use UserRepository #1147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
92868fe
              1a7dd18
              3c6ad2a
              7d3ee9e
              9414f7d
              b3efc5f
              2050371
              0cdf8b2
              11813d4
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -13,7 +13,7 @@ public class DatabaseConfig extends OkaeriConfig implements DatabaseSettings { | |
| @Comment({"Type of the database driver (e.g., SQLITE, H2, MYSQL, MARIADB, POSTGRESQL).", "Determines the " | ||
| + "database type " | ||
| + "to be used."}) | ||
| public DatabaseDriverType databaseType = DatabaseDriverType.SQLITE; | ||
| public DatabaseDriverType databaseType = DatabaseDriverType.MYSQL; | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm czemu tak? jak w testach coś nie gra to settuj fielda jest public | ||
|  | ||
| @Comment({"Hostname of the database server.", "For local databases, this is usually 'localhost'."}) | ||
| public String hostname = "localhost"; | ||
|  | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -8,7 +8,9 @@ public enum DatabaseDriverType { | |
| MARIADB(MARIADB_DRIVER, MARIADB_JDBC_URL), | ||
| POSTGRESQL(POSTGRESQL_DRIVER, POSTGRESQL_JDBC_URL), | ||
| H2(H2_DRIVER, H2_JDBC_URL), | ||
| SQLITE(SQLITE_DRIVER, SQLITE_JDBC_URL); | ||
| SQLITE(SQLITE_DRIVER, SQLITE_JDBC_URL), | ||
|  | ||
| H2_TEST(H2_DRIVER, "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MYSQL"); | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. raczej bym unikał dodawania kawałków kodu które są tylko dla testów, jaki tutaj był problem? może da się to lepiej rozwiązać? | ||
|  | ||
| private final String driver; | ||
| private final String urlFormat; | ||
|  | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,56 +1,107 @@ | ||
| package com.eternalcode.core.user; | ||
|  | ||
| import com.eternalcode.commons.algorithm.BatchProcessor; | ||
| import com.eternalcode.core.injector.annotations.Inject; | ||
| import com.eternalcode.core.injector.annotations.component.Service; | ||
| import com.eternalcode.core.user.database.UserRepository; | ||
| import com.eternalcode.core.user.database.UserRepositorySettings; | ||
| import com.github.benmanes.caffeine.cache.Cache; | ||
| import com.github.benmanes.caffeine.cache.Caffeine; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.function.Consumer; | ||
|  | ||
| @Service | ||
| public class UserManager { | ||
|  | ||
| private final Map<UUID, User> usersByUUID = new ConcurrentHashMap<>(); | ||
| private final Map<String, User> usersByName = new ConcurrentHashMap<>(); | ||
| private final Cache<UUID, User> usersByUUID; | ||
| private final Cache<String, User> usersByName; | ||
|  | ||
| private final UserRepository userRepository; | ||
| private final UserRepositorySettings userRepositorySettings; | ||
|  | ||
| @Inject | ||
| public UserManager(UserRepository userRepository, UserRepositorySettings userRepositorySettings) { | ||
| this.userRepositorySettings = userRepositorySettings; | ||
| this.usersByUUID = Caffeine.newBuilder().build(); | ||
| this.usersByName = Caffeine.newBuilder().build(); | ||
|  | ||
| this.userRepository = userRepository; | ||
|  | ||
| fetchUsers(); | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| public Optional<User> getUser(UUID uuid) { | ||
| return Optional.ofNullable(this.usersByUUID.get(uuid)); | ||
| return Optional.ofNullable(this.usersByUUID.getIfPresent(uuid)); | ||
| } | ||
|  | ||
| public Optional<User> getUser(String name) { | ||
| return Optional.ofNullable(this.usersByName.get(name)); | ||
| return Optional.ofNullable(this.usersByName.getIfPresent(name)); | ||
| } | ||
|  | ||
| public User getOrCreate(UUID uuid, String name) { | ||
| User userByUUID = this.usersByUUID.get(uuid); | ||
| User userByUUID = this.usersByUUID.getIfPresent(uuid); | ||
|  | ||
| if (userByUUID != null) { | ||
| return userByUUID; | ||
| } | ||
|  | ||
| User userByName = this.usersByName.get(name); | ||
| User userByName = this.usersByName.getIfPresent(name); | ||
|  | ||
| if (userByName != null) { | ||
| return userByName; | ||
| } | ||
|  | ||
| this.userRepository.saveUser(new User(uuid, name)); | ||
| return this.create(uuid, name); | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| public User create(UUID uuid, String name) { | ||
| if (this.usersByUUID.containsKey(uuid) || this.usersByName.containsKey(name)) { | ||
| if (this.usersByName.getIfPresent(name) != null || this.usersByUUID.getIfPresent(uuid) != null) { | ||
| throw new IllegalStateException("User already exists"); | ||
| } | ||
|  | ||
| User user = new User(uuid, name); | ||
| this.usersByUUID.put(uuid, user); | ||
| this.usersByName.put(name, user); | ||
|  | ||
| this.userRepository.saveUser(user); | ||
| return user; | ||
| } | ||
|  | ||
| public Collection<User> getUsers() { | ||
| return Collections.unmodifiableCollection(this.usersByUUID.values()); | ||
| return Collections.unmodifiableCollection(this.usersByUUID.asMap().values()); | ||
| } | ||
|  | ||
| private void fetchUsers() { | ||
| if (this.userRepositorySettings.batchDatabaseFetchSize() <= 0) { | ||
| throw new IllegalArgumentException("Value for batchDatabaseFetchSize must be greater than 0!"); | ||
| } | ||
|  | ||
| Consumer<Collection<User>> batchSave = users -> | ||
| { | ||
| BatchProcessor<User> batchProcessor = new BatchProcessor<>(users, this.userRepositorySettings.batchDatabaseFetchSize()); | ||
|  | ||
| do { | ||
| batchProcessor.processNext(user -> { | ||
| usersByName.put(user.getName(), user); | ||
| usersByUUID.put(user.getUniqueId(), user); | ||
| }); | ||
|  | ||
| } while (!batchProcessor.isComplete()); | ||
| }; | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| if (this.userRepositorySettings.useBatchDatabaseFetching()) { | ||
| this.userRepository.fetchUsersBatch(this.userRepositorySettings.batchDatabaseFetchSize()) | ||
| .thenAccept(batchSave); | ||
| } | ||
| else { | ||
|  | ||
| this.userRepository.fetchAllUsers() | ||
| .thenAccept(batchSave); | ||
|  | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.eternalcode.core.user.database; | ||
|  | ||
| import com.eternalcode.core.user.User; | ||
| import java.util.Collection; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import org.jetbrains.annotations.Nullable; | ||
|  | ||
| public interface UserRepository { | ||
|  | ||
| @Nullable CompletableFuture<User> getUser(UUID uniqueId); | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| CompletableFuture<Void> saveUser(User player); | ||
|  | ||
| CompletableFuture<User> updateUser(UUID uniqueId, User player); | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| CompletableFuture<Void> deleteUser(UUID uniqueId); | ||
|  | ||
| CompletableFuture<Collection<User>> fetchAllUsers(); | ||
|  | ||
| CompletableFuture<Collection<User>> fetchUsersBatch(int batchSize); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.eternalcode.core.user.database; | ||
|  | ||
| import eu.okaeri.configs.OkaeriConfig; | ||
| import eu.okaeri.configs.annotation.Comment; | ||
| import lombok.Getter; | ||
| import lombok.experimental.Accessors; | ||
|  | ||
| @Getter | ||
| @Accessors(fluent = true) | ||
| public class UserRepositoryConfig extends OkaeriConfig implements UserRepositorySettings { | ||
|  | ||
| @Comment({ | ||
| "# Should plugin use batches to fetch users from the database?", | ||
| "# We suggest turning this setting to TRUE for servers with more than 10k users", | ||
| "# Set this to false if you are using SQLITE or H2 database (local databases)" | ||
| }) | ||
| public boolean useBatchDatabaseFetching = false; | ||
|  | ||
| @Comment({ | ||
| "# Size of batches querried to the database", | ||
| "# Value must be greater than 0!" | ||
| }) | ||
| public int batchDatabaseFetchSize = 1000; | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| package com.eternalcode.core.user.database; | ||
|  | ||
| import com.eternalcode.commons.scheduler.Scheduler; | ||
| import com.eternalcode.core.database.AbstractRepositoryOrmLite; | ||
| import com.eternalcode.core.database.DatabaseManager; | ||
| import com.eternalcode.core.injector.annotations.Inject; | ||
| import com.eternalcode.core.injector.annotations.component.Repository; | ||
| import com.eternalcode.core.user.User; | ||
| import com.j256.ormlite.table.TableUtils; | ||
| import java.sql.SQLException; | ||
| import java.util.Collection; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.CompletableFuture; | ||
|  | ||
| @Repository | ||
| public class UserRepositoryOrmLite extends AbstractRepositoryOrmLite implements UserRepository { | ||
|  | ||
| @Inject | ||
| public UserRepositoryOrmLite(DatabaseManager databaseManager, Scheduler scheduler) throws SQLException { | ||
| super(databaseManager, scheduler); | ||
| TableUtils.createTableIfNotExists(databaseManager.connectionSource(), UserTable.class); | ||
| } | ||
|  | ||
| @Override | ||
| public CompletableFuture<User> getUser(UUID uniqueId) { | ||
| return this.selectSafe(UserTable.class, uniqueId) | ||
| .thenApply(optional -> optional.map(userTable -> userTable.toUser()).orElseGet(null)); | ||
| } | ||
|  | ||
| @Override | ||
| public CompletableFuture<Collection<User>> fetchAllUsers() { | ||
| return this.selectAll(UserTable.class) | ||
| .thenApply(userTables -> userTables.stream().map(UserTable::toUser).toList()); | ||
| } | ||
|  | ||
| @Override | ||
| public CompletableFuture<Collection<User>> fetchUsersBatch(int batchSize) { | ||
| return CompletableFuture.supplyAsync(() -> { | ||
|          | ||
| try { | ||
| var dao = this.databaseManager.getDao(UserTable.class); | ||
| var users = new java.util.ArrayList<User>(); | ||
|  | ||
| int offset = 0; | ||
| while (true) { | ||
| var queryBuilder = dao.queryBuilder(); | ||
| queryBuilder.limit((long) batchSize); | ||
| queryBuilder.offset((long) offset); | ||
|  | ||
| var batch = dao.query(queryBuilder.prepare()); | ||
|  | ||
| if (batch.isEmpty()) { | ||
| break; | ||
| } | ||
|  | ||
| batch.stream() | ||
| .map(UserTable::toUser) | ||
| .forEach(users::add); | ||
|  | ||
| offset += batchSize; | ||
| } | ||
|  | ||
| return users; | ||
| } catch (Exception exception) { | ||
| throw new RuntimeException("Failed to fetch users in batches", exception); | ||
| } | ||
| }); | ||
| } | ||
|         
                  CitralFlo marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| @Override | ||
| public CompletableFuture<Void> saveUser(User user) { | ||
| return this.save(UserTable.class, UserTable.from(user)).thenApply(v -> null); | ||
| } | ||
|  | ||
| @Override | ||
| public CompletableFuture<User> updateUser(UUID uniqueId, User user) { | ||
| return this.save(UserTable.class, UserTable.from(user)).thenApply(v -> user); | ||
| } | ||
|  | ||
| @Override | ||
| public CompletableFuture<Void> deleteUser(UUID uniqueId) { | ||
| return this.deleteById(UserTable.class, uniqueId).thenApply(v -> null); | ||
| } | ||
|  | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.eternalcode.core.user.database; | ||
|  | ||
| public interface UserRepositorySettings { | ||
|  | ||
| boolean useBatchDatabaseFetching(); | ||
|  | ||
| int batchDatabaseFetchSize(); | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.eternalcode.core.user.database; | ||
|  | ||
| import com.eternalcode.core.user.User; | ||
| import com.j256.ormlite.field.DatabaseField; | ||
| import com.j256.ormlite.table.DatabaseTable; | ||
| import java.util.UUID; | ||
|  | ||
| @DatabaseTable(tableName = "eternal_core_users") | ||
| public class UserTable { | ||
|  | ||
| @DatabaseField(columnName = "id", id = true) | ||
| private UUID uniqueId; | ||
|  | ||
| @DatabaseField(columnName = "name") | ||
| private String name; | ||
|  | ||
| UserTable() {} | ||
|  | ||
| UserTable(UUID uniqueId, String name) { | ||
| this.uniqueId = uniqueId; | ||
| this.name = name; | ||
| } | ||
|  | ||
| public User toUser() { | ||
| return new User(this.uniqueId, this.name); | ||
| } | ||
|  | ||
| public static UserTable from(User user) { | ||
| return new UserTable(user.getUniqueId(), user.getName()); | ||
| } | ||
| } | 
Uh oh!
There was an error while loading. Please reload this page.