Adds support for MongoDB.

Needs more testing, but seems to work. The main problem is that it pulls
in the MongoDB Java driver which boosts the size of the JAR to 2.3MB. It
may be better to put the Mongo driver into an addon so that only Mongo
users have to have the larger JAR.
This commit is contained in:
Tastybento 2018-03-18 21:54:24 -07:00
parent 17ecb1c0d4
commit 4100edd279
14 changed files with 347 additions and 12 deletions

View File

@ -48,8 +48,10 @@ general:
### Database-related Settings ###
database:
# FLATFILE, MYSQL
# FLATFILE, MYSQL, MONGO
type: FLATFILE
host: localhost
# Port 3306 is MySQL's default. Port 27017 is MongoDB's default.
port: 3306
name: BSkyBlock
username: username

26
pom.xml
View File

@ -61,6 +61,27 @@
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>us.tastybento.bskyblock.BSkyBlock</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- I'm going to comment out these sections for now to speed up compilation
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version> <executions> <execution> <id>attach-sources</id>
@ -163,6 +184,11 @@
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.6.3</version>
</dependency>
</dependencies>
<repositories>
<repository>

View File

@ -80,4 +80,5 @@ public abstract class AbstractDatabaseHandler<T> {
*/
public abstract boolean objectExists(String key);
public abstract void close();
}

View File

@ -2,6 +2,7 @@ package us.tastybento.bskyblock.database;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.database.flatfile.FlatFileDatabase;
import us.tastybento.bskyblock.database.mongodb.MongoDBDatabase;
import us.tastybento.bskyblock.database.mysql.MySQLDatabase;
public abstract class BSBDatabase {
@ -22,7 +23,8 @@ public abstract class BSBDatabase {
public enum DatabaseType{
FLATFILE(new FlatFileDatabase()),
MYSQL(new MySQLDatabase());
MYSQL(new MySQLDatabase()),
MONGO(new MongoDBDatabase());
BSBDatabase database;

View File

@ -1,6 +1,5 @@
package us.tastybento.bskyblock.database;
import java.sql.Connection;
import java.util.Map;
import org.bukkit.configuration.file.YamlConfiguration;
@ -17,8 +16,13 @@ public interface DatabaseConnecter {
*
* @return A new connection to the database using the settings provided
*/
Connection createConnection();
Object createConnection();
/**
* Close the database connection
*/
void closeConnection();
/**
* Returns the connection url
*

View File

@ -162,4 +162,9 @@ public class FlatFileDatabaseConnecter implements DatabaseConnecter {
return file.exists();
}
@Override
public void closeConnection() {
// Not used
}
}

View File

@ -448,4 +448,10 @@ public class FlatFileDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
}
}
@Override
public void close() {
// Not used
}
}

View File

@ -0,0 +1,22 @@
package us.tastybento.bskyblock.database.mongodb;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.database.AbstractDatabaseHandler;
import us.tastybento.bskyblock.database.BSBDatabase;
import us.tastybento.bskyblock.database.DatabaseConnectionSettingsImpl;
public class MongoDBDatabase extends BSBDatabase{
@Override
public AbstractDatabaseHandler<?> getHandler(Class<?> type) {
BSkyBlock plugin = BSkyBlock.getInstance();
return new MongoDBDatabaseHandler<>(plugin, type, new MongoDBDatabaseConnecter(new DatabaseConnectionSettingsImpl(
plugin.getSettings().getDbHost(),
plugin.getSettings().getDbPort(),
plugin.getSettings().getDbName(),
plugin.getSettings().getDbUsername(),
plugin.getSettings().getDbPassword()
)));
}
}

View File

@ -0,0 +1,77 @@
package us.tastybento.bskyblock.database.mongodb;
import java.util.Map;
import org.bukkit.configuration.file.YamlConfiguration;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
import us.tastybento.bskyblock.database.DatabaseConnecter;
import us.tastybento.bskyblock.database.DatabaseConnectionSettingsImpl;
public class MongoDBDatabaseConnecter implements DatabaseConnecter {
private MongoClient client;
private MongoCredential credential;
private DatabaseConnectionSettingsImpl dbSettings;
/**
* Class for MySQL database connections using the settings provided
* @param dbSettings
*/
public MongoDBDatabaseConnecter(DatabaseConnectionSettingsImpl dbSettings) {
this.dbSettings = dbSettings;
credential = MongoCredential.createCredential(dbSettings.getUsername(),
dbSettings.getDatabaseName(),
dbSettings.getPassword().toCharArray());
MongoClientOptions options = MongoClientOptions.builder().sslEnabled(false).build();
client = new MongoClient(new ServerAddress(dbSettings.getHost(), dbSettings.getPort()),credential,options);
}
@Override
public MongoDatabase createConnection() {
MongoDatabase database = client.getDatabase(dbSettings.getDatabaseName());
return database;
}
@Override
public String getConnectionUrl() {
return "";
}
@Override
public String getUniqueId(String tableName) {
// Not used
return "";
}
@Override
public YamlConfiguration loadYamlFile(String string, String key) {
// Not used
return null;
}
@Override
public boolean uniqueIdExists(String tableName, String key) {
// Not used
return false;
}
@Override
public void saveYamlFile(YamlConfiguration yamlConfig, String tableName, String fileName,
Map<String, String> commentMap) {
// Not used
}
@Override
public void closeConnection() {
client.close();
}
}

View File

@ -0,0 +1,166 @@
package us.tastybento.bskyblock.database.mongodb;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.potion.PotionEffectType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.util.JSON;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.api.flags.Flag;
import us.tastybento.bskyblock.database.AbstractDatabaseHandler;
import us.tastybento.bskyblock.database.DatabaseConnecter;
import us.tastybento.bskyblock.database.mysql.adapters.FlagAdapter;
import us.tastybento.bskyblock.database.mysql.adapters.LocationAdapter;
import us.tastybento.bskyblock.database.mysql.adapters.PotionEffectTypeAdapter;
import us.tastybento.bskyblock.database.mysql.adapters.WorldAdapter;
import us.tastybento.bskyblock.database.objects.DataObject;
/**
*
* Class that inserts a <T> into the corresponding database-table.
*
* @author tastybento
*
* @param <T>
*/
@SuppressWarnings("deprecation")
public class MongoDBDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
/**
* Connection to the database
*/
private MongoDatabase database = null;
private MongoCollection<Document> collection;
private DatabaseConnecter databaseConnecter;
private BSkyBlock bskyblock;
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - BSkyBlock plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param databaseConnecter - authentication details for the database
*/
public MongoDBDatabaseHandler(BSkyBlock plugin, Class<T> type, DatabaseConnecter databaseConnecter) {
super(plugin, type, databaseConnecter);
this.bskyblock = plugin;
this.databaseConnecter = databaseConnecter;
database = (MongoDatabase)databaseConnecter.createConnection();
collection = database.getCollection(dataObject.getCanonicalName());
IndexOptions indexOptions = new IndexOptions().unique(true);
collection.createIndex(Indexes.text("uniqueId"), indexOptions);
}
// Gets the GSON builder
private Gson getGSON() {
// excludeFieldsWithoutExposeAnnotation - this means that every field to be stored should use @Expose
// enableComplexMapKeySerialization - forces GSON to use TypeAdapters even for Map keys
GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().enableComplexMapKeySerialization();
// Register adapters
builder.registerTypeAdapter(Location.class, new LocationAdapter(plugin)) ;
builder.registerTypeAdapter(World.class, new WorldAdapter(plugin));
builder.registerTypeAdapter(Flag.class, new FlagAdapter(bskyblock));
builder.registerTypeAdapter(PotionEffectType.class, new PotionEffectTypeAdapter());
// Keep null in the database
builder.serializeNulls();
// Allow characters like < or > without escaping them
builder.disableHtmlEscaping();
return builder.create();
}
@Override
public List<T> loadObjects() {
List<T> list = new ArrayList<>();
Gson gson = getGSON();
MongoCursor<Document> it = collection.find(new Document()).iterator();
while (it.hasNext()) {
// The deprecated serialize option does not have a viable alternative without involving a huge amount of custom code
String json = JSON.serialize(it.next());
//String json = it.next().toJson();
Bukkit.getLogger().info("DEBUG: " + json);
json = json.replaceFirst("_id", "uniqueId");
Bukkit.getLogger().info("DEBUG: " + json);
list.add(gson.fromJson(json, dataObject));
}
Bukkit.getLogger().info("DEBUG: list is " + list.size());
return list;
}
@Override
public T loadObject(String uniqueId) {
Document doc = collection.find(new Document("_id", uniqueId)).limit(1).first();
if (doc != null) {
Gson gson = getGSON();
String json = JSON.serialize(doc).replaceFirst("_id", "uniqueId");
Bukkit.getLogger().info("DEBUG:loading single object: " + json);
return gson.fromJson(json, dataObject);
}
return null;
}
@Override
public void saveObject(T instance) {
if (!(instance instanceof DataObject)) {
plugin.getLogger().severe(() -> "This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Gson gson = getGSON();
String toStore = gson.toJson(instance);
// Change uniqueId to _id
toStore = toStore.replaceFirst("uniqueId", "_id");
Bukkit.getLogger().info("DEBUG: Saving " + toStore);
Document document = Document.parse(toStore);
collection.insertOne(document);
} catch (Exception e) {
plugin.getLogger().severe(() -> "Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) {
if (!(instance instanceof DataObject)) {
plugin.getLogger().severe(() -> "This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance);
collection.findOneAndDelete(new Document("_id", uniqueId));
} catch (Exception e) {
plugin.getLogger().severe(() -> "Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
/* (non-Javadoc)
* @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#objectExists(java.lang.String)
*/
@Override
public boolean objectExists(String key) {
return collection.find(new Document("_id", key)).limit(1).first() != null ? true : false;
}
@Override
public void close() {
databaseConnecter.closeConnection();
}
}

View File

@ -29,7 +29,7 @@ public class MySQLDatabaseConnecter implements DatabaseConnecter {
Bukkit.getLogger().severe("Could not instantiate JDBC driver! " + e.getMessage());
}
// jdbc:mysql://localhost:3306/Peoples?autoReconnect=true&useSSL=false
connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + "/" + dbSettings.getDatabaseName() + "?autoReconnect=true&useSSL=false&allowMultiQueries=true";
connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName() + "?autoReconnect=true&useSSL=false&allowMultiQueries=true";
}
@Override
@ -69,7 +69,17 @@ public class MySQLDatabaseConnecter implements DatabaseConnecter {
public void saveYamlFile(YamlConfiguration yamlConfig, String tableName, String fileName,
Map<String, String> commentMap) {
// Not used
}
@Override
public void closeConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close MySQL database connection");
}
}
}
}

View File

@ -40,7 +40,7 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
* Connection to the database
*/
private Connection connection = null;
private BSkyBlock bskyblock;
/**
@ -53,11 +53,11 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
public MySQLDatabaseHandler(BSkyBlock plugin, Class<T> type, DatabaseConnecter databaseConnecter) {
super(plugin, type, databaseConnecter);
this.bskyblock = plugin;
connection = databaseConnecter.createConnection();
connection = (Connection)databaseConnecter.createConnection();
// Check if the table exists in the database and if not, create it
createSchema();
}
/**
* Creates the table in the database if it doesn't exist already
*/
@ -73,7 +73,7 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
plugin.getLogger().severe(() -> "Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
// Gets the GSON builder
private Gson getGSON() {
// excludeFieldsWithoutExposeAnnotation - this means that every field to be stored should use @Expose
@ -201,4 +201,15 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return false;
}
@Override
public void close() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
plugin.getLogger().severe("Could not close database for some reason");
}
}
}
}

View File

@ -642,6 +642,7 @@ public class IslandsManager {
}
} catch (Exception e) {
plugin.getLogger().severe(()->"Could not load islands to cache! " + e.getMessage());
e.printStackTrace();
}
}
@ -815,6 +816,7 @@ public class IslandsManager {
public void shutdown(){
save(false);
islandCache.clear();
handler.close();
}
}

View File

@ -25,7 +25,7 @@ import us.tastybento.bskyblock.database.objects.Players;
public class PlayersManager{
private static final boolean DEBUG = false;
private static final boolean DEBUG = true;
private BSkyBlock plugin;
private BSBDatabase database;
private AbstractDatabaseHandler<Players> handler;
@ -106,6 +106,7 @@ public class PlayersManager{
public void shutdown(){
save(false);
playerCache.clear();
handler.close();
}
public Players getPlayer(UUID uuid){