Merge branch 'develop' into IslandDelete

This commit is contained in:
Florian CUNY 2019-01-13 10:33:50 +01:00
commit 35883435e5
12 changed files with 371 additions and 54 deletions

View File

@ -119,6 +119,15 @@
<version>2.9.2</version>
<scope>provided</scope>
</dependency>
<!-- Static analysis -->
<!-- We are using Eclipse's annotations.
If you're using IDEA, update your project settings to take these
into account for in real time static analysis -->
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.annotation</artifactId>
<version>2.2.200</version>
</dependency>
</dependencies>
<build>

View File

@ -44,8 +44,9 @@ public class Settings implements DataObject {
private boolean useEconomy = true;
// Database
@ConfigComment("YAML, JSON, MYSQL, MONGODB.")
@ConfigComment("YAML, JSON, MYSQL, MARIADB (10.2.3+), MONGODB.")
@ConfigComment("YAML and JSON are both file-based databases.")
@ConfigComment("MYSQL might not work with all implementations: if available, use a dedicated database type (e.g. MARIADB).")
@ConfigComment("If you use MONGODB, you must also run the BSBMongo plugin (not addon).")
@ConfigComment("See https://github.com/tastybento/bsbMongo/releases/.")
@ConfigEntry(path = "general.database.type")

View File

@ -97,17 +97,15 @@ public class IslandBanCommand extends CompositeCommand {
.build();
// Event is not cancelled
if (!banEvent.isCancelled()) {
if (island.ban(issuer.getUniqueId(), target.getUniqueId())) {
issuer.sendMessage("general.success");
target.sendMessage("commands.island.ban.owner-banned-you", TextVariables.NAME, issuer.getName());
// If the player is online, has an island and on the banned island, move them home immediately
if (target.isOnline() && getIslands().hasIsland(getWorld(), target.getUniqueId()) && island.onIsland(target.getLocation())) {
getIslands().homeTeleport(getWorld(), target.getPlayer());
island.getWorld().playSound(target.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1F, 1F);
}
return true;
if (!banEvent.isCancelled() && island.ban(issuer.getUniqueId(), target.getUniqueId())) {
issuer.sendMessage("general.success");
target.sendMessage("commands.island.ban.owner-banned-you", TextVariables.NAME, issuer.getName());
// If the player is online, has an island and on the banned island, move them home immediately
if (target.isOnline() && getIslands().hasIsland(getWorld(), target.getUniqueId()) && island.onIsland(target.getLocation())) {
getIslands().homeTeleport(getWorld(), target.getPlayer());
island.getWorld().playSound(target.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1F, 1F);
}
return true;
}
} else {
issuer.sendMessage("commands.island.ban.cannot-ban-more-players");

View File

@ -67,26 +67,26 @@ public class IslandUnbanCommand extends CompositeCommand {
}
private boolean unban(User issuer, User target) {
Island island = getIslands().getIsland(getWorld(), issuer.getUniqueId());
// Run the event
IslandBaseEvent unbanEvent = IslandEvent.builder()
.island(getIslands().getIsland(getWorld(), issuer.getUniqueId()))
.island(island)
.involvedPlayer(target.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.UNBAN)
.build();
// Event is not cancelled
if (!unbanEvent.isCancelled()) {
if (getIslands().getIsland(getWorld(), issuer.getUniqueId()).unban(issuer.getUniqueId(), target.getUniqueId())) {
issuer.sendMessage("general.success");
target.sendMessage("commands.island.unban.you-are-unbanned", TextVariables.NAME, issuer.getName());
// Set cooldown
if (getSettings().getBanCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("ban").ifPresent(subCommand ->
subCommand.setCooldown(issuer.getUniqueId(), target.getUniqueId(), getSettings().getBanCooldown() * 60));
}
return true;
if (!unbanEvent.isCancelled() && island.unban(issuer.getUniqueId(), target.getUniqueId())) {
issuer.sendMessage("general.success");
target.sendMessage("commands.island.unban.you-are-unbanned", TextVariables.NAME, issuer.getName());
// Set cooldown
if (getSettings().getBanCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("ban").ifPresent(subCommand ->
subCommand.setCooldown(issuer.getUniqueId(), target.getUniqueId(), getSettings().getBanCooldown() * 60));
}
return true;
}
// Unbanning was blocked, maybe due to an event cancellation. Fail silently.
return false;

View File

@ -1,5 +1,8 @@
package world.bentobox.bentobox.api.logs;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
@ -7,8 +10,8 @@ import java.util.Map;
/**
* Represents an event that occurred and that is logged.
* <br/>
* An {@link world.bentobox.bentobox.database.objects.adapters.AdapterInterface} is provided to be able to save/retrieve
* a list of instances of this object to/from the database: {@link world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter}.
* An {@link world.bentobox.bentobox.database.objects.adapters.AdapterInterface AdapterInterface} is provided to be able to save/retrieve
* a list of instances of this object to/from the database: {@link world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter LogEntryListAdapter}.
*
* @author Poslovitch
*/
@ -17,7 +20,7 @@ public class LogEntry {
private final String type;
private final Map<String, Object> data;
private LogEntry(Builder builder) {
private LogEntry(@NonNull Builder builder) {
this.timestamp = builder.timestamp;
this.type = builder.type;
this.data = builder.data;
@ -27,10 +30,12 @@ public class LogEntry {
return timestamp;
}
@NonNull
public String getType() {
return type;
}
@Nullable
public Map<String, Object> getData() {
return data;
}
@ -40,7 +45,7 @@ public class LogEntry {
private String type;
private Map<String, Object> data;
public Builder(String type) {
public Builder(@NonNull String type) {
this.timestamp = System.currentTimeMillis();
this.type = type.toUpperCase(Locale.ENGLISH);
this.data = new LinkedHashMap<>();
@ -51,7 +56,7 @@ public class LogEntry {
return this;
}
public Builder data(Map<String, Object> data) {
public Builder data(Map<@NonNull String, @Nullable Object> data) {
this.data = data;
return this;
}
@ -62,7 +67,7 @@ public class LogEntry {
* @param value value to set
* @return the Builder instance
*/
public Builder data(String key, Object value) {
public Builder data(@NonNull String key, @Nullable Object value) {
this.data.put(key, value);
return this;
}

View File

@ -2,15 +2,19 @@ package world.bentobox.bentobox.database;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.json.JSONDatabase;
import world.bentobox.bentobox.database.mariadb.MariaDBDatabase;
import world.bentobox.bentobox.database.mongodb.MongoDBDatabase;
import world.bentobox.bentobox.database.mysql.MySQLDatabase;
import world.bentobox.bentobox.database.yaml.YamlDatabase;
/**
* @author Poslovitch
*/
public interface DatabaseSetup {
/**
* Gets the type of database being used.
* Currently supported options are YAML, JSON, MYSQL and MONGODB.
* Currently supported options are YAML, JSON, MYSQL, MARIADB and MONGODB.
* Default is YAML.
* @return Database type
*/
@ -28,8 +32,11 @@ public interface DatabaseSetup {
YAML(new YamlDatabase()),
JSON(new JSONDatabase()),
MYSQL(new MySQLDatabase()),
/**
* @since 1.1
*/
MARIADB(new MariaDBDatabase()),
MONGODB(new MongoDBDatabase());
DatabaseSetup database;
DatabaseType(DatabaseSetup database){

View File

@ -0,0 +1,29 @@
package world.bentobox.bentobox.database.mariadb;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseSetup;
/**
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabase implements DatabaseSetup {
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.BSBDbSetup#getHandler(java.lang.Class)
*/
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> type) {
BentoBox plugin = BentoBox.getInstance();
return new MariaDBDatabaseHandler<>(plugin, type, new MariaDBDatabaseConnector(new DatabaseConnectionSettingsImpl(
plugin.getSettings().getDatabaseHost(),
plugin.getSettings().getDatabasePort(),
plugin.getSettings().getDatabaseName(),
plugin.getSettings().getDatabaseUsername(),
plugin.getSettings().getDatabasePassword()
)));
}
}

View File

@ -0,0 +1,68 @@
package world.bentobox.bentobox.database.mariadb;
import org.bukkit.Bukkit;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseConnector;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabaseConnector implements DatabaseConnector {
private String connectionUrl;
private DatabaseConnectionSettingsImpl dbSettings;
private Connection connection = null;
/**
* Class for MariaDB database connections using the settings provided
* @param dbSettings - database settings
*/
MariaDBDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
this.dbSettings = dbSettings;
connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=false&allowMultiQueries=true";
}
@Override
public Connection createConnection() {
try {
connection = DriverManager.getConnection(connectionUrl, dbSettings.getUsername(), dbSettings.getPassword());
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
return connection;
}
@Override
public String getConnectionUrl() {
return connectionUrl;
}
@Override
public String getUniqueId(String tableName) {
// Not used
return "";
}
@Override
public boolean uniqueIdExists(String tableName, String key) {
// Not used
return false;
}
@Override
public void closeConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close MariaDB database connection");
}
}
}
}

View File

@ -0,0 +1,186 @@
package world.bentobox.bentobox.database.mariadb;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
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 java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* Class that inserts a <T> into the corresponding database-table.
*
* @author barpec12
* @since 1.1
*
* @param <T>
*/
public class MariaDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
/**
* Connection to the database
*/
private Connection connection;
/**
* 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
*/
MariaDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
super(plugin, type, dbConnecter);
connection = (Connection)dbConnecter.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
*/
private void createSchema() {
String sql = "CREATE TABLE IF NOT EXISTS `" +
dataObject.getCanonicalName() +
"` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))";
// Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.executeUpdate();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public List<T> loadObjects() {
List<T> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append("SELECT `json` FROM `");
sb.append(dataObject.getCanonicalName());
sb.append("`");
try (Statement preparedStatement = connection.createStatement()) {
try (ResultSet resultSet = preparedStatement.executeQuery(sb.toString())) {
// Load all the results
Gson gson = getGson();
while (resultSet.next()) {
String json = resultSet.getString("json");
if (json != null) {
try {
T gsonResult = gson.fromJson(json, dataObject);
if (gsonResult != null) {
list.add(gsonResult);
}
} catch (JsonSyntaxException ex) {
plugin.logError("Could not load object " + ex.getMessage());
}
}
}
}
} catch (SQLException e) {
plugin.logError("Could not load objects " + e.getMessage());
}
return list;
}
@Override
public T loadObject(String uniqueId) {
String sb = "SELECT `json` FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ? LIMIT 1";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
// If there is a result, we only want/need the first one
Gson gson = getGson();
return gson.fromJson(resultSet.getString("json"), dataObject);
}
}
} catch (SQLException e) {
plugin.logError("Could not load object " + uniqueId + " " + e.getMessage());
}
return null;
}
@Override
public void saveObject(T instance) {
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "INSERT INTO " +
"`" +
dataObject.getCanonicalName() +
"` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
// Replace into is used so that any data in the table will be replaced with updated data
// The table name is the canonical name, so that add-ons can be sure of a unique table in the database
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
Gson gson = getGson();
String toStore = gson.toJson(instance);
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore);
preparedStatement.execute();
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public void deleteObject(T instance) {
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
Method getUniqueId = dataObject.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance);
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
@Override
public boolean objectExists(String uniqueId) {
// Create the query to see if this key exists
String query = "SELECT IF ( EXISTS( SELECT * FROM `" +
dataObject.getCanonicalName() +
"` WHERE `uniqueId` = ?), 1, 0)";
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getBoolean(1);
}
}
} catch (SQLException e) {
plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
return false;
}
@Override
public void close() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
plugin.logError("Could not close database for some reason");
}
}
}
}

View File

@ -20,6 +20,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.gson.annotations.Expose;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
@ -108,7 +110,7 @@ public class Island implements DataObject {
public Island() {}
public Island(Location location, UUID owner, int protectionRange) {
public Island(@NonNull Location location, UUID owner, int protectionRange) {
setOwner(owner);
createdDate = System.currentTimeMillis();
updatedDate = System.currentTimeMillis();
@ -124,7 +126,7 @@ public class Island implements DataObject {
* Adds a team member. If player is on banned list, they will be removed from it.
* @param playerUUID - the player's UUID
*/
public void addMember(UUID playerUUID) {
public void addMember(@NonNull UUID playerUUID) {
setRank(playerUUID, RanksManager.MEMBER_RANK);
}
@ -136,15 +138,12 @@ public class Island implements DataObject {
* @param issuer UUID of the issuer, may be null.
* Whenever possible, one should be provided.
* @param target UUID of the target, must be provided.
* @return {@code true} if the target is successfully banned, {@code false} otherwise.
* @return {@code true}
*/
public boolean ban(UUID issuer, UUID target) {
if (target != null) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target).data("issuer", issuer).build());
return true;
}
return false;
public boolean ban(@Nullable UUID issuer, @NonNull UUID target) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target).data("issuer", issuer).build());
return true;
}
/**
@ -169,7 +168,7 @@ public class Island implements DataObject {
* @param target UUID of the target, must be provided.
* @return {@code true} if the target is successfully unbanned, {@code false} otherwise.
*/
public boolean unban(UUID issuer, UUID target) {
public boolean unban(@Nullable UUID issuer, @NonNull UUID target) {
if (members.remove(target) != null) {
log(new LogEntry.Builder("UNBAN").data("player", target).data("issuer", issuer).build());
return true;
@ -198,7 +197,7 @@ public class Island implements DataObject {
* @param flag - flag
* @return flag value
*/
public int getFlag(Flag flag) {
public int getFlag(@NonNull Flag flag) {
flags.putIfAbsent(flag, flag.getDefaultRank());
return flags.get(flag);
}

View File

@ -98,11 +98,8 @@ public class JoinLeaveListener implements Listener {
&& Bukkit.getOfflinePlayer(uuid).getLastPlayed() < timestamp)
.toArray());
if (!candidates.isEmpty()) {
if (!plugin.getSettings().isAutoOwnershipTransferIgnoreRanks()) {
// Ranks are not ignored, our candidates can only have the highest rank
}
if (!candidates.isEmpty() && !plugin.getSettings().isAutoOwnershipTransferIgnoreRanks()) {
// Ranks are not ignored, our candidates can only have the highest rank
}
}
});

View File

@ -4,10 +4,13 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.event.Listener;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.lists.Flags;
@ -19,15 +22,15 @@ import world.bentobox.bentobox.lists.Flags;
public class FlagsManager {
private BentoBox plugin;
private List<Flag> flags = new ArrayList<>();
private List<@NonNull Flag> flags = new ArrayList<>();
/**
* Stores the flag listeners that have already been registered into Bukkit's API to avoid duplicates.
* Value is true if the listener has been registered already
*/
private Map<Listener, Boolean> registeredListeners = new HashMap<>();
private Map<@NonNull Listener, @NonNull Boolean> registeredListeners = new HashMap<>();
public FlagsManager(BentoBox plugin) {
public FlagsManager(@NonNull BentoBox plugin) {
this.plugin = plugin;
// Register default flags
@ -39,7 +42,7 @@ public class FlagsManager {
* @param flag flag to be registered
* @return true if successfully registered, false if not, e.g., because one with the same ID already exists
*/
public boolean registerFlag(Flag flag) {
public boolean registerFlag(@NonNull Flag flag) {
// Check in case the flag id or icon already exists
for (Flag fl : flags) {
if (fl.getID().equals(flag.getID()) || fl.getIcon().equals(flag.getIcon())) {
@ -64,7 +67,7 @@ public class FlagsManager {
* Tries to register a listener
* @param l - listener
*/
private void registerListener(Listener l) {
private void registerListener(@NonNull Listener l) {
registeredListeners.putIfAbsent(l, false);
if (!registeredListeners.get(l)) {
Bukkit.getServer().getPluginManager().registerEvents(l, plugin);
@ -75,6 +78,7 @@ public class FlagsManager {
/**
* @return list of all flags
*/
@NonNull
public List<Flag> getFlags() {
return flags;
}
@ -83,8 +87,22 @@ public class FlagsManager {
* Get flag by ID
* @param id unique id for this flag
* @return Flag or null if not known
* @deprecated As of 1.1, use {@link #getFlag(String)} instead.
*/
public Flag getFlagByID(String id) {
return flags.stream().filter(flag -> flag.getID().equals(id)).findFirst().orElse(null);
@Deprecated
@Nullable
public Flag getFlagByID(@NonNull String id) {
return getFlag(id).orElse(null);
}
/**
* Gets a Flag by providing an ID.
* @param id Unique ID for this Flag.
* @return Optional containing the Flag instance or empty.
* @since 1.1
*/
@NonNull
public Optional<Flag> getFlag(@NonNull String id) {
return flags.stream().filter(flag -> flag.getID().equals(id)).findFirst();
}
}