diff --git a/build.gradle b/build.gradle index 2d1d998..7b8c5ff 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,8 @@ repositories { } dependencies { - compile group: 'me.joshlarson', name: 'jlcommon', version: '1.8.4' + compile group: 'me.joshlarson', name: 'jlcommon', version: '1.9.0' compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.60' + compileOnly group: 'org.mongodb', name: 'mongodb-driver-sync', version: '3.9.1' testCompile 'junit:junit:4.12' } diff --git a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java new file mode 100644 index 0000000..280b9bd --- /dev/null +++ b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java @@ -0,0 +1,356 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + ***********************************************************************************/ + +package com.projectswg.common.data.encodables.mongo; + +import org.bson.Document; +import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.Instant; +import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class MongoData { + + private final @NotNull Document doc; + + public MongoData() { + this(new Document()); + } + + public MongoData(Document doc) { + this.doc = doc == null ? new Document() : doc; + } + + public Document toDocument() { + return new Document(doc); + } + + public boolean containsKey(String key) { + return doc.containsKey(key); + } + + @Nullable + public Integer getInteger(String key) { + return doc.getInteger(key); + } + + public int getInteger(String key, int defaultValue) { + return doc.getInteger(key, defaultValue); + } + + @Nullable + public Long getLong(String key) { + return doc.getLong(key); + } + + public long getLong(String key, long defaultValue) { + Long l = doc.getLong(key); + return l == null ? defaultValue : l; + } + + @Nullable + public Float getFloat(String key) { + Double f = doc.getDouble(key); + return f == null ? null : f.floatValue(); + } + + public float getFloat(String key, float defaultValue) { + Double f = doc.getDouble(key); + return f == null ? defaultValue : f.floatValue(); + } + + @Nullable + public Double getDouble(String key) { + return doc.getDouble(key); + } + + public double getDouble(String key, double defaultValue) { + Double d = doc.getDouble(key); + return d == null ? defaultValue : d; + } + + @Nullable + public String getString(String key) { + return doc.getString(key); + } + + public String getString(String key, String defaultValue) { + String str = doc.getString(key); + return str == null ? defaultValue : str; + } + + @Nullable + public Boolean getBoolean(String key) { + return doc.getBoolean(key); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return doc.getBoolean(key, defaultValue); + } + + @Nullable + public ObjectId getObjectId(String key) { + return doc.getObjectId(key); + } + + @Nullable + public Instant getDate(String key) { + @SuppressWarnings("UseOfObsoleteDateTimeApi") // forced to by MongoDB + Date d = doc.getDate(key); + return d == null ? null : d.toInstant(); + } + + @NotNull + public List getArray(String key, Class klass) { + List mdbArray = doc.get(key, List.class); + List ret = new ArrayList<>(mdbArray.size()); + if (klass == Instant.class) { + for (Object o : mdbArray) { + if (o == null) + ret.add(null); + else + //noinspection UseOfObsoleteDateTimeApi + ret.add(klass.cast(((Date) o).toInstant())); + } + } else { + for (Object o : mdbArray) { + if (o == null) + ret.add(null); + else + ret.add(klass.cast(o)); + } + } + return ret; + } + + @NotNull + public MongoData getDocument(String key) { + return new MongoData(doc.get(key, Document.class)); + } + + /** + * Attempts to parse the document at the specified key. If the document exists, it will be parsed by the specified data instance. + * @param key the key to look up + * @param dataInstance the data instance to use + * @param the parser type + * @return the parsed data, or null if the value does not exist + */ + @Nullable + public T getDocument(String key, T dataInstance) { + Document doc = this.doc.get(key, Document.class); + if (doc != null) + dataInstance.read(new MongoData(doc)); + return dataInstance; + } + + @NotNull + public Map getMap(String key, Class klass) { + Document mdbMap = doc.get(key, Document.class); + Map ret = new HashMap<>(mdbMap.size()); + if (klass == Instant.class) { + for (Entry e : mdbMap.entrySet()) { + Object o = e.getValue(); + if (o == null) + ret.put(e.getKey(), null); + else + //noinspection UseOfObsoleteDateTimeApi + ret.put(e.getKey(), klass.cast(((Date) o).toInstant())); + } + } else { + for (Entry e : mdbMap.entrySet()) { + Object o = e.getValue(); + if (o == null) + ret.put(e.getKey(), null); + else + ret.put(e.getKey(), klass.cast(o)); + } + } + return ret; + } + + public void putInteger(String key, int i) { + assert !containsKey(key) : "key already exists"; + doc.put(key, i); + } + + public void putLong(String key, long l) { + assert !containsKey(key) : "key already exists"; + doc.put(key, l); + } + + public void putFloat(String key, float f) { + assert !containsKey(key) : "key already exists"; + doc.put(key, f); + } + + public void putDouble(String key, double d) { + assert !containsKey(key) : "key already exists"; + doc.put(key, d); + } + + public void putString(String key, String str) { + assert !containsKey(key) : "key already exists"; + doc.put(key, str); + } + + public void putBoolean(String key, boolean bool) { + assert !containsKey(key) : "key already exists"; + doc.put(key, bool); + } + + public void putObjectId(String key, ObjectId id) { + assert !containsKey(key) : "key already exists"; + doc.put(key, id); + } + + public void putDate(String key, Instant date) { + assert !containsKey(key) : "key already exists"; + doc.put(key, Date.from(date)); + } + + public void putArray(String key, byte [] array) { + assert !containsKey(key) : "key already exists"; + List mdbArray = new ArrayList<>(array.length); + for (byte b : array) + mdbArray.add((int) b); + doc.put(key, mdbArray); + } + + public void putArray(String key, short [] array) { + assert !containsKey(key) : "key already exists"; + List mdbArray = new ArrayList<>(array.length); + for (short s : array) + mdbArray.add((int) s); + doc.put(key, mdbArray); + } + + public void putArray(String key, int [] array) { + assert !containsKey(key) : "key already exists"; + doc.put(key, List.of(array)); + } + + public void putArray(String key, long [] array) { + assert !containsKey(key) : "key already exists"; + doc.put(key, List.of(array)); + } + + public void putArray(String key, String [] array) { + assert !containsKey(key) : "key already exists"; + doc.put(key, List.of(array)); + } + + public void putArray(String key, ObjectId [] array) { + assert !containsKey(key) : "key already exists"; + doc.put(key, List.of(array)); + } + + public void putArray(String key, Instant [] array) { + assert !containsKey(key) : "key already exists"; + @SuppressWarnings("UseOfObsoleteDateTimeApi") // forced to by MongoDB + List mdbArray = new ArrayList<>(array.length); + for (Instant i : array) + mdbArray.add(Date.from(i)); + doc.put(key, mdbArray); + } + + public void putArray(String key, List array) { + assert !containsKey(key) : "key already exists"; + List mdbArray = new ArrayList<>(array.size()); + for (Object o : array) { + mdbArray.add(translatePut(o)); + } + doc.put(key, mdbArray); + } + + public void putArray(String key, List array, Function converter) { + assert !containsKey(key) : "key already exists"; + List mdbArray = new ArrayList<>(array.size()); + for (T o : array) { + mdbArray.add(translatePut(converter.apply(o))); + } + doc.put(key, mdbArray); + } + + public void putDocument(String key, @NotNull MongoData doc) { + assert !containsKey(key) : "key already exists"; + this.doc.put(key, doc.doc); + } + + public void putDocument(String key, T data) { + assert !containsKey(key) : "key already exists"; + if (data != null) { + MongoData dataDoc = new MongoData(); + data.save(dataDoc); + doc.put(key, dataDoc.doc); + } + } + + public void putMap(String key, Map data) { + MongoData doc = new MongoData(); + for (Entry e : data.entrySet()) { + doc.doc.put(e.getKey(), translatePut(e.getValue())); + } + putDocument(key, doc); + } + + public void putMap(String key, Map data, Function keyExtractor) { + MongoData doc = new MongoData(); + for (Entry e : data.entrySet()) { + doc.doc.put(keyExtractor.apply(e.getKey()), translatePut(e.getValue())); + } + putDocument(key, doc); + } + + public void putMap(String key, Map data, Function keyExtractor, BiFunction valueExtractor) { + MongoData doc = new MongoData(); + for (Entry e : data.entrySet()) { + doc.doc.put(keyExtractor.apply(e.getKey()), translatePut(valueExtractor.apply(e.getKey(), e.getValue()))); + } + putDocument(key, doc); + } + + @SuppressWarnings("UseOfObsoleteDateTimeApi") + private Object translatePut(Object input) { + if (input instanceof Instant) { + return Date.from((Instant) input); + } else if (input instanceof String || input instanceof Boolean || input instanceof Long || input instanceof Date || input instanceof Document) { + return input; + } else if (input instanceof Float || input instanceof Double) { + return ((Number) input).doubleValue(); + } else if (input instanceof Number) { + return ((Number) input).intValue(); + } else + assert false : "bad object type: " + input; + return null; + } + +} diff --git a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java new file mode 100644 index 0000000..12d2a3d --- /dev/null +++ b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java @@ -0,0 +1,35 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + ***********************************************************************************/ + +package com.projectswg.common.data.encodables.mongo; + +public interface MongoPersistable { + + void read(MongoData data); + void save(MongoData data); + +} diff --git a/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java b/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java index 59ed4a9..af3d0e3 100644 --- a/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java +++ b/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java @@ -26,12 +26,14 @@ ***********************************************************************************/ package com.projectswg.common.data.encodables.oob; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import me.joshlarson.jlcommon.log.Log; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; -public class StringId implements OutOfBandData, Persistable { +public class StringId implements OutOfBandData, Persistable, MongoPersistable { private String key; private String file; @@ -94,6 +96,18 @@ public class StringId implements OutOfBandData, Persistable { key = stream.getAscii(); } + @Override + public void read(MongoData data) { + file = data.getString("file"); + key = data.getString("key"); + } + + @Override + public void save(MongoData data) { + data.putString("file", file); + data.putString("key", key); + } + @Override public OutOfBandPackage.Type getOobType() { return OutOfBandPackage.Type.STRING_ID; diff --git a/src/main/java/com/projectswg/common/data/location/Location.java b/src/main/java/com/projectswg/common/data/location/Location.java index bc254dc..cc70a9c 100644 --- a/src/main/java/com/projectswg/common/data/location/Location.java +++ b/src/main/java/com/projectswg/common/data/location/Location.java @@ -26,12 +26,14 @@ ***********************************************************************************/ package com.projectswg.common.data.location; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.encoding.Encodable; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; -public class Location implements Encodable, Persistable { +public class Location implements Encodable, Persistable, MongoPersistable { private static final Location ZERO = new Location(0, 0, 0, Terrain.GONE); @@ -243,6 +245,21 @@ public class Location implements Encodable, Persistable { terrain = Terrain.valueOf(stream.getAscii()); } + @Override + public void read(MongoData data) { + data.getDocument("orientation", orientation); + data.getDocument("point", point); + terrain = data.containsKey("terrain") ? Terrain.valueOf(data.getString("terrain")) : null; + } + + @Override + public void save(MongoData data) { + data.putDocument("orientation", orientation); + data.putDocument("point", point); + if (terrain != null) + data.putString("terrain", terrain.name()); + } + @Override public String toString() { return String.format("Location[TRN=%s, %s %s]", terrain, point, orientation); diff --git a/src/main/java/com/projectswg/common/data/location/Point3D.java b/src/main/java/com/projectswg/common/data/location/Point3D.java index 3ba8a87..a383612 100644 --- a/src/main/java/com/projectswg/common/data/location/Point3D.java +++ b/src/main/java/com/projectswg/common/data/location/Point3D.java @@ -26,12 +26,14 @@ ***********************************************************************************/ package com.projectswg.common.data.location; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.encoding.Encodable; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; -public class Point3D implements Encodable, Persistable { +public class Point3D implements Encodable, Persistable, MongoPersistable { private double x; private double y; @@ -202,7 +204,21 @@ public class Point3D implements Encodable, Persistable { y = stream.getFloat(); z = stream.getFloat(); } - + + @Override + public void read(MongoData data) { + x = data.getDouble("x", 0); + y = data.getDouble("y", 0); + z = data.getDouble("z", 0); + } + + @Override + public void save(MongoData data) { + data.putDouble("x", x); + data.putDouble("y", y); + data.putDouble("z", z); + } + @Override public boolean equals(Object o) { if (!(o instanceof Point3D)) diff --git a/src/main/java/com/projectswg/common/data/location/Quaternion.java b/src/main/java/com/projectswg/common/data/location/Quaternion.java index 7b2d1c5..1fb37d2 100644 --- a/src/main/java/com/projectswg/common/data/location/Quaternion.java +++ b/src/main/java/com/projectswg/common/data/location/Quaternion.java @@ -26,13 +26,15 @@ ***********************************************************************************/ package com.projectswg.common.data.location; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.encoding.Encodable; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; import me.joshlarson.jlcommon.utilities.Arguments; -public class Quaternion implements Encodable, Persistable { +public class Quaternion implements Encodable, Persistable, MongoPersistable { private final double [][] rotationMatrix; private double x; @@ -236,7 +238,23 @@ public class Quaternion implements Encodable, Persistable { w = stream.getFloat(); updateRotationMatrix(); } - + + @Override + public void read(MongoData data) { + x = data.getDouble("x", 0); + y = data.getDouble("y", 0); + z = data.getDouble("z", 0); + w = data.getDouble("w", 1); + } + + @Override + public void save(MongoData data) { + data.putDouble("x", x); + data.putDouble("y", y); + data.putDouble("z", z); + data.putDouble("w", w); + } + @Override public String toString() { return String.format("Quaternion[%.3f, %.3f, %.3f, %.3f]", x, y, z, w); diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 9392601..adfd3fb 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ module com.projectswg.common { requires transitive me.joshlarson.jlcommon; requires static java.sql; requires static java.desktop; + requires static org.mongodb.bson; requires org.jetbrains.annotations; requires org.bouncycastle.provider; @@ -11,6 +12,7 @@ module com.projectswg.common { exports com.projectswg.common.data.encodables.chat; exports com.projectswg.common.data.encodables.galaxy; exports com.projectswg.common.data.encodables.map; + exports com.projectswg.common.data.encodables.mongo; exports com.projectswg.common.data.encodables.oob; exports com.projectswg.common.data.encodables.oob.waypoint; exports com.projectswg.common.data.encodables.player;