127 lines
4.6 KiB
Java
Raw Normal View History

2023-03-31 22:30:45 -04:00
package emu.grasscutter.database;
import static emu.grasscutter.config.Configuration.DATABASE;
import static emu.grasscutter.config.Configuration.SERVER;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import dev.morphia.Datastore;
import dev.morphia.Morphia;
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
import dev.morphia.mapping.MapperOptions;
import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.Account;
import org.reflections.Reflections;
public final class DatabaseManager {
private static Datastore gameDatastore;
private static Datastore dispatchDatastore;
public static Datastore getGameDatastore() {
return gameDatastore;
}
public static MongoDatabase getGameDatabase() {
return getGameDatastore().getDatabase();
}
// Yes. I very dislike this method. However, this will be good for now.
// TODO: Add dispatch routes for player account management
public static Datastore getAccountDatastore() {
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
return dispatchDatastore;
} else {
return gameDatastore;
}
}
public static void initialize() {
// Initialize
MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri);
// Set mapper options.
MapperOptions mapperOptions =
MapperOptions.builder().storeEmpties(true).storeNulls(false).build();
// Create data store.
gameDatastore =
Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions);
// Map classes.
Class<?>[] entities =
new Reflections(Grasscutter.class.getPackageName())
.getTypesAnnotatedWith(Entity.class).stream()
.filter(
cls -> {
Entity e = cls.getAnnotation(Entity.class);
return e != null && !e.value().equals(Mapper.IGNORED_FIELDNAME);
})
.toArray(Class<?>[]::new);
gameDatastore.getMapper().map(entities);
// Ensure indexes for the game datastore
ensureIndexes(gameDatastore);
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri);
dispatchDatastore =
Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection, mapperOptions);
dispatchDatastore.getMapper().map(new Class<?>[] {DatabaseCounter.class, Account.class});
// Ensure indexes for dispatch datastore
ensureIndexes(dispatchDatastore);
}
}
/**
* Ensures the database indexes exist and rebuilds them if there is an error with them
*
* @param datastore The datastore to ensure indexes on
*/
private static void ensureIndexes(Datastore datastore) {
try {
datastore.ensureIndexes();
} catch (MongoCommandException e) {
Grasscutter.getLogger().info("Mongo index error: ", e);
// Duplicate index error
if (e.getCode() == 85) {
// Drop all indexes and re add them
MongoIterable<String> collections = datastore.getDatabase().listCollectionNames();
for (String name : collections) {
datastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
datastore.ensureIndexes();
}
}
}
public static synchronized int getNextId(Class<?> c) {
DatabaseCounter counter =
getGameDatastore()
.find(DatabaseCounter.class)
.filter(Filters.eq("_id", c.getSimpleName()))
.first();
if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName());
}
try {
return counter.getNextId();
} finally {
getGameDatastore().save(counter);
}
}
public static synchronized int getNextId(Object o) {
return getNextId(o.getClass());
}
}