diff --git a/engine/src/main/java/com/arcadedb/database/BaseDocument.java b/engine/src/main/java/com/arcadedb/database/BaseDocument.java index e280ed06e2..cc835a0917 100644 --- a/engine/src/main/java/com/arcadedb/database/BaseDocument.java +++ b/engine/src/main/java/com/arcadedb/database/BaseDocument.java @@ -17,11 +17,13 @@ import com.arcadedb.schema.DocumentType; import com.arcadedb.schema.Type; +import com.arcadedb.serializer.JavaBinarySerializer; +import java.io.*; import java.math.*; import java.util.*; -public abstract class BaseDocument extends BaseRecord implements Document { +public abstract class BaseDocument extends BaseRecord implements Document, Serializable, Externalizable { protected final DocumentType type; protected int propertiesStartingPosition = 1; @@ -118,4 +120,14 @@ public void reload() { if (buffer != null) buffer.position(propertiesStartingPosition); } + + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + JavaBinarySerializer.writeExternal(this, out); + } + + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + JavaBinarySerializer.readExternal(this, in); + } } diff --git a/engine/src/main/java/com/arcadedb/database/ImmutableDocument.java b/engine/src/main/java/com/arcadedb/database/ImmutableDocument.java index 7db2987440..a149318232 100644 --- a/engine/src/main/java/com/arcadedb/database/ImmutableDocument.java +++ b/engine/src/main/java/com/arcadedb/database/ImmutableDocument.java @@ -19,8 +19,7 @@ import com.arcadedb.schema.DocumentType; import org.json.JSONObject; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Immutable document implementation. To modify the record, you need to get the mutable representation by calling {@link #modify()}. This implementation keeps the diff --git a/engine/src/main/java/com/arcadedb/database/MutableDocument.java b/engine/src/main/java/com/arcadedb/database/MutableDocument.java index 65fd2729ee..8b96eb4bb7 100644 --- a/engine/src/main/java/com/arcadedb/database/MutableDocument.java +++ b/engine/src/main/java/com/arcadedb/database/MutableDocument.java @@ -64,12 +64,13 @@ public synchronized void unsetDirty() { dirty = false; } - public synchronized void fromMap(final Map map) { + public synchronized MutableDocument fromMap(final Map map) { this.map = new LinkedHashMap<>(map.size()); for (Map.Entry entry : map.entrySet()) this.map.put(entry.getKey(), convertValueToSchemaType(entry.getKey(), entry.getValue(), type)); dirty = true; + return this; } @Override diff --git a/engine/src/main/java/com/arcadedb/database/RID.java b/engine/src/main/java/com/arcadedb/database/RID.java index 0d080499aa..31af002f09 100644 --- a/engine/src/main/java/com/arcadedb/database/RID.java +++ b/engine/src/main/java/com/arcadedb/database/RID.java @@ -187,4 +187,8 @@ else if (offset < other.offset) public Database getDatabase() { return database; } + + public boolean isValid() { + return bucketId > -1 && offset > -1; + } } diff --git a/engine/src/main/java/com/arcadedb/graph/MutableEdge.java b/engine/src/main/java/com/arcadedb/graph/MutableEdge.java index 5850b5a9c3..8f8ca99d66 100644 --- a/engine/src/main/java/com/arcadedb/graph/MutableEdge.java +++ b/engine/src/main/java/com/arcadedb/graph/MutableEdge.java @@ -15,7 +15,11 @@ */ package com.arcadedb.graph; -import com.arcadedb.database.*; +import com.arcadedb.database.Binary; +import com.arcadedb.database.Database; +import com.arcadedb.database.MutableDocument; +import com.arcadedb.database.RID; +import com.arcadedb.database.Transaction; import com.arcadedb.schema.DocumentType; import com.arcadedb.serializer.BinaryTypes; @@ -142,6 +146,14 @@ public Edge asEdge(final boolean loadContent) { return this; } + public void setOut(final RID out) { + this.out = out; + } + + public void setIn(final RID in) { + this.in = in; + } + private void init() { if (buffer != null) { buffer.position(1); diff --git a/engine/src/main/java/com/arcadedb/graph/MutableVertex.java b/engine/src/main/java/com/arcadedb/graph/MutableVertex.java index da22e15ef1..c754285576 100644 --- a/engine/src/main/java/com/arcadedb/graph/MutableVertex.java +++ b/engine/src/main/java/com/arcadedb/graph/MutableVertex.java @@ -15,7 +15,12 @@ */ package com.arcadedb.graph; -import com.arcadedb.database.*; +import com.arcadedb.database.Binary; +import com.arcadedb.database.Database; +import com.arcadedb.database.Identifiable; +import com.arcadedb.database.MutableDocument; +import com.arcadedb.database.RID; +import com.arcadedb.database.Transaction; import com.arcadedb.schema.DocumentType; /** @@ -66,6 +71,10 @@ public MutableVertex set(final String name, final Object value) { return this; } + public synchronized MutableVertex set(final Object... properties) { + return (MutableVertex) super.set(properties); + } + @Override public void setBuffer(final Binary buffer) { super.setBuffer(buffer); diff --git a/engine/src/main/java/com/arcadedb/serializer/JavaBinarySerializer.java b/engine/src/main/java/com/arcadedb/serializer/JavaBinarySerializer.java new file mode 100644 index 0000000000..c08dcc17b4 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/serializer/JavaBinarySerializer.java @@ -0,0 +1,135 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arcadedb.serializer; + +import com.arcadedb.database.Binary; +import com.arcadedb.database.DatabaseInternal; +import com.arcadedb.database.Document; +import com.arcadedb.database.MutableDocument; +import com.arcadedb.database.RID; +import com.arcadedb.graph.Edge; +import com.arcadedb.graph.MutableEdge; +import com.arcadedb.graph.MutableVertex; +import com.arcadedb.graph.Vertex; + +import java.io.*; +import java.util.*; + +/** + * Java binary serializer. Used to serialize/deserialize Java objects. Null properties are excluded from serialization. + */ +public class JavaBinarySerializer { + public static void writeExternal(final Document document, final ObjectOutput out) throws IOException { + // RID + final RID rid = document.getIdentity(); + out.writeInt(rid != null ? rid.getBucketId() : -1); + out.writeLong(rid != null ? rid.getPosition() : -1); + + final DatabaseInternal db = ((DatabaseInternal) document.getDatabase()); + + final BinarySerializer serializer = db.getSerializer(); + + final Binary buffer = db.getContext().getTemporaryBuffer1(); + + // PROPERTY COUNT + final Set properties = document.getPropertyNames(); + out.writeInt(properties.size()); + for (String propName : properties) { + // PROPERTY NAME + out.writeUTF(propName); + + final Object propValue = document.get(propName); + if (propValue != null) { + // PROPERTY VALUE + buffer.clear(); + + final byte type = BinaryTypes.getTypeFromValue(propValue); + buffer.putByte(type); + serializer.serializeValue(db, buffer, type, propValue); + buffer.flip(); + + out.writeInt(buffer.size()); + out.write(buffer.getContent(), 0, buffer.size()); + } + } + + // SPECIAL OPERATION FOR VERTICES AND EDGES + if (document instanceof Vertex) { + final RID outRID = ((MutableVertex) document).getOutEdgesHeadChunk(); + out.writeInt(outRID != null ? outRID.getBucketId() : -1); + out.writeLong(outRID != null ? outRID.getPosition() : -1); + + final RID inRID = ((MutableVertex) document).getInEdgesHeadChunk(); + out.writeInt(inRID != null ? inRID.getBucketId() : -1); + out.writeLong(inRID != null ? inRID.getPosition() : -1); + } else if (document instanceof Edge) { + final RID outRID = ((MutableEdge) document).getOut(); + out.writeInt(outRID != null ? outRID.getBucketId() : -1); + out.writeLong(outRID != null ? outRID.getPosition() : -1); + + final RID inRID = ((MutableEdge) document).getIn(); + out.writeInt(inRID != null ? inRID.getBucketId() : -1); + out.writeLong(inRID != null ? inRID.getPosition() : -1); + } + } + + public static void readExternal(final Document document, final ObjectInput in) throws IOException, ClassNotFoundException { + if (!(document instanceof MutableDocument)) + throw new IllegalStateException("Error on deserialization: the current object is immutable"); + + final MutableDocument mutable = (MutableDocument) document; + + final DatabaseInternal db = ((DatabaseInternal) document.getDatabase()); + + // RID + final RID rid = new RID(db, in.readInt(), in.readLong()); + mutable.setIdentity(rid.isValid() ? rid : null); + + // PROPERTIES + final Map properties = new LinkedHashMap<>(); + final int propertyCount = in.readInt(); + for (int i = 0; i < propertyCount; i++) { + final String propName = in.readUTF(); + final int propertySize = in.readInt(); + + final byte[] array = new byte[propertySize]; + in.read(array); + + Binary buffer = new Binary(array); + final byte propType = buffer.getByte(); + final Object propValue = db.getSerializer().deserializeValue(db, buffer, propType, null); + + properties.put(propName, propValue); + } + mutable.fromMap(properties); + + // SPECIAL OPERATION FOR VERTICES AND EDGES + if (document instanceof Vertex) { + final RID outRID = new RID(db, in.readInt(), in.readLong()); + ((MutableVertex) document).setOutEdgesHeadChunk(outRID.isValid() ? outRID : null); + + final RID inRID = new RID(db, in.readInt(), in.readLong()); + ((MutableVertex) document).setInEdgesHeadChunk(inRID.isValid() ? inRID : null); + + } else if (document instanceof Edge) { + final RID outRID = new RID(db, in.readInt(), in.readLong()); + ((MutableEdge) document).setOut(outRID.isValid() ? outRID : null); + + final RID inRID = new RID(db, in.readInt(), in.readLong()); + ((MutableEdge) document).setIn(inRID.isValid() ? inRID : null); + } + } +} diff --git a/engine/src/test/java/com/arcadedb/serializer/JavaBinarySerializerTest.java b/engine/src/test/java/com/arcadedb/serializer/JavaBinarySerializerTest.java new file mode 100644 index 0000000000..7f7c0f083d --- /dev/null +++ b/engine/src/test/java/com/arcadedb/serializer/JavaBinarySerializerTest.java @@ -0,0 +1,162 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arcadedb.serializer; + +import com.arcadedb.TestHelper; +import com.arcadedb.database.MutableDocument; +import com.arcadedb.graph.MutableEdge; +import com.arcadedb.graph.MutableVertex; +import com.arcadedb.schema.DocumentType; +import com.arcadedb.schema.EdgeType; +import com.arcadedb.schema.Type; +import com.arcadedb.schema.VertexType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.*; + +public class JavaBinarySerializerTest extends TestHelper { + + @Test + public void testDocumentTransient() throws IOException, ClassNotFoundException { + final DocumentType type = database.getSchema().createDocumentType("Doc"); + type.createProperty("id", Type.LONG); + + final MutableDocument doc1 = database.newDocument("Doc").set("id", 100L, "name", "Elon"); + try (ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(); ObjectOutput buffer = new ObjectOutputStream(arrayOut);) { + doc1.writeExternal(buffer); + buffer.flush(); + + Assertions.assertTrue(arrayOut.size() > 0); + + final MutableDocument doc2 = database.newDocument("Doc"); + + try (ByteArrayInputStream arrayIn = new ByteArrayInputStream(arrayOut.toByteArray()); ObjectInput in = new ObjectInputStream(arrayIn)) { + doc2.readExternal(in); + Assertions.assertEquals(doc1, doc2); + Assertions.assertEquals(doc1.toMap(), doc2.toMap()); + } + } + } + + @Test + public void testDocumentPersistent() throws IOException, ClassNotFoundException { + final DocumentType type = database.getSchema().createDocumentType("Doc"); + type.createProperty("id", Type.LONG); + + database.setAutoTransaction(true); + final MutableDocument doc1 = database.newDocument("Doc").set("id", 100L, "name", "Elon"); + doc1.save(); + + try (ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(); ObjectOutput buffer = new ObjectOutputStream(arrayOut);) { + doc1.writeExternal(buffer); + buffer.flush(); + + Assertions.assertTrue(arrayOut.size() > 0); + + final MutableDocument doc2 = database.newDocument("Doc"); + + try (ByteArrayInputStream arrayIn = new ByteArrayInputStream(arrayOut.toByteArray()); ObjectInput in = new ObjectInputStream(arrayIn)) { + doc2.readExternal(in); + Assertions.assertEquals(doc1, doc2); + Assertions.assertEquals(doc1.toMap(), doc2.toMap()); + } + } + } + + @Test + public void testVertexTransient() throws IOException, ClassNotFoundException { + final VertexType type = database.getSchema().createVertexType("Doc"); + type.createProperty("id", Type.LONG); + + final MutableVertex doc1 = database.newVertex("Doc").set("id", 100L, "name", "Elon"); + try (ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(); ObjectOutput buffer = new ObjectOutputStream(arrayOut);) { + doc1.writeExternal(buffer); + buffer.flush(); + + Assertions.assertTrue(arrayOut.size() > 0); + + final MutableVertex docTest = database.newVertex("Doc"); + + try (ByteArrayInputStream arrayIn = new ByteArrayInputStream(arrayOut.toByteArray()); ObjectInput in = new ObjectInputStream(arrayIn)) { + docTest.readExternal(in); + Assertions.assertEquals(doc1, docTest); + Assertions.assertEquals(doc1.toMap(), docTest.toMap()); + } + } + } + + @Test + public void testVertexPersistent() throws IOException, ClassNotFoundException { + final VertexType type = database.getSchema().createVertexType("Doc"); + database.getSchema().createEdgeType("Edge"); + type.createProperty("id", Type.LONG); + + database.setAutoTransaction(true); + final MutableVertex v1 = database.newVertex("Doc").set("id", 100L, "name", "Elon"); + v1.save(); + final MutableVertex v2 = database.newVertex("Doc").set("id", 101L, "name", "Jay"); + v2.save(); + v1.newEdge("Edge", v2, true); + + try (ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(); ObjectOutput buffer = new ObjectOutputStream(arrayOut);) { + v1.writeExternal(buffer); + buffer.flush(); + + Assertions.assertTrue(arrayOut.size() > 0); + + final MutableVertex vTest = database.newVertex("Doc"); + + try (ByteArrayInputStream arrayIn = new ByteArrayInputStream(arrayOut.toByteArray()); ObjectInput in = new ObjectInputStream(arrayIn)) { + vTest.readExternal(in); + Assertions.assertEquals(v1, vTest); + Assertions.assertEquals(v1.toMap(), vTest.toMap()); + Assertions.assertEquals(v1.getOutEdgesHeadChunk(), vTest.getOutEdgesHeadChunk()); + Assertions.assertEquals(v1.getInEdgesHeadChunk(), vTest.getInEdgesHeadChunk()); + } + } + } + + @Test + public void testEdgePersistent() throws IOException, ClassNotFoundException { + database.getSchema().createVertexType("Doc"); + final EdgeType type = database.getSchema().createEdgeType("Edge"); + + database.setAutoTransaction(true); + final MutableVertex v1 = database.newVertex("Doc").set("id", 100L, "name", "Elon"); + v1.save(); + final MutableVertex v2 = database.newVertex("Doc").set("id", 101L, "name", "Jay"); + v2.save(); + MutableEdge edge1 = v1.newEdge("Edge", v2, true); + + try (ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(); ObjectOutput buffer = new ObjectOutputStream(arrayOut);) { + edge1.writeExternal(buffer); + buffer.flush(); + + Assertions.assertTrue(arrayOut.size() > 0); + + final MutableEdge edgeTest = new MutableEdge(database, type, null); + + try (ByteArrayInputStream arrayIn = new ByteArrayInputStream(arrayOut.toByteArray()); ObjectInput in = new ObjectInputStream(arrayIn)) { + edgeTest.readExternal(in); + Assertions.assertEquals(edge1, edgeTest); + Assertions.assertEquals(edge1.toMap(), edgeTest.toMap()); + Assertions.assertEquals(edge1.getOut(), edgeTest.getOut()); + Assertions.assertEquals(edge1.getIn(), edgeTest.getIn()); + } + } + } +}