DatabaseManager.java 4.18 KB
Newer Older
Melledy's avatar
Melledy committed
1
2
package emu.grasscutter.database;

3
4
import static emu.grasscutter.config.Configuration.*;

Melledy's avatar
Melledy committed
5
import com.mongodb.MongoCommandException;
KingRainbow44's avatar
KingRainbow44 committed
6
7
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
Melledy's avatar
Melledy committed
8
9
10
11
12
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;

import dev.morphia.Datastore;
import dev.morphia.Morphia;
13
14
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
KingRainbow44's avatar
KingRainbow44 committed
15
16
import dev.morphia.mapping.MapperOptions;
import dev.morphia.query.experimental.filters.Filters;
17

Melledy's avatar
Melledy committed
18
import emu.grasscutter.Grasscutter;
19
import emu.grasscutter.Grasscutter.ServerRunMode;
Melledy's avatar
Melledy committed
20
import emu.grasscutter.game.Account;
21
22

import org.reflections.Reflections;
Melledy's avatar
Melledy committed
23

24
public final class DatabaseManager {
github-actions's avatar
github-actions committed
25
26
    private static Datastore gameDatastore;
    private static Datastore dispatchDatastore;
Akka's avatar
Akka committed
27

28
    public static Datastore getGameDatastore() {
github-actions's avatar
github-actions committed
29
        return gameDatastore;
Melledy's avatar
Melledy committed
30
    }
Akka's avatar
Akka committed
31

32
    public static MongoDatabase getGameDatabase() {
github-actions's avatar
github-actions committed
33
        return getGameDatastore().getDatabase();
Melledy's avatar
Melledy committed
34
    }
35

github-actions's avatar
github-actions committed
36
37
38
39
40
41
42
43
44
45
46
47
48
    // 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);
49

github-actions's avatar
github-actions committed
50
51
52
        // Set mapper options.
        MapperOptions mapperOptions = MapperOptions.builder()
                .storeEmpties(true).storeNulls(false).build();
github-actions's avatar
github-actions committed
53

github-actions's avatar
github-actions committed
54
55
        // Create data store.
        gameDatastore = Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions);
github-actions's avatar
github-actions committed
56

github-actions's avatar
github-actions committed
57
        // Map classes.
58
59
60
61
62
63
64
65
66
67
        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);
Benjamin Elsdon's avatar
Benjamin Elsdon committed
68

69
70
71
72
73
        // Ensure indexes for the game datastore
        ensureIndexes(gameDatastore);

        if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
            MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri);
github-actions's avatar
github-actions committed
74

75
76
            dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection, mapperOptions);
            dispatchDatastore.getMapper().map(new Class<?>[] {DatabaseCounter.class, Account.class});
github-actions's avatar
github-actions committed
77

78
79
80
81
            // Ensure indexes for dispatch datastore
            ensureIndexes(dispatchDatastore);
        }
    }
github-actions's avatar
github-actions committed
82

83
84
85
86
87
    /**
     * 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) {
github-actions's avatar
github-actions committed
88
        try {
89
90
91
            datastore.ensureIndexes();
        } catch (MongoCommandException e) {
            Grasscutter.getLogger().info("Mongo index error: ", e);
github-actions's avatar
github-actions committed
92
            // Duplicate index error
93
            if (e.getCode() == 85) {
github-actions's avatar
github-actions committed
94
                // Drop all indexes and re add them
95
                MongoIterable<String> collections = datastore.getDatabase().listCollectionNames();
github-actions's avatar
github-actions committed
96
                for (String name : collections) {
97
                    datastore.getDatabase().getCollection(name).dropIndexes();
github-actions's avatar
github-actions committed
98
99
                }
                // Add back indexes
100
                datastore.ensureIndexes();
github-actions's avatar
github-actions committed
101
102
103
            }
        }
    }
KingRainbow44's avatar
KingRainbow44 committed
104

github-actions's avatar
github-actions committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    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());
    }
}