bentobox/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java

205 lines
8.0 KiB
Java

package world.bentobox.bentobox.database.mongodb;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import com.google.gson.Gson;
import com.mongodb.MongoClientException;
import com.mongodb.MongoNamespace;
import com.mongodb.MongoTimeoutException;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.util.JSON;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
/**
*
* Class that inserts a <T> into the corresponding database-table.
*
* @author tastybento
*
* @param <T>
*/
@SuppressWarnings("deprecation")
public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
private static final String UNIQUEID = "uniqueId";
private static final String MONGO_ID = "_id";
private MongoCollection<Document> collection;
private final DatabaseConnector dbConnecter;
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database
*/
MongoDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
super(plugin, type, dbConnecter);
this.dbConnecter = dbConnecter;
boolean connected = true; // if it is set to false, it will consider there has been an error upon connecting.
try {
// Connection to the database
MongoDatabase database = (MongoDatabase) dbConnecter.createConnection(dataObject);
if (database == null) {
plugin.logError("Could not connect to the database. Are the credentials in the config.yml file correct?");
connected = false;
} else {
// Check for old collections
String oldName = plugin.getSettings().getDatabasePrefix() + type.getCanonicalName();
String newName = getName(plugin, dataObject);
if (!oldName.equals((newName)) && collectionExists(database, oldName) && !collectionExists(database, newName)){
collection = database.getCollection(oldName);
collection.renameCollection(new MongoNamespace(database.getName(), newName));
} else {
collection = database.getCollection(newName);
}
IndexOptions indexOptions = new IndexOptions().unique(true);
collection.createIndex(Indexes.text(UNIQUEID), indexOptions);
}
} catch (MongoTimeoutException e) {
plugin.logError("Could not connect to the database. MongoDB timed out.");
plugin.logError("Error code: " + e.getCode());
plugin.logError("Errors: " + String.join(", ", e.getErrorLabels()));
connected = false;
} catch (MongoClientException e) {
plugin.logError("Could not connect to the database. An unhandled error occurred.");
plugin.logStacktrace(e);
connected = false;
}
if (!connected) {
plugin.logWarning("Disabling BentoBox...");
Bukkit.getPluginManager().disablePlugin(plugin);
}
}
private boolean collectionExists(MongoDatabase database, final String collectionName) {
for (final String name : database.listCollectionNames()) {
if (name.equalsIgnoreCase(collectionName)) {
return true;
}
}
return false;
}
private String getName(BentoBox plugin, Class<T> type) {
return plugin.getSettings().getDatabasePrefix() +
(type.getAnnotation(Table.class) == null ?
type.getCanonicalName()
: type.getAnnotation(Table.class)
.name());
}
@Override
public List<T> loadObjects() {
List<T> list = new ArrayList<>();
Gson gson = getGson();
for (Document document : collection.find(new Document())) {
// The deprecated serialize option does not have a viable alternative without involving a huge amount of custom code
String json = JSON.serialize(document);
json = json.replaceFirst(MONGO_ID, UNIQUEID);
try {
list.add(gson.fromJson(json, dataObject));
} catch (Exception e) {
plugin.logError("Could not load object :" + e.getMessage());
}
}
return list;
}
@Override
public T loadObject(@NonNull String uniqueId) {
Document doc = collection.find(new Document(MONGO_ID, uniqueId)).limit(1).first();
Gson gson = getGson();
String json = JSON.serialize(doc).replaceFirst(MONGO_ID, UNIQUEID);
// load single object
return gson.fromJson(json, dataObject);
}
@Override
public CompletableFuture<Boolean> saveObject(T instance) {
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// Null check
if (instance == null) {
plugin.logError("MongoDB database request to store a null. ");
completableFuture.complete(false);
return completableFuture;
}
if (!(instance instanceof DataObject dataObj)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
}
try {
Gson gson = getGson();
String toStore = gson.toJson(instance);
// Change uniqueId to _id
toStore = toStore.replaceFirst(UNIQUEID, MONGO_ID);
// This parses JSON to a Mongo Document
Document document = Document.parse(toStore);
// Filter based on the id
Bson filter = new Document(MONGO_ID, dataObj.getUniqueId());
// Set the options to upsert (update or insert if doc is not there)
FindOneAndReplaceOptions options = new FindOneAndReplaceOptions().upsert(true);
// Do the deed
collection.findOneAndReplace(filter, document, options);
completableFuture.complete(true);
} catch (Exception e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
completableFuture.complete(false);
}
return completableFuture;
}
@Override
public void deleteID(String uniqueId) {
try {
collection.findOneAndDelete(new Document(MONGO_ID, uniqueId));
} catch (Exception e) {
plugin.logError("Could not delete object " + getName(plugin, dataObject) + " " + uniqueId + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) {
// Null check
if (instance == null) {
plugin.logError("MondDB database request to delete a null. ");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
deleteID(((DataObject)instance).getUniqueId());
}
@Override
public boolean objectExists(String uniqueId) {
return collection.find(new Document(MONGO_ID, uniqueId)).first() != null;
}
@Override
public void close() {
dbConnecter.closeConnection(dataObject);
}
}