JavaClassRpcCollector.java

package eu.javaexperience.rpc;

import static eu.javaexperience.log.LogLevel.TRACE;
import static eu.javaexperience.log.LogLevel.WARNING;
import static eu.javaexperience.log.LoggingTools.tryLogFormat;
import static eu.javaexperience.log.LoggingTools.tryLogSimple;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

import eu.javaexperience.asserts.AssertArgument;
import eu.javaexperience.database.annotations.Ignore;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.rpc.function.JavaFunctionRpcWrapper;
import eu.javaexperience.rpc.function.RpcFunctionParameter;

public class JavaClassRpcCollector<REQ extends RpcRequest> implements RpcFacility<REQ>
{	
	protected static final Logger DEFAULT_LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("RpcFunctionStorage"));
	
	public static JavaClassRpcFunctions[] emptyJavaClassRpcFunctionsArray = new JavaClassRpcFunctions[0];
	
	public static JavaClassRpcCollector[] emptyJavaClassRpcCollector = new JavaClassRpcCollector[0];
	
	public static RpcFacility[] emptyRpcFacilityArray = new RpcFacility[0];
	
	protected Logger LOG = DEFAULT_LOG;
	
	protected Class<?> requestClass;
	
	protected Map<String, JavaFunctionRpcWrapper<REQ>> METHODS;
	
	protected Object javaMethodThisParam;
	
	protected String rpcName;
	
	/*public Class<?> getWrappedClass()
	{
		return requestClass;
	}*/
	
	public JavaClassRpcCollector(Class<?> toWrap)
	{
 		requestClass = toWrap;
	}
	
	protected JavaClassRpcCollector()
	{
 		requestClass = getClass();
	}
	
	protected void initalize()
	{
		Map<String, JavaFunctionRpcWrapper<REQ>> methods = new HashMap<>();
		Method[] ms = requestClass.getMethods();
		for(Method m:ms)
		{
			try
			{
				if(mayRegister(m))
				{	
					JavaFunctionRpcWrapper func = wrapFunction(m);
					methods.put(func.getMethodName(), func);
				}
			}
			catch(Exception e)
			{
				tryLogFormat(LOG, WARNING, "Method: %s", m);
				tryLogSimple(LOG, WARNING, e);
			}
		}
		
		METHODS = Collections.unmodifiableMap(methods);
	}
	
	protected JavaFunctionRpcWrapper wrapFunction(Method m)
	{
		return JavaFunctionRpcWrapper.wrapJavaFunction(m);
	}

	protected void onException(Throwable t)
	{
		t.printStackTrace();
	}
	
	public Collection<JavaFunctionRpcWrapper<REQ>> getWrappedMethods()
	{
		return METHODS.values();
	}
		
	public Collection<JavaFunctionRpcWrapper<REQ>> getWrappedFunctions()
	{
		return METHODS.values();
	}
	
	public void fillFunctionList(Collection<RpcFunction<REQ, RpcFunctionParameter>> fill)
	{
		for(Entry<String, JavaFunctionRpcWrapper<REQ>> kv:METHODS.entrySet())
		{
			fill.add(kv.getValue());
		}
	}
	
	public Map<String, JavaFunctionRpcWrapper<REQ>> getWrappedMethodsWithName()
	{
		return METHODS;
	}
	
	public List<RpcFunction<REQ, RpcFunctionParameter>> getFunctionList()
	{
		ArrayList<RpcFunction<REQ, RpcFunctionParameter>> ret = new ArrayList<>();
		fillFunctionList(ret);
		return ret;
	}
	
	
	public void setLogger(Logger logger)
	{
		AssertArgument.assertNotNull(logger, "logger");
		this.LOG = logger;
	}
	
	public static boolean mayRegisterMethod(Method m, Class<?> rpcThisClass)
	{
		int mod = m.getModifiers();
		if(Modifier.isPublic(mod) && Modifier.isStatic(mod))
		{
			if(null == m.getAnnotation(Ignore.class))
			{
				Class[] clss = m.getParameterTypes();
				if(clss.length > 0)
				{
					if(rpcThisClass.isAssignableFrom(clss[0]))
					{
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * You may reject the function invocation by throwing an exception
	 * that will be shown (relayed) on the caller end.
	 * */
	protected void beforeCall(REQ ctx, JavaFunctionRpcWrapper<REQ> m) throws Throwable
	{
	}
	
	protected boolean mayRegister(Method m)
	{
		return mayRegisterMethod(m, RpcRequest.class);
	}
	
	protected DataObject onMethodNotFound(REQ ctx, String name)
	{
		throw new RuntimeException("Method not found: "+name);
	}
	
	protected static final AtomicInteger REQUEST_ID = new AtomicInteger(0);
	
	public DataObject dispatch(REQ ctx)
	{
		RpcProtocolHandler protocol = ctx.getProtocolHandler();
		
		int req_id = -1;
		if(LOG.mayLog(LogLevel.TRACE))
		{
			req_id = REQUEST_ID.incrementAndGet();
			LOG.logFormat(TRACE, "%d Request: %s", req_id, ctx.getRequestData().getImpl().toString());
		}
		
		try
		{
			String name = ctx.getProtocolHandler().getRequestFunctionName(ctx);
			JavaFunctionRpcWrapper<REQ> func = METHODS.get(name);
			
			if(null != func)
			{
				beforeCall(ctx, func);
				Object ret = RpcTools.callFunction(ctx, javaMethodThisParam, func);
				if(ret instanceof DataObject)
				{
					return (DataObject) ret;
				}
				
				return RpcTools.wrapReturningValue(ctx, ret);
			}
			
			return onMethodNotFound(ctx, name);
		}
		catch(Throwable t)
		{
			onException(t);
			DataObject ret = protocol.createException(ctx, t);
			
			if(-1 != req_id && LOG.mayLog(LogLevel.TRACE))
			{
				LOG.logFormat(TRACE, "%d Exception response: %s", req_id, ret.getImpl().toString());
			}
			
			return ret;
		}
	}

	@Override
	public DataObject getBy(REQ a)
	{
		return dispatch(a);
	}

	public String getRpcName()
	{
		if(null != rpcName)
		{
			return rpcName;
		}
		return requestClass.getSimpleName();
	}
	
	public Class getWrappedClass()
	{
		return requestClass;
	}
}