From db932dd83604d8c9587b254cf4ceaf37c5615245 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Jan 2019 15:32:52 -0800 Subject: [PATCH] Fix for LogEntry serialization. https://github.com/BentoBoxWorld/BentoBox/issues/486 Includes an autofixer for corrupted files and test for the adapter. LogEntry was changed to just store strings instead of arbitrary types. Unless the log serializer were to store hints as to what type of data was being stored, it is impossible to deseralize. To make it all simpler, we just store strings. If the UUIDs need to be converted back to players are some point, that can be done based on the type of the log stored. --- .../bentobox/bentobox/api/logs/LogEntry.java | 18 ++--- .../bentobox/database/objects/Island.java | 8 +-- .../objects/adapters/LogEntryListAdapter.java | 31 ++------- .../database/yaml/YamlDatabaseConnector.java | 45 ++++++++++-- .../world/bentobox/bentobox/TestBentoBox.java | 8 +-- .../adapters/LogEntryListAdapterTest.java | 68 +++++++++++++++++++ 6 files changed, 128 insertions(+), 50 deletions(-) create mode 100644 src/test/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapterTest.java diff --git a/src/main/java/world/bentobox/bentobox/api/logs/LogEntry.java b/src/main/java/world/bentobox/bentobox/api/logs/LogEntry.java index 786523164..061480460 100644 --- a/src/main/java/world/bentobox/bentobox/api/logs/LogEntry.java +++ b/src/main/java/world/bentobox/bentobox/api/logs/LogEntry.java @@ -1,12 +1,12 @@ 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; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + /** * Represents an event that occurred and that is logged. *
@@ -18,7 +18,7 @@ import java.util.Map; public class LogEntry { private final long timestamp; private final String type; - private final Map data; + private final Map data; private LogEntry(@NonNull Builder builder) { this.timestamp = builder.timestamp; @@ -36,14 +36,14 @@ public class LogEntry { } @Nullable - public Map getData() { + public Map getData() { return data; } public static class Builder { private long timestamp; private String type; - private Map data; + private Map data; public Builder(@NonNull String type) { this.timestamp = System.currentTimeMillis(); @@ -56,8 +56,8 @@ public class LogEntry { return this; } - public Builder data(Map<@NonNull String, @Nullable Object> data) { - this.data = data; + public Builder data(Map data2) { + this.data = data2; return this; } @@ -67,7 +67,7 @@ public class LogEntry { * @param value value to set * @return the Builder instance */ - public Builder data(@NonNull String key, @Nullable Object value) { + public Builder data(@NonNull String key, @Nullable String value) { this.data.put(key, value); return this; } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index beb41e208..539dab700 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -140,9 +140,9 @@ public class Island implements DataObject { * @param target UUID of the target, must be provided. * @return {@code true} */ - public boolean ban(@Nullable UUID issuer, @NonNull UUID target) { + public boolean ban(@NonNull UUID issuer, @NonNull UUID target) { setRank(target, RanksManager.BANNED_RANK); - log(new LogEntry.Builder("BAN").data("player", target).data("issuer", issuer).build()); + log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); return true; } @@ -168,9 +168,9 @@ 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(@Nullable UUID issuer, @NonNull UUID target) { + public boolean unban(@NonNull UUID issuer, @NonNull UUID target) { if (members.remove(target) != null) { - log(new LogEntry.Builder("UNBAN").data("player", target).data("issuer", issuer).build()); + log(new LogEntry.Builder("UNBAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); return true; } return false; diff --git a/src/main/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapter.java b/src/main/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapter.java index f37e2d314..a4f8b5954 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapter.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapter.java @@ -1,14 +1,11 @@ package world.bentobox.bentobox.database.objects.adapters; -import org.eclipse.jdt.annotation.NonNull; -import world.bentobox.bentobox.api.logs.LogEntry; - -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.UUID; + +import world.bentobox.bentobox.api.logs.LogEntry; /** * @author Poslovitch @@ -45,7 +42,7 @@ public class LogEntryListAdapter implements AdapterInterface, Lis for (Map entry : serialized) { long timestamp = (long) entry.get(TIMESTAMP); String type = (String) entry.get(TYPE); - Map data = (Map) entry.get(DATA); + Map data = (Map) entry.get(DATA); result.add(new LogEntry.Builder(type).timestamp(timestamp).data(data).build()); } @@ -68,7 +65,7 @@ public class LogEntryListAdapter implements AdapterInterface, Lis value.put(TYPE, logEntry.getType()); if (logEntry.getData() != null) { - value.put(DATA, tidy(logEntry.getData())); + value.put(DATA, logEntry.getData()); } result.add(value); @@ -77,24 +74,4 @@ public class LogEntryListAdapter implements AdapterInterface, Lis return result; } - /** - * Returns a "data" map that contains "tidied up" values, as some types are not safe to store 'as is'. - * See https://github.com/BentoBoxWorld/BentoBox/issues/486 - * @param data the data map to tidy up - * @return the tidied up data map - * @since 1.1.1 - */ - @NonNull - private Map tidy(@NonNull Map data) { - Map result = new HashMap<>(); - for (Map.Entry entry : data.entrySet()) { - if (entry.getValue() instanceof UUID) { - // UUIDs need some special handling - result.put(entry.getKey(), entry.getValue().toString()); - } else { - result.put(entry.getKey(), entry.getValue()); - } - } - return result; - } } diff --git a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseConnector.java b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseConnector.java index 4e0c7f407..ce5257787 100644 --- a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseConnector.java +++ b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseConnector.java @@ -1,22 +1,23 @@ package world.bentobox.bentobox.database.yaml; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.sql.Connection; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Scanner; -import java.util.UUID; +import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; import com.google.common.base.Charsets; @@ -59,7 +60,20 @@ public class YamlDatabaseConnector implements DatabaseConnector { config = new YamlConfiguration(); config.load(yamlFile); } catch (Exception e) { - plugin.logError("Could not load yml file from database " + tableName + " " + fileName + " " + e.getMessage()); + /* + * TODO: Temporary fix for removing UUID tags. Remove in a few versions + */ + if (e.getMessage().contains("!!java.util.UUID")) { + removeStringFromFile(yamlFile); + // Try again + try { + Objects.requireNonNull(config).load(yamlFile); + } catch (IOException | InvalidConfigurationException e1) { + plugin.logError("Could not load yml file from database " + tableName + " " + fileName + " " + e.getMessage()); + } + } else { + plugin.logError("Could not load yml file from database " + tableName + " " + fileName + " " + e.getMessage()); + } } } else { // Create the missing file @@ -81,6 +95,25 @@ public class YamlDatabaseConnector implements DatabaseConnector { return config; } + private void removeStringFromFile(File yamlFile) { + try { + File temp = File.createTempFile("file", ".tmp", yamlFile.getParentFile()); + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(yamlFile), StandardCharsets.UTF_8)); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(temp), StandardCharsets.UTF_8)); + for (String line; (line = reader.readLine()) != null;) { + line = line.replace("!!java.util.UUID", ""); + writer.println(line); + } + reader.close(); + writer.close(); + if (yamlFile.delete()) { + temp.renameTo(yamlFile); + } + } catch (Exception e) { + plugin.logError("Could not fix Island object - skipping - " + e.getMessage()); + } + } + public void saveYamlFile(String data, String tableName, String fileName, Map commentMap) { String name = fileName.endsWith(YML) ? fileName : fileName + YML; File tableFolder = new File(plugin.getDataFolder(), tableName); diff --git a/src/test/java/world/bentobox/bentobox/TestBentoBox.java b/src/test/java/world/bentobox/bentobox/TestBentoBox.java index ab994a03f..005c89010 100644 --- a/src/test/java/world/bentobox/bentobox/TestBentoBox.java +++ b/src/test/java/world/bentobox/bentobox/TestBentoBox.java @@ -402,7 +402,7 @@ public class TestBentoBox { assertFalse(members.contains(member3)); // Ban member - island.ban(null, member1); + island.ban(UUID.randomUUID(), member1); members = island.getMemberSet(); assertTrue(members.contains(playerUUID)); assertFalse(members.contains(member1)); @@ -413,7 +413,7 @@ public class TestBentoBox { assertTrue(banned.contains(member1)); // Unban - island.unban(null, member1); + island.unban(UUID.randomUUID(), member1); assertFalse(island.getBanned().contains(member1)); // Protection @@ -445,7 +445,7 @@ public class TestBentoBox { // Check if the members have capability User mem1 = User.getInstance(member1); // Visitor User mem2 = User.getInstance(member2); // Member - island.ban(null, member3); + island.ban(member2, member3); User mem3 = User.getInstance(member3); // Banned // Member 1 is a visitor @@ -505,7 +505,7 @@ public class TestBentoBox { assertEquals(fl, customFlag.getListener().get()); // Add it to the Flag Manager flagsManager.registerFlag(customFlag); - assertEquals(customFlag, flagsManager.getFlagByID("CUSTOM_FLAG")); + assertEquals(customFlag, flagsManager.getFlag("CUSTOM_FLAG").get()); } /** diff --git a/src/test/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapterTest.java b/src/test/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapterTest.java new file mode 100644 index 000000000..27fca6397 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/database/objects/adapters/LogEntryListAdapterTest.java @@ -0,0 +1,68 @@ +/** + * + */ +package world.bentobox.bentobox.database.objects.adapters; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Before; +import org.junit.Test; + +import world.bentobox.bentobox.api.logs.LogEntry; + +/** + * @author tastybento + * + */ +public class LogEntryListAdapterTest { + + private LogEntryListAdapter a; + private YamlConfiguration config; + private List history = new LinkedList<>(); + private UUID target; + private UUID issuer; + private List toLog; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + config = new YamlConfiguration(); + a = new LogEntryListAdapter(); + target = UUID.randomUUID(); + issuer = UUID.randomUUID(); + + toLog = new ArrayList<>(); + toLog.add(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); + toLog.add(new LogEntry.Builder("UNBAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); + toLog.add(new LogEntry.Builder("UNOWNED").build()); + toLog.forEach(history::add); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter#serialize(java.lang.Object)} + * and {@link world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter#deserialize(java.lang.Object)}. + */ + @Test + public void testSerializeDeserialize() { + config.set("test.history", a.serialize(history)); + // Verify + List historyCheck = a.deserialize(config.get("test.history")); + assertTrue(historyCheck.size() == 3); + for (int i = 0; i < historyCheck.size(); i++) { + assertEquals(toLog.get(i).getTimestamp(), historyCheck.get(i).getTimestamp()); + assertEquals(toLog.get(i).getType(), historyCheck.get(i).getType()); + assertEquals(toLog.get(i).getData().get("player"), historyCheck.get(i).getData().get("player")); + assertEquals(toLog.get(i).getData().get("issuer"), historyCheck.get(i).getData().get("issuer")); + } + } + +}