SaacServerUnit.java

package eu.javaexperience.saac;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletOutputStream;

import org.json.JSONObject;

import eu.javaexperience.collection.iterator.IteratorTools;
import eu.javaexperience.collection.map.NullMap;
import eu.javaexperience.collection.map.OneShotMap;
import eu.javaexperience.datareprez.DataArray;
import eu.javaexperience.datareprez.DataCommon;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.datareprez.DataSender;
import eu.javaexperience.datareprez.convertFrom.DataWrapper;
import eu.javaexperience.datareprez.jsonImpl.DataObjectJsonImpl;
import eu.javaexperience.dispatch.Dispatcher;
import eu.javaexperience.functional.saac.Functions;
import eu.javaexperience.functional.saac.Functions.PreparedFunction;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.patterns.behavioral.cor.CorDispatcher;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.reflect.Mirror.BelongTo;
import eu.javaexperience.reflect.Mirror.FieldSelector;
import eu.javaexperience.reflect.Mirror.Select;
import eu.javaexperience.reflect.Mirror.Visibility;
import eu.javaexperience.rpc.RpcFacility;
import eu.javaexperience.rpc.RpcFunction;
import eu.javaexperience.rpc.RpcRequest;
import eu.javaexperience.rpc.SimpleRpcRequest;
import eu.javaexperience.rpc.SimpleRpcSession;
import eu.javaexperience.rpc.bidirectional.BidirectionalRpcDefaultProtocol;
import eu.javaexperience.rpc.bidirectional.BidirectionalRpcProtocolHandler;
import eu.javaexperience.rpc.codegen.JavascriptRpcSourceGenerator;
import eu.javaexperience.rpc.function.RpcFunctionParameter;
import eu.javaexperience.rpc.http.RpcHttpTools;
import eu.javaexperience.semantic.references.MayNull;
import eu.javaexperience.text.StringFunctions;
import eu.javaexperience.url.UrlPart;
import eu.javaexperience.web.Context;
import eu.javaexperience.web.dispatch.WebDispatchTools;
import eu.javaexperience.web.dispatch.url.UrlNodePatternTools;
import eu.javaexperience.web.features.WebSocket;
import eu.javaexperience.web.features.WebSocketEndpoint;

public class SaacServerUnit
{
	public static final DataWrapper reflectTypeDataWrapper = new DataWrapper()
	{
		@Override
		public DataCommon wrap
		(
			DataWrapper topWrapper,
			DataCommon prototype,
			Object o
		)
		{
			/*if(o instanceof AnnotatedType)
			{
				DataObject ret = prototype.newObjectInstance();
				AnnotatedType at = (AnnotatedType)o;
				
				return ret;
			}*/
			
			if(o instanceof Type)
			{
				DataObject ret = prototype.newObjectInstance();
				ret.putString("toString", o.toString());
				
				if(o instanceof Class)
				{
					ret.putString("type", "class");
					ret.putString("class", ((Class)o).getSimpleName());
				}
				else if(o instanceof GenericArrayType)
				{
					GenericArrayType gat = (GenericArrayType) o;
					ret.putString("type", "genericArray");
					ret.putObject("genericComponentType", (DataObject) topWrapper.wrap(topWrapper, prototype, gat.getGenericComponentType()));
				}
				else if(o instanceof ParameterizedType)
				{
					ParameterizedType pt = (ParameterizedType) o;
					ret.putString("type", "parameterized");
					ret.putArray("actualTypeArguments", (DataArray) topWrapper.wrap(topWrapper, prototype, pt.getActualTypeArguments()));
					ret.putObject("rawType", (DataObject) wrap(topWrapper, prototype, pt.getRawType()));
				}
				else if(o instanceof TypeVariable)
				{
					TypeVariable tv = (TypeVariable) o;
					ret.putString("type", "typeVariable");
					//ret.putObject("genericDeclaration", (DataObject) topWrapper.wrap(topWrapper, prototype, tv.getGenericDeclaration()));
					//ret.putArray("annotatedBounds", (DataArray) topWrapper.wrap(topWrapper, prototype, tv.getAnnotatedBounds()));
					ret.putArray("bounds", (DataArray) topWrapper.wrap(topWrapper, prototype, tv.getBounds()));
					ret.putString("name", tv.getName());
				}
				else if(o instanceof WildcardType)
				{
					WildcardType wt = (WildcardType) o;
					ret.putString("type", "wildcard");
					ret.putArray("lowerBounds" , (DataArray) topWrapper.wrap(topWrapper, prototype, wt.getLowerBounds()));
					ret.putArray("upperBounds" , (DataArray) topWrapper.wrap(topWrapper, prototype, wt.getUpperBounds()));
				}
				
				return ret;
			}
			return null;
		}
	};  
	
	public static final BidirectionalRpcProtocolHandler<SimpleRpcSession> DEFAULT_PROTOCOL = new BidirectionalRpcDefaultProtocol<SimpleRpcSession>
	(
		new DataObjectJsonImpl(),
		DataReprezTools.combineWrappers
		(
			reflectTypeDataWrapper,
			DataReprezTools.WRAP_ARRAY_COLLECTION_MAP,
			DataReprezTools.WRAP_DATA_LIKE,
			DataReprezTools.createClassInstanceWrapper
			(
				new FieldSelector
				(
					true,
					Visibility.Public,
					BelongTo.Instance,
					Select.All,
					Select.IsNot,
					Select.All
				)
			)
		)
	);
	
	public static SaacServerUnit create
	(
		String apiName,
		Iterable<Class<?>> clss
	)
	{
		ArrayList<PreparedFunction> funcs = new ArrayList<>();
		
		for(Class<?> cls:clss)
		{
			Functions.collectFunctions(funcs, cls);
		}
		
		HashMap<String, PreparedFunction> pf = new HashMap<>();
		
		for(PreparedFunction f:funcs)
		{
			pf.put(f.getId(), f);
		}
		
		return new SaacServerUnit(apiName, pf);
	}
	
	public static SaacServerUnit create
	(
		String apiName,
		Class<?>... clss
	)
	{
		return create(apiName, IteratorTools.asList(clss));
	}

	protected final String apiName;
	protected final Map<String, PreparedFunction> saacFunctions;
	
	protected final BidirectionalRpcProtocolHandler<SimpleRpcSession> protocol;
	
	public SaacServerUnit(String apiName, Map<String, PreparedFunction> funcs)
	{
		this.protocol = DEFAULT_PROTOCOL;
		this.apiName = apiName;
		this.saacFunctions = Collections.unmodifiableMap(new HashMap<String, PreparedFunction>(funcs));
	}
	
	public Map<String, PreparedFunction> getFunctions()
	{
		return saacFunctions;
	}
	
	public String getJsApiCall(boolean async)
	{
		return JavascriptRpcSourceGenerator.BASIC_JAVASCRIPT_SOURCE_BUILDER.buildRpcClientSource
		(
			"SaacApi",
			(Collection<RpcFunction<RpcRequest, RpcFunctionParameter>>) (Object) SaacRpc.DISPATCH.getWrappedMethods(),
			async?new OneShotMap<>("using_callback_return", "true"):NullMap.instance
		);
	}
	
	public SimpleRpcSession sessionStart(@MayNull WebSocket ws)
	{
		final SimpleRpcSession session = new SimpleRpcSession(protocol);
		SaacRpc.setSessionFunctionSet(session, saacFunctions);
		if(null != ws)
		{
			session.put("SEND", ws);
		}
		session.put("PROTOCOL", protocol);
		return session;
	}
	
	public void sessionStart(SimpleRpcSession session, @MayNull WebSocket ws)
	{
		SaacRpc.setSessionFunctionSet(session, saacFunctions);
		if(null != ws)
		{
			session.put("SEND", ws);
		}
		session.put("PROTOCOL", protocol);
	}
	
	public void serveRestCall(Context ctx) throws IOException
	{
		RpcHttpTools.serveRpcAjaxRequest(ctx, RpcHttpTools.WRAP_SIMPLE_RPC_REQUEST, GET_SESSION, protocol, SaacRpc.DISPATCH);
	}
	
	public final GetBy1<SimpleRpcSession, Context> GET_SESSION = new GetBy1<SimpleRpcSession, Context>()
	{
		@Override
		public SimpleRpcSession getBy(Context a)
		{
			return sessionStart(null);
		}
	};
	
	public void handleWebSocket(Context ctx) throws NoSuchAlgorithmException, IOException
	{
		//HttpRequest h = (HttpRequest) ctx.getRequest();
		WebSocket ws = WebSocketEndpoint.upgradeRequest(ctx);//h.getQuery().upgradeWebsocket();
		SimpleRpcSession session = sessionStart(ws);
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataSender ds = protocol.getDefaultCommunicationProtocolPrototype().newDataSender(baos);
		
		
		while(true)
		{
			String get = null;
			try
			{
				baos.reset();
				get = null;
				byte[] rec = ws.receive();
				get = new String(rec);
				DataObject rpcReq = new DataObjectJsonImpl(new JSONObject(get));
				ds.send(SaacRpc.DISPATCH.dispatch(new SimpleRpcRequest(session, rpcReq)));
			}
			catch(Exception e)
			{
				//System.out.println(get);
				//throw e;
				Mirror.propagateAnyway(e);
				//ds.send(RpcTools.wrapException(new SimpleRpcRequest(session), e));
			}
			finally
			{
				ws.send(baos.toByteArray());
			}
		}
	}
	
	public Dispatcher<Context> generateApiAjaxDispatcher()
	{
		return new Dispatcher<Context>()
		{
			@Override
			public boolean dispatch(Context ctx)
			{
				try
				{
					serveRestCall(ctx);
				}
				catch (Exception e)
				{
					Mirror.propagateAnyway(e);
				}
				ctx.finishOperation();
				return true;
			}
		};
	}
	
	public Dispatcher<Context> generateApiWebsocketDispatcher()
	{
		return new Dispatcher<Context>()
		{

			@Override
			public boolean dispatch(Context ctx)
			{
				try
				{
					handleWebSocket(ctx);
				}
				catch (Exception e)
				{
					Mirror.propagateAnyway(e);
				}
				ctx.finishOperation();
				return false;
			}
		};
	}

	public void registerApiDispacher(CorDispatcher<Context> dds, final String chain, final  String restPath, final  String websocketPath)
	{
		dds.getChainByName(chain).addLink
		(
			WebDispatchTools.exactPath
			(
				UrlNodePatternTools.assemble(UrlPart.PATH, StringFunctions.isEquals(restPath)),
				generateApiAjaxDispatcher()
			)
		);
		
		dds.getChainByName(chain).addLink
		(
			WebDispatchTools.exactPath
			(
				UrlNodePatternTools.assemble(UrlPart.PATH, StringFunctions.isEquals(websocketPath)),
				generateApiWebsocketDispatcher()
			)
		);
	}

	public static void registerSaveDispatch
	(
		CorDispatcher<Context> dispatcher,
		String chain,
		File rootDir,
		final String getPath,
		final String savePath
	)
		throws IOException
	{

		final Dispatcher<Context> SAVE_RESTORE = SaacWebTools.createSaveRestoreDispatcher(rootDir);
		
		dispatcher.getChainByName(chain).addLink
		(
			WebDispatchTools.createWith(new Dispatcher<Context>()
			{
				@Override
				public boolean dispatch(Context ctx)
				{
					if(getPath.equals(ctx.getRequestUrl().getPath()))
					{
						SAVE_RESTORE.dispatch(ctx);
					}
					
					if(savePath.equals(ctx.getRequestUrl().getPath()))
					{
						SAVE_RESTORE.dispatch(ctx);
						
					}
					
					return false;
				}
			})
		);
	}

	public Dispatcher<Context> generateSourceApiDispatcher()
	{
		final byte[] RPC_SORUCE = getJsApiCall(true).getBytes();
		return new Dispatcher<Context>()
		{
			@Override
			public boolean dispatch(Context ctx)
			{
				try
				{
					ctx.getResponse().setContentType("text/javascript");
					ServletOutputStream os = ctx.getResponse().getOutputStream();
					os.write(RPC_SORUCE);
					os.flush();
				}
				catch(Exception e)
				{
					Mirror.propagateAnyway(e);
				}
				
				ctx.finishOperation();
				return true;
			}
		};
	}
	
	public void registerApiSourceJs(CorDispatcher<Context> dispatcher, String chain, String path)
	{
		dispatcher.getChainByName(chain).addLink
		(
			WebDispatchTools.exactPath
			(
				UrlNodePatternTools.assemble(UrlPart.PATH, StringFunctions.isEquals(path)),
				generateSourceApiDispatcher()
			)
		);
		
	}

	public void registerFullstack(CorDispatcher<Context> dispatcher, String chain, String apiSourcePath, String restPath, String websocketPath)
	{
		registerApiSourceJs(dispatcher, "static", apiSourcePath);
		registerApiDispacher(dispatcher, "static", restPath, websocketPath);
	}

	public RpcFacility<SimpleRpcRequest> getApi()
	{
		return new RpcFacility<SimpleRpcRequest>()
		{
			@Override
			public DataObject getBy(SimpleRpcRequest a)
			{
				return dispatch(a);
			}

			@Override
			public String getRpcName()
			{
				return apiName;
			}

			@Override
			public DataObject dispatch(SimpleRpcRequest req)
			{
				((SimpleRpcSession)req.getRpcSession()).setProtocolHandler(DEFAULT_PROTOCOL);
				sessionStart((SimpleRpcSession) req.getRpcSession(), null);
				return SaacRpc.DISPATCH.dispatch(req);
			}

			@Override
			public Collection<? extends RpcFunction<SimpleRpcRequest, ?>> getWrappedFunctions()
			{
				return SaacRpc.DISPATCH.getWrappedFunctions();
			}
		};
	}
}