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.
This commit is contained in:
tastybento 2019-01-26 15:32:52 -08:00
parent 4e81baf45e
commit db932dd836
6 changed files with 128 additions and 50 deletions

View File

@ -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.
* <br/>
@ -18,7 +18,7 @@ import java.util.Map;
public class LogEntry {
private final long timestamp;
private final String type;
private final Map<String, Object> data;
private final Map<String, String> data;
private LogEntry(@NonNull Builder builder) {
this.timestamp = builder.timestamp;
@ -36,14 +36,14 @@ public class LogEntry {
}
@Nullable
public Map<String, Object> getData() {
public Map<String, String> getData() {
return data;
}
public static class Builder {
private long timestamp;
private String type;
private Map<String, Object> data;
private Map<String, String> 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<String, String> 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;
}

View File

@ -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;

View File

@ -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<List<LogEntry>, Lis
for (Map<String, Object> entry : serialized) {
long timestamp = (long) entry.get(TIMESTAMP);
String type = (String) entry.get(TYPE);
Map<String, Object> data = (Map<String, Object>) entry.get(DATA);
Map<String, String> data = (Map<String, String>) entry.get(DATA);
result.add(new LogEntry.Builder(type).timestamp(timestamp).data(data).build());
}
@ -68,7 +65,7 @@ public class LogEntryListAdapter implements AdapterInterface<List<LogEntry>, 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<List<LogEntry>, 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<String, Object> tidy(@NonNull Map<String, Object> data) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> 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;
}
}

View File

@ -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,8 +60,21 @@ public class YamlDatabaseConnector implements DatabaseConnector {
config = new YamlConfiguration();
config.load(yamlFile);
} catch (Exception e) {
/*
* 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
config = new YamlConfiguration();
@ -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<String, String> commentMap) {
String name = fileName.endsWith(YML) ? fileName : fileName + YML;
File tableFolder = new File(plugin.getDataFolder(), tableName);

View File

@ -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());
}
/**

View File

@ -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<LogEntry> history = new LinkedList<>();
private UUID target;
private UUID issuer;
private List<LogEntry> 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<LogEntry> 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"));
}
}
}