All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.paremus.dosgi.net.pushstream.PushStreamFactory Maven / Gradle / Ivy

Go to download

An implementation of OSGi Remote Service Admin, enhanced to provide support for OSGi Promises and streaming using OSGi PushStreams.

The newest version!
/*-
 * #%L
 * com.paremus.dosgi.net
 * %%
 * Copyright (C) 2016 - 2019 Paremus Ltd
 * %%
 * Licensed under the Fair Source License, Version 0.9 (the "License");
 * 
 * See the NOTICE.txt file distributed with this work for additional 
 * information regarding copyright ownership. You may not use this file 
 * except in compliance with the License. For usage restrictions see the 
 * LICENSE.txt file distributed with this work
 * #L%
 */
package com.paremus.dosgi.net.pushstream;

import static java.util.Arrays.stream;
import static java.util.Collections.singletonMap;
import static java.util.stream.Stream.concat;
import static java.util.stream.Stream.of;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToLongFunction;

import org.osgi.framework.ServiceException;

import com.paremus.dosgi.net.message.AbstractRSAMessage.CacheKey;
import com.paremus.dosgi.net.promise.PromiseFactory;

import io.netty.util.Timer;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;

public class PushStreamFactory {

	private static final String PUSH_STREAM_TYPE = "org.osgi.util.pushstream.PushStream";

	private static final String PUSH_STREAM_PROVIDER_TYPE = "org.osgi.util.pushstream.PushStreamProvider";

	private static final String PUSH_STREAM_BUILDER_TYPE = "org.osgi.util.pushstream.PushStreamBuilder";

	private static final String PUSH_EVENT_SOURCE_TYPE = "org.osgi.util.pushstream.PushEventSource";
	
	public static interface OnConnect {
	    void connect(CacheKey key, EventExecutor worker, Future closeFuture, ToLongFunction pushData, Consumer pushClose);
	}
	
	public static interface DataStreamFactory {
		DataStream apply(ToLongFunction onData, Consumer onTerminal,
				Object connectTo);
	}
	
	public static interface DataStream {
		void open();
		void asyncBackPressure(long bp);
		void close();
		Future closeFuture();
	}
	
	public static Function,Object> pushStreamHandler(Class pushStream, 
			EventExecutorGroup executor, OnConnect onConnect, 
			Consumer onClose) throws Exception {
		
		Class providerClass = pushStream.getClassLoader()
				.loadClass(PUSH_STREAM_PROVIDER_TYPE);
		Class pushEventSourceClass = pushStream.getClassLoader()
				.loadClass(PUSH_EVENT_SOURCE_TYPE);
		Class builderClass = pushStream.getClassLoader()
				.loadClass(PUSH_STREAM_BUILDER_TYPE);
		
		Object provider = providerClass.getConstructor().newInstance();
		
		Method buildStream = providerClass.getMethod("buildStream", pushEventSourceClass);
		Method withExecutor = builderClass.getMethod("withExecutor", Executor.class);
		Method withScheduler = builderClass.getMethod("withScheduler", ScheduledExecutorService.class);
		Method unbuffered = builderClass.getMethod("unbuffered");
		Method build = builderClass.getMethod("build");
		
		BiFunction eventSource = createPushEventSource(
				pushEventSourceClass, onConnect, onClose);
		
		return f -> {
			CacheKey id;
			try {
				Object[] value = (Object[]) f.sync().getNow();
				id = new CacheKey((UUID) value[0], (Integer) value[1]);
			} catch (InterruptedException ie) {
				Thread.currentThread().interrupt();
				throw new ServiceException("Interrupted while waiting for a remote service response", 
						ServiceException.REMOTE, ie);
			}
			
			try {
				EventExecutor e = executor.next();
				Object builder = buildStream.invoke(provider, eventSource.apply(id, e));
				builder = withExecutor.invoke(builder, e);
				builder = withScheduler.invoke(builder, e);
				builder = unbuffered.invoke(builder);
				return build.invoke(builder);
			} catch (InvocationTargetException ite) {
				throw new ServiceException("A problem occurred building a PushStream", 
						ServiceException.REMOTE, ite.getCause());
			} catch (IllegalAccessException iae) {
				throw new ServiceException("A problem occurred building a PushStream", 
						ServiceException.REMOTE, iae);
			}
		};
		
	}

	public static Function,Object> pushEventSourceHandler(Class pushEventSource, 
			EventExecutorGroup executor, OnConnect onConnect, 
			Consumer onClose) throws Exception {
		
		BiFunction eventSource = createPushEventSource(
				pushEventSource, onConnect, onClose);
		
		return f -> {
			CacheKey id;
			try {
				Object[] value = (Object[]) f.sync().getNow();
				id = new CacheKey((UUID) value[0], (Integer) value[1]);
			} catch (InterruptedException ie) {
				Thread.currentThread().interrupt();
				throw new ServiceException("Interrupted while waiting for a remote service response", 
						ServiceException.REMOTE, ie);
			}
			EventExecutor e = executor.next();
			return eventSource.apply(id, e);
		};
		
	}

	private static BiFunction createPushEventSource(Class pushEventSourceClass,
			OnConnect onConnect, Consumer onClose) throws Exception {
		return new CustomPushEventSourceFactory(pushEventSourceClass, onConnect, onClose);
	}
	
	private static class CustomPushEventSourceFactory implements BiFunction {
		
		private static final String CUSTOM_IMPL_NAME = "com.paremus.dosgi.net.pushstream.PushEventSourceImpl";
		private static final String CUSTOM_RESOURCE_NAME = "com/paremus/dosgi/net/pushstream/PushEventSourceImpl.class";
		
		private final OnConnect onConnect;
		private final Consumer onClose;
		
		private final Constructor pesConstructor;
		
		public CustomPushEventSourceFactory(Class pushEventSourceClass, OnConnect onConnect, 
				Consumer onClose) throws Exception {
			this.onConnect = onConnect;
			this.onClose = onClose;
			
			ClassLoader customImplLoader = getCustomImplLoader(pushEventSourceClass,
					singletonMap(CUSTOM_IMPL_NAME, CUSTOM_RESOURCE_NAME), "io.netty.util.concurrent",
					"com.paremus.dosgi.net.message.AbstractRSAMessage$CacheKey",
					"com.paremus.dosgi.net.pushstream.PushStreamFactory$OnConnect");
			
			pesConstructor = customImplLoader.loadClass(CUSTOM_IMPL_NAME)
				.getConstructor(CacheKey.class, OnConnect.class, Consumer.class, EventExecutor.class);
			pesConstructor.setAccessible(true);
		}
	
		@Override
		public Object apply(CacheKey key, EventExecutor executor) {
			try {
				return pesConstructor.newInstance(key, onConnect, onClose, executor);
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e.getTargetException());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}

	private static ClassLoader getCustomImplLoader(Class pushEventSourceClass, 
			Map customImplToCustomResource, String safePackage, String... safeClasses) {
		
		ClassLoader factoryLoader = PushStreamFactory.class.getClassLoader();
		
		ClassLoader customImplLoader = new ClassLoader(pushEventSourceClass.getClassLoader()) {
	
			@Override
			protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
				if(name.startsWith(safePackage) || Arrays.stream(safeClasses)
						.anyMatch(s -> name.equals(s))) {
					return factoryLoader.loadClass(name);
				}
				
				String resource = null;
				if(customImplToCustomResource.containsKey(name)) {
					resource = customImplToCustomResource.get(name);
				}
				
				if(resource != null) {
					return doLoad(name, resource);
				}
				
				return super.loadClass(name, resolve);
			}
	
			private Class doLoad(String name, String resource) throws ClassNotFoundException {
				try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
						InputStream is = factoryLoader.getResourceAsStream(resource)) {
					
					byte[] b = new byte[4096];
					int r;
					while((r = is.read(b)) > -1) {
						baos.write(b, 0, r);
					}
					
					return defineClass(name, baos.toByteArray(), 0, baos.size(), 
							PromiseFactory.class.getProtectionDomain());
				} catch (IOException ioe) {
					// TODO log this
					throw new ClassNotFoundException(name, ioe);
				}
				
			}
			
		};
		return customImplLoader;
	}

	private static final String PUSHSTREAM_CONNECTOR_CUSTOM_IMPL_NAME = 
			"com.paremus.dosgi.net.pushstream.PushStreamPushEventConsumerImpl";
	private static final String PUSHSTREAM_CONNECTOR_CUSTOM_RESOURCE_NAME = 
			"com/paremus/dosgi/net/pushstream/PushStreamPushEventConsumerImpl.class";
	
	private static final String COMMON_PARENT_CUSTOM_IMPL_NAME = "com.paremus.dosgi.net.pushstream.AbstractPushEventConsumerImpl";
	private static final String COMMON_PARENT_CUSTOM_RESOURCE_NAME = "com/paremus/dosgi/net/pushstream/AbstractPushEventConsumerImpl.class";

	public static DataStreamFactory pushStreamConnector(Class pushStream, Timer timer) throws Exception {
		Map map = new HashMap<>();
		map.put(PUSHSTREAM_CONNECTOR_CUSTOM_IMPL_NAME, PUSHSTREAM_CONNECTOR_CUSTOM_RESOURCE_NAME);
		map.put(COMMON_PARENT_CUSTOM_IMPL_NAME, COMMON_PARENT_CUSTOM_RESOURCE_NAME);
		return new StreamConsumerFactory(pushStream, map, PUSHSTREAM_CONNECTOR_CUSTOM_IMPL_NAME, timer);
	}

	private static final String PUSH_EVENT_SOURCE_CONNECTOR_CUSTOM_IMPL_NAME = 
			"com.paremus.dosgi.net.pushstream.PushEventSourcePushEventConsumerImpl";
	private static final String PUSH_EVENT_SOURCE_CONNECTOR_CUSTOM_RESOURCE_NAME = 
			"com/paremus/dosgi/net/pushstream/PushEventSourcePushEventConsumerImpl.class";
	
	public static DataStreamFactory pushEventSourceConnector(Class pushEventSource, Timer timer) throws Exception {
		Map map = new HashMap<>();
		map.put(PUSH_EVENT_SOURCE_CONNECTOR_CUSTOM_IMPL_NAME, PUSH_EVENT_SOURCE_CONNECTOR_CUSTOM_RESOURCE_NAME);
		map.put(COMMON_PARENT_CUSTOM_IMPL_NAME, COMMON_PARENT_CUSTOM_RESOURCE_NAME);
		return new StreamConsumerFactory(pushEventSource, map, PUSH_EVENT_SOURCE_CONNECTOR_CUSTOM_IMPL_NAME, timer);
	}
	
	private static class StreamConsumerFactory implements DataStreamFactory {
		
		
		private final Constructor constructor;
		private final Timer timer;
		
		public StreamConsumerFactory(Class connectorTargetType, Map resourceMappings,
				String connectorClassName, Timer timer) throws Exception {
			
			this.timer = timer;
			ClassLoader customImplLoader = getCustomImplLoader(connectorTargetType,
					resourceMappings, "io.netty.util",
					"com.paremus.dosgi.net.pushstream.PushStreamFactory$DataStream",
					ServiceException.class.getName());
			
			constructor = customImplLoader.loadClass(connectorClassName)
				.getConstructor(ToLongFunction.class, Consumer.class, connectorTargetType, Timer.class);
			constructor.setAccessible(true);
		}
	
		@Override
		public DataStream apply(ToLongFunction onData, Consumer onTerminal,
				Object toConnectTo) {
			try {
				return (DataStream) constructor.newInstance(onData, onTerminal, toConnectTo, timer);
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e.getTargetException());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}

	public static boolean isPushStream(Class returnType) {
		return locateInterface(returnType, PUSH_STREAM_TYPE) != null;
	}

	public static boolean isPushEventSource(Class returnType) {
		return locateInterface(returnType, PUSH_EVENT_SOURCE_TYPE) != null;
	}
	
	private static Class locateInterface(Class type, String toCheck) {
		Class result = null;

		do {
			result = concat(of(type), stream(type.getInterfaces())).filter(c -> toCheck.equals(c.getName()))
					.findFirst().orElse(null);
		} while (result == null && (type = type.getSuperclass()) != null);

		return result;
	}
	
}