/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * 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 io.janusproject.kernel.services.jdk.network; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.StreamCorruptedException; import java.lang.reflect.Proxy; import java.text.MessageFormat; import java.util.Map; import java.util.UUID; import com.google.inject.Inject; import io.janusproject.services.network.AbstractEventSerializer; import io.janusproject.services.network.EventDispatch; import io.janusproject.services.network.EventEncrypter; import io.janusproject.services.network.EventEnvelope; import io.janusproject.services.network.NetworkUtil; import io.janusproject.util.ClassFinder; import io.sarl.lang.core.Event; import io.sarl.lang.core.Scope; import io.sarl.lang.core.SpaceID; import io.sarl.lang.core.SpaceSpecification; /** * Serialize the {@link EventDispatch} content using the Java serialization mechanism to generate the corresponding * {@link EventEnvelope}. * * <p>This implementation assumes that an {@link EventEncrypter} is injected. * * @author $Author: sgalland$ * @author $Author: ngaud$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public class JavaBinaryEventSerializer extends AbstractEventSerializer { /** * Constructs an GsonEventSerializer. The {@link EventEncrypter} is injected. * * @param encrypter - the object that will permits to encrypt the events. */ @Inject public JavaBinaryEventSerializer(EventEncrypter encrypter) { super(encrypter); } @Override public EventEnvelope serialize(EventDispatch dispatch) throws Exception { assert this.encrypter != null : "Invalid injection of the encrypter"; //$NON-NLS-1$ assert dispatch != null : "Parameter 'dispatch' must not be null"; //$NON-NLS-1$ final Event event = dispatch.getEvent(); assert event != null; final SpaceID spaceID = dispatch.getSpaceID(); assert spaceID != null; assert spaceID.getSpaceSpecification() != null; final Map<String, String> headers = dispatch.getCustomHeaders(); assert headers != null; headers.put("x-java-spacespec-class", //$NON-NLS-1$ 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)); this.encrypter.encrypt(envelope); return envelope; } private static byte[] toBytes(Object object) throws IOException { try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { final ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); return baos.toByteArray(); } } @SuppressWarnings("unchecked") @Override public EventDispatch deserialize(EventEnvelope envelope) throws Exception { assert this.encrypter != null : "Invalid injection of the encrypter"; //$NON-NLS-1$ assert envelope != null : "Parameter 'envelope' must not be null"; //$NON-NLS-1$ this.encrypter.decrypt(envelope); final Map<String, String> headers = fromBytes(envelope.getCustomHeaders(), Map.class); assert headers != null; Class<?> spaceSpec = null; final String classname = headers.get("x-java-spacespec-class"); //$NON-NLS-1$ if (classname != null) { try { spaceSpec = Class.forName(classname); } catch (Throwable exception) { // } } if (spaceSpec == null || !SpaceSpecification.class.isAssignableFrom(spaceSpec)) { throw new ClassCastException(MessageFormat.format(Messages.JavaBinaryEventSerializer_0, spaceSpec)); } final UUID contextId = NetworkUtil.fromByteArray(envelope.getContextId()); final UUID spaceId = NetworkUtil.fromByteArray(envelope.getSpaceId()); final SpaceID spaceID = new SpaceID(contextId, spaceId, (Class<? extends SpaceSpecification<?>>) spaceSpec); final Event event = fromBytes(envelope.getBody(), Event.class); assert event != null; final Scope<?> scope = fromBytes(envelope.getScope(), Scope.class); return new EventDispatch(spaceID, event, scope, headers); } private static <T> T fromBytes(byte[] data, Class<T> type) throws IOException, ClassNotFoundException { try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) { try (ObjectInputStream oos = new ClassLoaderObjectInputStream(bais)) { final Object object = oos.readObject(); if (object != null && type.isInstance(object)) { return type.cast(object); } throw new ClassCastException(MessageFormat.format(Messages.JavaBinaryEventSerializer_0, type.getName())); } } } /** Object input stream that is aware of the Bundle class loaders. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class ClassLoaderObjectInputStream extends ObjectInputStream { /** * Constructor. * * @param inputStream the InputStream to work on * @throws IOException in case of an I/O error * @throws StreamCorruptedException if the stream is corrupted */ public ClassLoaderObjectInputStream(InputStream inputStream) throws IOException, StreamCorruptedException { super(inputStream); } /** * Resolve a class specified by the descriptor using the * specified ClassLoader or the super ClassLoader. * * @param objectStreamClass descriptor of the class * @return the Class object described by the ObjectStreamClass * @throws IOException in case of an I/O error * @throws ClassNotFoundException if the Class cannot be found */ @Override protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException { final Class<?> type = ClassFinder.findClass(objectStreamClass.getName()); if (type != null) { return type; } // classloader knows not of class, let the super classloader do it return super.resolveClass(objectStreamClass); } /** * Create a proxy class that implements the specified interfaces using * the specified ClassLoader or the super ClassLoader. * * @param interfaces the interfaces to implement * @return a proxy class implementing the interfaces * @throws IOException in case of an I/O error * @throws ClassNotFoundException if the Class cannot be found */ @Override protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { final Class<?>[] interfaceClasses = new Class[interfaces.length]; ClassLoader cl = null; for (int i = 0; i < interfaces.length; i++) { final Class<?> type = ClassFinder.findClass(interfaces[i]); if (type != null) { interfaceClasses[i] = type; cl = type.getClassLoader(); } else { throw new ClassNotFoundException(interfaces[i]); } } try { return Proxy.getProxyClass(cl, interfaceClasses); } catch (IllegalArgumentException e) { return super.resolveProxyClass(interfaces); } } } }