From d3eaaecd6527645544e2bf33efde8dcfc3d0ba0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Galland?= Date: Thu, 8 Nov 2018 19:41:23 +0100 Subject: [PATCH] [core][sre] Ensure API events are fully serializable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Galland --- .../io.sarl.core/META-INF/MANIFEST.MF | 3 +- .../io.sarl.core/src/io/sarl/core/events.sarl | 6 +- .../src/io/sarl/util/Collections3.sarl | 112 +++++++++++++++++- .../network/JavaBinaryEventSerializer.java | 15 ++- .../jdk/spawn/StandardSpawnService.java | 3 +- 5 files changed, 128 insertions(+), 11 deletions(-) diff --git a/main/apiplugins/io.sarl.core/META-INF/MANIFEST.MF b/main/apiplugins/io.sarl.core/META-INF/MANIFEST.MF index 598095e260..796302f999 100644 --- a/main/apiplugins/io.sarl.core/META-INF/MANIFEST.MF +++ b/main/apiplugins/io.sarl.core/META-INF/MANIFEST.MF @@ -6,5 +6,6 @@ Bundle-Version: 0.9.0.qualifier Bundle-Vendor: %Bundle-Vendor Bundle-Name: %Bundle-Name Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: io.sarl.lang.core;bundle-version="0.9.0";visibility:=reexport +Require-Bundle: io.sarl.lang.core;bundle-version="0.9.0";visibility:=reexport, + io.sarl.util;bundle-version="0.9.0" Export-Package: io.sarl.core diff --git a/main/apiplugins/io.sarl.core/src/io/sarl/core/events.sarl b/main/apiplugins/io.sarl.core/src/io/sarl/core/events.sarl index 799cc98552..b526a5a3d4 100644 --- a/main/apiplugins/io.sarl.core/src/io/sarl/core/events.sarl +++ b/main/apiplugins/io.sarl.core/src/io/sarl/core/events.sarl @@ -20,12 +20,14 @@ */ package io.sarl.core +import java.util.Arrays import java.util.Collection import java.util.Collections import java.util.UUID import io.sarl.lang.^annotation.EarlyExit import io.sarl.lang.core.SpaceID import io.sarl.lang.core.Address +import io.sarl.util.Collections3 /** * Initialization event. @@ -160,7 +162,7 @@ final event AgentSpawned { */ new (source : Address, agentType : String, agentID : UUID*) { setSource(source) - this.agentIdentifiers = newImmutableList(agentID) + this.agentIdentifiers = Collections::unmodifiableCollection(Arrays::asList(agentID)) this.agentID = agentID.get(0) this.agentType = agentType } @@ -173,7 +175,7 @@ final event AgentSpawned { */ new (source : Address, agentType : String, agentID : Collection) { setSource(source) - this.agentIdentifiers = Collections::unmodifiableCollection(agentID) + this.agentIdentifiers = Collections::unmodifiableCollection(Collections3::serializableCollection(agentID)) this.agentID = agentID.iterator.next this.agentType = agentType } diff --git a/main/apiplugins/io.sarl.util/src/io/sarl/util/Collections3.sarl b/main/apiplugins/io.sarl.util/src/io/sarl/util/Collections3.sarl index c7e249492e..03bc496bbf 100644 --- a/main/apiplugins/io.sarl.util/src/io/sarl/util/Collections3.sarl +++ b/main/apiplugins/io.sarl.util/src/io/sarl/util/Collections3.sarl @@ -23,9 +23,12 @@ package io.sarl.util import io.sarl.lang.util.SynchronizedCollection import io.sarl.lang.util.SynchronizedIterable import io.sarl.lang.util.SynchronizedSet +import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.io.Serializable +import java.util.AbstractCollection import java.util.AbstractSet +import java.util.ArrayList import java.util.Collection import java.util.Collections import java.util.Iterator @@ -59,7 +62,7 @@ final class Collections3 { */ private static class EmptySet extends AbstractSet implements SynchronizedSet, Serializable { - val mutex = new Object + transient var mutex = new Object package new { } @@ -76,6 +79,21 @@ final class Collections3 { 0 } + @SuppressWarnings("unused_private_member") + private def writeObject(stream : ObjectOutputStream) { + synchronized (this.mutex) { + stream.defaultWriteObject + } + } + + @SuppressWarnings("unused_private_member") + private def readObject(stream : ObjectInputStream) { + synchronized (this.mutex) { + stream.defaultReadObject + this.mutex = new Object + } + } + } /** Copied from Collections. @@ -94,7 +112,7 @@ final class Collections3 { /** Object on which to synchronize. */ - package val mutex : Object + package transient var mutex : Object new (collection : Collection, mutex : Object) { this.collection = collection @@ -206,6 +224,14 @@ final class Collections3 { } } + @SuppressWarnings("unused_private_member") + private def readObject(stream : ObjectInputStream) { + synchronized (this.mutex) { + stream.defaultReadObject + this.mutex = new Object + } + } + override mutex : Object { this.mutex } @@ -565,6 +591,7 @@ final class Collections3 { static def unmodifiableSynchronizedIterable(elements : Iterable, mutex : Object) : SynchronizedIterable with T { new UnmodifiableSynchronizedIterableWrapper(elements, mutex) } + /** Unmodifiable synchronized iterable. * * @param the type of the elements in the collection. @@ -577,7 +604,7 @@ final class Collections3 { val iterable : Iterable - val mutex : Object + transient var mutex : Object package new (iterable : Iterable, mutex : Object) { this.iterable = iterable @@ -592,5 +619,84 @@ final class Collections3 { return this.mutex } + @SuppressWarnings("unused_private_member") + private def writeObject(stream : ObjectOutputStream) { + synchronized (this.mutex) { + stream.defaultWriteObject + } + } + + @SuppressWarnings("unused_private_member") + private def readObject(stream : ObjectInputStream) { + synchronized (this.mutex) { + stream.defaultReadObject + this.mutex = new Object + } + } + } + + /** Replies a serializable collection. + * If the wrapped collection is serializable, it is replied itself without wrapping. + * If the wrapped collection is not serializable, it is wrapped into a collection + * that supports serialization of the collection elements. + * + * @param - type of the elements in the collection. + * @param elements the set to transform as unmodifiable collection. + * @return an unmodifiable synchronized set. + * @since 0.8.6 + */ + static def serializableCollection(elements : Collection) : Collection with T { + if (elements instanceof Serializable) { + return elements + } + return new SerializableCollectionWrapper(elements) + } + + /** Unmodifiable synchronized iterable. + * + * @param the type of the elements in the collection. + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + * @since 0.8.6 + */ + private static class SerializableCollectionWrapper extends AbstractCollection implements Serializable { + + transient var collection : Collection + + package new (collection : Collection) { + this.collection = collection + } + + override iterator : Iterator { + this.collection.iterator + } + + override size : int { + this.collection.size + } + + @SuppressWarnings("unused_private_member") + private def writeObject(stream : ObjectOutputStream) { + stream.defaultWriteObject + stream.writeInt(collection.size); + for (element : this.collection) { + stream.writeObject(element); + } + } + + @SuppressWarnings("unused_private_member") + private def readObject(stream : ObjectInputStream) { + stream.defaultReadObject + val s = stream.readInt + this.collection = new ArrayList(s) + for (var i = 0; i < s; i++) { + this.collection.add(stream.readObject as E) + } + } + + } + } diff --git a/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/network/JavaBinaryEventSerializer.java b/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/network/JavaBinaryEventSerializer.java index a4e7dd48d0..337d8871f1 100644 --- a/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/network/JavaBinaryEventSerializer.java +++ b/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/network/JavaBinaryEventSerializer.java @@ -62,6 +62,8 @@ */ public class JavaBinaryEventSerializer extends AbstractEventSerializer { + private static final String SPACE_SPECIFICATION_HEADER = "x-java-spacespec-class"; //$NON-NLS-1$ + /** * Constructs an GsonEventSerializer. The {@link EventEncrypter} is injected. * @@ -84,13 +86,18 @@ public EventEnvelope serialize(EventDispatch dispatch) throws Exception { final Map headers = dispatch.getCustomHeaders(); assert headers != null; - headers.put("x-java-spacespec-class", //$NON-NLS-1$ + headers.put(SPACE_SPECIFICATION_HEADER, spaceID.getSpaceSpecification().getName()); final Scope scope = dispatch.getScope(); - final EventEnvelope envelope = new EventEnvelope(NetworkUtil.toByteArray(spaceID.getContextID()), - NetworkUtil.toByteArray(spaceID.getID()), toBytes(scope), toBytes(dispatch.getCustomHeaders()), toBytes(event)); + final byte[] serializedContextID = NetworkUtil.toByteArray(spaceID.getContextID()); + final byte[] serializedSpaceID = NetworkUtil.toByteArray(spaceID.getID()); + final byte[] serializedScope = toBytes(scope); + final byte[] serializedHeaders = toBytes(dispatch.getCustomHeaders()); + final byte[] serializedEvent = toBytes(event); + final EventEnvelope envelope = new EventEnvelope( + serializedContextID, serializedSpaceID, serializedScope, serializedHeaders, serializedEvent); this.encrypter.encrypt(envelope); @@ -117,7 +124,7 @@ public EventDispatch deserialize(EventEnvelope envelope) throws Exception { assert headers != null; Class spaceSpec = null; - final String classname = headers.get("x-java-spacespec-class"); //$NON-NLS-1$ + final String classname = headers.get(SPACE_SPECIFICATION_HEADER); if (classname != null) { try { spaceSpec = Class.forName(classname); diff --git a/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/spawn/StandardSpawnService.java b/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/spawn/StandardSpawnService.java index 63b7399b65..a34003bea4 100644 --- a/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/spawn/StandardSpawnService.java +++ b/sre/io.janusproject/io.janusproject.plugin/src/io/janusproject/kernel/services/jdk/spawn/StandardSpawnService.java @@ -223,7 +223,8 @@ protected void fireAgentSpawnedOutsideAgent(UUID spawningAgent, AgentContext con final UUID spawner = spawningAgent == null ? context.getID() : spawningAgent; final Address source = new Address(defSpace.getSpaceID(), spawner); assert source != null; - final Collection spawnedAgentIds = Collections2.transform(agents, it -> it.getID()); + final Collection spawnedAgentIds = Collections3.serializableCollection( + Collections2.transform(agents, it -> it.getID())); final AgentSpawned event = new AgentSpawned(source, agentClazz.getName(), spawnedAgentIds); final Scope
scope = address -> { final UUID receiver = address.getUUID();