mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-18 15:24:45 +01:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package emu.nebula.database;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AccountDatabaseOnly {
|
||||
|
||||
}
|
||||
23
src/main/java/emu/nebula/database/DatabaseCounter.java
Normal file
23
src/main/java/emu/nebula/database/DatabaseCounter.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package emu.nebula.database;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
|
||||
@Entity(value = "counters", useDiscriminator = false)
|
||||
public class DatabaseCounter {
|
||||
@Id
|
||||
private String id;
|
||||
private int count;
|
||||
|
||||
public DatabaseCounter() {}
|
||||
|
||||
public DatabaseCounter(String id) {
|
||||
this.id = id;
|
||||
this.count = 10000;
|
||||
}
|
||||
|
||||
public int getNextId() {
|
||||
int id = ++count;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
251
src/main/java/emu/nebula/database/DatabaseManager.java
Normal file
251
src/main/java/emu/nebula/database/DatabaseManager.java
Normal file
@@ -0,0 +1,251 @@
|
||||
package emu.nebula.database;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import emu.nebula.Config.DatabaseInfo;
|
||||
import emu.nebula.Config.InternalMongoInfo;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.Nebula.ServerType;
|
||||
import emu.nebula.database.codecs.*;
|
||||
import emu.nebula.util.Utils;
|
||||
|
||||
import org.bson.codecs.configuration.CodecRegistries;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
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 com.mongodb.client.result.DeleteResult;
|
||||
|
||||
import de.bwaldvogel.mongo.MongoBackend;
|
||||
import de.bwaldvogel.mongo.MongoServer;
|
||||
import de.bwaldvogel.mongo.backend.h2.H2Backend;
|
||||
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
|
||||
import dev.morphia.*;
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.mapping.Mapper;
|
||||
import dev.morphia.mapping.MapperOptions;
|
||||
import dev.morphia.query.filters.Filters;
|
||||
import dev.morphia.query.updates.UpdateOperators;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public final class DatabaseManager {
|
||||
@Getter
|
||||
private static MongoServer server;
|
||||
private Datastore datastore;
|
||||
|
||||
private static final InsertOneOptions INSERT_OPTIONS = new InsertOneOptions();
|
||||
private static final DeleteOptions DELETE_OPTIONS = new DeleteOptions();
|
||||
private static final DeleteOptions DELETE_MANY = new DeleteOptions().multi(true);
|
||||
|
||||
public DatabaseManager(DatabaseInfo info, ServerType type) {
|
||||
// Variables
|
||||
var internalConfig = Nebula.getConfig().getInternalMongoServer();
|
||||
String connectionString = info.getUri();
|
||||
|
||||
// Start local mongo server
|
||||
if (info.isUseInternal()) {
|
||||
if (Utils.isPortOpen(internalConfig.getAddress(), internalConfig.getPort())) {
|
||||
connectionString = startInternalMongoServer(internalConfig);
|
||||
Nebula.getLogger().info("Started local MongoDB server at " + server.getConnectionString());
|
||||
} else {
|
||||
Nebula.getLogger().warn("Local MongoDB server could not be created because the port is in use.");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
MongoClient mongoClient = MongoClients.create(connectionString);
|
||||
|
||||
// Add our custom fastutil codecs
|
||||
var codecProvider = CodecRegistries.fromCodecs(
|
||||
new IntSetCodec(), new IntListCodec(), new Int2IntMapCodec(), new ItemParamMapCodec()
|
||||
);
|
||||
|
||||
// Set mapper options
|
||||
MapperOptions mapperOptions = MapperOptions.builder()
|
||||
.storeEmpties(true)
|
||||
.storeNulls(false)
|
||||
.codecProvider(codecProvider)
|
||||
.build();
|
||||
|
||||
// Create data store.
|
||||
datastore = Morphia.createDatastore(mongoClient, info.getCollection(), mapperOptions);
|
||||
|
||||
// Map classes
|
||||
var entities = new Reflections(Nebula.class.getPackageName())
|
||||
.getTypesAnnotatedWith(Entity.class)
|
||||
.stream()
|
||||
.filter(cls -> {
|
||||
Entity e = cls.getAnnotation(Entity.class);
|
||||
return e != null && !e.value().equals(Mapper.IGNORED_FIELDNAME);
|
||||
})
|
||||
.toList();
|
||||
|
||||
if (type.runLogin()) {
|
||||
// Only map account related entities
|
||||
var map = entities.stream().filter(cls -> {
|
||||
return cls.getAnnotation(AccountDatabaseOnly.class) != null;
|
||||
}).toArray(Class<?>[]::new);
|
||||
|
||||
datastore.getMapper().map(map);
|
||||
}
|
||||
if (type.runGame()) {
|
||||
// Only map game related entities
|
||||
var map = entities.stream().filter(cls -> {
|
||||
return cls.getAnnotation(AccountDatabaseOnly.class) == null;
|
||||
}).toArray(Class<?>[]::new);
|
||||
|
||||
datastore.getMapper().map(map);
|
||||
}
|
||||
|
||||
// Ensure indexes
|
||||
ensureIndexes();
|
||||
|
||||
// Done
|
||||
Nebula.getLogger().info("Connected to the MongoDB database at " + connectionString);
|
||||
}
|
||||
|
||||
public MongoDatabase getDatabase() {
|
||||
return getDatastore().getDatabase();
|
||||
}
|
||||
|
||||
private void ensureIndexes() {
|
||||
try {
|
||||
datastore.ensureIndexes();
|
||||
} catch (MongoCommandException exception) {
|
||||
Nebula.getLogger().warn("Mongo index error: ", exception);
|
||||
// Duplicate index error
|
||||
if (exception.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Database Functions
|
||||
|
||||
public boolean checkIfObjectExists(Class<?> cls, String filter, String value) {
|
||||
return getDatastore().find(cls).filter(Filters.eq(filter, value)).count() > 0;
|
||||
}
|
||||
|
||||
public <T> T getObjectByUid(Class<T> cls, long uid) {
|
||||
return getDatastore().find(cls).filter(Filters.eq("_id", uid)).first();
|
||||
}
|
||||
|
||||
public <T> T getObjectByField(Class<T> cls, String filter, Object value) {
|
||||
return getDatastore().find(cls).filter(Filters.eq(filter, value)).first();
|
||||
}
|
||||
|
||||
public <T> T getObjectByField(Class<T> cls, String filter, long value) {
|
||||
return getDatastore().find(cls).filter(Filters.eq(filter, value)).first();
|
||||
}
|
||||
|
||||
public <T> Stream<T> getObjects(Class<T> cls, String filter, Object value) {
|
||||
return getDatastore().find(cls).filter(Filters.eq(filter, value)).stream();
|
||||
}
|
||||
|
||||
public <T> Stream<T> getObjects(Class<T> cls, String filter, long value) {
|
||||
return getDatastore().find(cls).filter(Filters.eq(filter, value)).stream();
|
||||
}
|
||||
|
||||
public <T> Stream<T> getObjects(Class<T> cls) {
|
||||
return getDatastore().find(cls).stream();
|
||||
}
|
||||
|
||||
public <T> void save(T obj) {
|
||||
getDatastore().save(obj, INSERT_OPTIONS);
|
||||
}
|
||||
|
||||
public <T> boolean delete(T obj) {
|
||||
DeleteResult result = getDatastore().delete(obj, DELETE_OPTIONS);
|
||||
return result.getDeletedCount() > 0;
|
||||
}
|
||||
|
||||
public boolean delete(Class<?> cls, String filter, long uid) {
|
||||
DeleteResult result = getDatastore().find(cls).filter(Filters.eq(filter, uid)).delete(DELETE_MANY);
|
||||
return result.getDeletedCount() > 0;
|
||||
}
|
||||
|
||||
public void update(Object obj, int uid, String field, Object item) {
|
||||
update(obj, uid, field, item, false);
|
||||
}
|
||||
|
||||
public void update(Object obj, int uid, String field, Object value, boolean upsert) {
|
||||
var opt = new UpdateOptions().upsert(upsert);
|
||||
|
||||
getDatastore().find(obj.getClass())
|
||||
.filter(Filters.eq("_id", uid))
|
||||
.update(opt, UpdateOperators.set(field, value));
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public void update(Object obj, int uid, String field, Object value, String field2, Object value2) {
|
||||
getDatastore().find(obj.getClass())
|
||||
.filter(Filters.eq("_id", uid))
|
||||
.update(UpdateOperators.set(field, value), UpdateOperators.set(field2, value2));
|
||||
}
|
||||
|
||||
public void updateNested(Object obj, int uid, String filter, int filterId, String field, Object item) {
|
||||
var opt = new UpdateOptions().upsert(false);
|
||||
|
||||
getDatastore().find(obj.getClass())
|
||||
.filter(Filters.eq("_id", uid))
|
||||
.filter(Filters.eq(filter, filterId))
|
||||
.update(opt, UpdateOperators.set(field, item));
|
||||
}
|
||||
|
||||
public void addToList(Object obj, int uid, String field, Object item) {
|
||||
var opt = new UpdateOptions().upsert(false);
|
||||
|
||||
getDatastore().find(obj.getClass())
|
||||
.filter(Filters.eq("_id", uid))
|
||||
.update(opt, UpdateOperators.addToSet(field, item));
|
||||
}
|
||||
|
||||
// Database counter
|
||||
|
||||
public synchronized int getNextObjectId(Class<?> c) {
|
||||
DatabaseCounter counter = getDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getSimpleName())).first();
|
||||
if (counter == null) {
|
||||
counter = new DatabaseCounter(c.getSimpleName());
|
||||
}
|
||||
try {
|
||||
return counter.getNextId();
|
||||
} finally {
|
||||
getDatastore().save(counter);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal MongoDB server
|
||||
|
||||
public static String startInternalMongoServer(InternalMongoInfo internalMongo) {
|
||||
// Get backend
|
||||
MongoBackend backend = null;
|
||||
|
||||
if (internalMongo.filePath != null && internalMongo.filePath.length() > 0) {
|
||||
backend = new H2Backend(internalMongo.filePath);
|
||||
} else {
|
||||
backend = new MemoryBackend();
|
||||
}
|
||||
|
||||
// Create the local mongo server and replace the connection string
|
||||
server = new MongoServer(backend);
|
||||
|
||||
// Bind to address of it exists
|
||||
if (internalMongo.getAddress() != null && internalMongo.getPort() != 0) {
|
||||
server.bind(internalMongo.getAddress(), internalMongo.getPort());
|
||||
} else {
|
||||
server.bind(); // Binds to random port
|
||||
}
|
||||
|
||||
return server.getConnectionString();
|
||||
}
|
||||
}
|
||||
11
src/main/java/emu/nebula/database/GameDatabaseObject.java
Normal file
11
src/main/java/emu/nebula/database/GameDatabaseObject.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package emu.nebula.database;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
|
||||
public interface GameDatabaseObject {
|
||||
|
||||
public default void save() {
|
||||
Nebula.getGameDatabase().save(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package emu.nebula.database.codecs;
|
||||
|
||||
import org.bson.BsonReader;
|
||||
import org.bson.BsonType;
|
||||
import org.bson.BsonWriter;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.DecoderContext;
|
||||
import org.bson.codecs.EncoderContext;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
|
||||
/**
|
||||
* Custom mongodb codec for encoding/decoding fastutil int2int maps.
|
||||
*/
|
||||
public class Int2IntMapCodec implements Codec<Int2IntMap> {
|
||||
|
||||
@Override
|
||||
public Class<Int2IntMap> getEncoderClass() {
|
||||
return Int2IntMap.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(BsonWriter writer, Int2IntMap collection, EncoderContext encoderContext) {
|
||||
writer.writeStartDocument();
|
||||
for (var entry : collection.int2IntEntrySet()) {
|
||||
writer.writeName(Integer.toString(entry.getIntKey()));
|
||||
writer.writeInt32(entry.getIntValue());
|
||||
}
|
||||
writer.writeEndDocument();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Int2IntMap decode(BsonReader reader, DecoderContext decoderContext) {
|
||||
Int2IntMap collection = new Int2IntOpenHashMap();
|
||||
reader.readStartDocument();
|
||||
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
|
||||
collection.put(Integer.parseInt(reader.readName()), reader.readInt32());
|
||||
}
|
||||
reader.readEndDocument();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
42
src/main/java/emu/nebula/database/codecs/IntListCodec.java
Normal file
42
src/main/java/emu/nebula/database/codecs/IntListCodec.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package emu.nebula.database.codecs;
|
||||
|
||||
import org.bson.BsonReader;
|
||||
import org.bson.BsonType;
|
||||
import org.bson.BsonWriter;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.DecoderContext;
|
||||
import org.bson.codecs.EncoderContext;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
/**
|
||||
* Custom mongodb codec for encoding/decoding fastutil int sets.
|
||||
*/
|
||||
public class IntListCodec implements Codec<IntList> {
|
||||
|
||||
@Override
|
||||
public Class<IntList> getEncoderClass() {
|
||||
return IntList.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(BsonWriter writer, IntList collection, EncoderContext encoderContext) {
|
||||
writer.writeStartArray();
|
||||
for (int value : collection) {
|
||||
writer.writeInt32(value);
|
||||
}
|
||||
writer.writeEndArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntList decode(BsonReader reader, DecoderContext decoderContext) {
|
||||
IntList collection = new IntArrayList();
|
||||
reader.readStartArray();
|
||||
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
|
||||
collection.add(reader.readInt32());
|
||||
}
|
||||
reader.readEndArray();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
42
src/main/java/emu/nebula/database/codecs/IntSetCodec.java
Normal file
42
src/main/java/emu/nebula/database/codecs/IntSetCodec.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package emu.nebula.database.codecs;
|
||||
|
||||
import org.bson.BsonReader;
|
||||
import org.bson.BsonType;
|
||||
import org.bson.BsonWriter;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.DecoderContext;
|
||||
import org.bson.codecs.EncoderContext;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
/**
|
||||
* Custom mongodb codec for encoding/decoding fastutil int sets.
|
||||
*/
|
||||
public class IntSetCodec implements Codec<IntSet> {
|
||||
|
||||
@Override
|
||||
public Class<IntSet> getEncoderClass() {
|
||||
return IntSet.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(BsonWriter writer, IntSet collection, EncoderContext encoderContext) {
|
||||
writer.writeStartArray();
|
||||
for (int value : collection) {
|
||||
writer.writeInt32(value);
|
||||
}
|
||||
writer.writeEndArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntSet decode(BsonReader reader, DecoderContext decoderContext) {
|
||||
IntSet collection = new IntOpenHashSet();
|
||||
reader.readStartArray();
|
||||
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
|
||||
collection.add(reader.readInt32());
|
||||
}
|
||||
reader.readEndArray();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package emu.nebula.database.codecs;
|
||||
|
||||
import org.bson.BsonReader;
|
||||
import org.bson.BsonType;
|
||||
import org.bson.BsonWriter;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.DecoderContext;
|
||||
import org.bson.codecs.EncoderContext;
|
||||
|
||||
import emu.nebula.game.inventory.ItemParamMap;
|
||||
|
||||
/**
|
||||
* Copy of Int2IntMapCodec.java
|
||||
*/
|
||||
public class ItemParamMapCodec implements Codec<ItemParamMap> {
|
||||
|
||||
@Override
|
||||
public Class<ItemParamMap> getEncoderClass() {
|
||||
return ItemParamMap.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(BsonWriter writer, ItemParamMap collection, EncoderContext encoderContext) {
|
||||
writer.writeStartDocument();
|
||||
for (var entry : collection.int2IntEntrySet()) {
|
||||
writer.writeName(Integer.toString(entry.getIntKey()));
|
||||
writer.writeInt32(entry.getIntValue());
|
||||
}
|
||||
writer.writeEndDocument();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemParamMap decode(BsonReader reader, DecoderContext decoderContext) {
|
||||
ItemParamMap collection = new ItemParamMap();
|
||||
reader.readStartDocument();
|
||||
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
|
||||
collection.put(Integer.parseInt(reader.readName()), reader.readInt32());
|
||||
}
|
||||
reader.readEndDocument();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user