ZabbixApiClient.java

package eu.linuxengineering.zabbix;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import eu.javaexperience.asserts.AssertArgument;
import eu.javaexperience.collection.map.SmallMap;
import eu.javaexperience.datareprez.DataArray;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.datareprez.convertFrom.ModifiableObject;
import eu.javaexperience.datareprez.jsonImpl.DataObjectJsonImpl;
import eu.javaexperience.generic.annotations.Ignore;
import eu.javaexperience.interfaces.simple.getBy.GetBy3;
import eu.javaexperience.io.IOTools;
import eu.javaexperience.pdw.BeanTools;
import eu.javaexperience.pdw.ProxyDataWrapperTools;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.rpc.bidirectional.BidirectionalRpcDefaultProtocol;
import eu.javaexperience.url.UrlTools;
import eu.linuxengineering.zabbix.annotation.ExtraParameters;
import eu.linuxengineering.zabbix.annotation.ParameterName;
import eu.linuxengineering.zabbix.api.TargetObject;
import eu.linuxengineering.zabbix.api.ZabbixApi;
import eu.linuxengineering.zabbix.api.ZabbixApiUser.ZabixUserData;
import eu.linuxengineering.zabbix.api.ZabbixGenericApiNode;
import eu.linuxengineering.zabbix.api.ZabbixParameters;
import eu.linuxengineering.zabbix.api.ZabbixRoot;

public class ZabbixApiClient implements Closeable
{
	protected String url;
	protected String sessionId;
	
	protected ZabbixRoot root;
	
	public ZabbixApiClient(String url)
	{
		this.url = url;
		this.root = ProxyDataWrapperTools.wrapAccessor(new ZabbixGenericApiNode(this, ""), ZabbixRoot.class, MAPPER);
	}
	
	public static final GetBy3<Object, ZabbixGenericApiNode, Method, Object[]> MAPPER = new GetBy3<Object, ZabbixGenericApiNode, Method, Object[]>()
	{
		@Override
		public Object getBy(ZabbixGenericApiNode path, Method b, Object[] c)
		{
			String methodName = b.getName();
			
			try
			{
				//top interface functions:
				if(BACKEND_IMPL_METHOD_NAMES.contains(methodName))
				{
					return relayApiCall(path, b, c);
				}
				
				switch (methodName)
				{
				
				//object's useful functions
				case "toString":return "Proxy generated Zabbix api node path: "+path.getApiPath();
				
				//interited from object
				case "clone":
				case "notifyAll":
				case "notify":
				case "wait":
				case "equals":
				case "hashCode":
					throw new RuntimeException("illegal operation on a proxy object: "+methodName);
				}
				
				String reqName = BeanTools.getCLikeBeanName(b);
				Class<?> retType = b.getReturnType();
				
				if(ZabbixApi.class.isAssignableFrom(retType))
				{
					return wrapWithClass((Class) retType, path.nextPath(reqName));
				}
				
				//execute RPC call
				return executeRpcCall(path, b, c);
			}
			catch(Exception e)
			{
				Mirror.propagateAnyway(e);
				return null;
			}
		}
	};
	
	public static Object executeRpcCall(ZabbixGenericApiNode path, Method b, Object[] c) throws Exception
	{
		String method = path.getApiPath()+"."+b.getName();
		DataObject to = DataObjectJsonImpl.instane.newObjectInstance();
		
		to.putString("jsonrpc", "2.0");
		to.putString("method", method);
		to.putString("auth", path.getApiClient().sessionId);
		to.putString("id", "1");
		
		DataObject ps = to.newObjectInstance();
		
		Parameter[] params = b.getParameters();
		
		if(1 == params.length && ZabbixParameters.class.isAssignableFrom(params[0].getType()))
		{
			ZabbixParameters zps = (ZabbixParameters) c[0];
			for(String k:zps.keys())
			{
				Object val = zps.get(k);
				if(null != val)
				{
					DataReprezTools.put(BidirectionalRpcDefaultProtocol.DEFAULT_RPC_DATA_WRAPPER, ps, k, val);
				}
			}
		}
		else
		{
		
			for(int i=0;i<params.length;++i)
			{
				Parameter param = params[i];
				
				ParameterName pn = param.getAnnotation(ParameterName.class);
				if(null == pn)
				{
					throw new RuntimeException("No parameter name present at method signature `"+b+"` at parameter index `"+i+"`");
				}
				
				DataReprezTools.put(BidirectionalRpcDefaultProtocol.DEFAULT_RPC_DATA_WRAPPER, ps, pn.name(), c[i]);
			}
		}
		
		ExtraParameters ep = b.getAnnotation(ExtraParameters.class);
		
		if(null != ep)
		{
			Map<String, String[]> p = new SmallMap<>();
			UrlTools.processArgsRequest(ep.params(), p);
			Map<String, String> map = UrlTools.convMapMulti(p);
			for(Entry<String, String> kv:map.entrySet())
			{
				ps.putString(kv.getKey(), kv.getValue());
			}
		}
		
		to.putObject("params", ps);
		
		URL u = new URL(path.getApiClient().url);
		
		byte[] rj = post(u, to.getImpl().toString().getBytes());
		
		//return as array
		DataObject result = null;
		try
		{
			result = to.objectFromBlob(rj);
		}
		catch(Exception e)
		{
			throw new RuntimeException("Exception while parsing result: "+new String(rj), e);
		}
		
		if(result.has("error"))
		{
			throw new RuntimeException("Error while calling `"+method+"`: "+result.getImpl());
		}
		
		Class<?> ret = b.getReturnType();
		
		if(List.class.isAssignableFrom(ret))
		{
			DataArray arr = result.getArray("result");
			Type genRet = b.getGenericReturnType();
			if(genRet instanceof ParameterizedType)
			{
				Type[] args = ((ParameterizedType)genRet).getActualTypeArguments();
				if(args.length > 0)
				{
					Type t = args[0];
					if(t instanceof Class && ModifiableObject.class.isAssignableFrom(((Class)t)))
					{
						ArrayList ar = new ArrayList<>();
						Class target = (Class) t;
						
						for(int i=0;i<arr.size();++i)
						{
							try
							{
								ModifiableObject dst = (ModifiableObject) target.newInstance();
								DataReprezTools.copyInto(dst, arr.getObject(i));
								ar.add(dst);
							}
							catch(Exception e)
							{
								e.printStackTrace();
							}
						}
						
						return ar;
					}
					else
					{
						throw new RuntimeException("The generic parameter of List returing type at method `"+b+"` doesn't extends "+TargetObject.class.getCanonicalName());
					}
				}
			}
		}
		
		if(ModifiableObject.class.isAssignableFrom(ret))
		{
			ModifiableObject dst = (ModifiableObject) ret.newInstance();
			DataReprezTools.copyInto(dst, result.getObject("result"));
			return dst;
		}
		return result.get("result");
	}
	
	public static <T extends ZabbixApi> T wrapWithClass(Class<T> cls, ZabbixGenericApiNode zkp) throws Exception
	{
		if(cls.isInterface())
		{
			return ProxyDataWrapperTools.wrapAccessor(zkp, cls, MAPPER);
		}
		else
		{
			Constructor<?> constr = cls.getConstructor(ZabbixApiClient.class);
			if(null == constr)
			{
				throw new RuntimeException("No constructor "+cls.getSimpleName()+"(ZookeeperPath zkp) defined in the requested class.");
			}
			
			return (T) constr.newInstance(zkp);
		}
	}
	
	public static byte[] post(URL url, byte[] POST_data) throws IOException
	{
		URLConnection connection = url.openConnection();
		
		if(null != POST_data)
		{
			connection.addRequestProperty("Content-Type", "application/json");
			connection.addRequestProperty("Content-Length", String.valueOf(POST_data.length));
			connection.setDoOutput(true);
			try
			(
				OutputStream os = connection.getOutputStream();
			)
			{
				if(null != POST_data)
				{
					os.write(POST_data);
					os.flush();
				}
			}
		}
		
		return IOTools.loadAllFromInputStream(connection.getInputStream());
	}
	
	protected static final Set<String> BACKEND_IMPL_METHOD_NAMES = new HashSet<>();
	
	static
	{
		for(Method m:ZabbixApi.class.getDeclaredMethods())
		{
			if(null == m.getAnnotation(Ignore.class))
			{
				BACKEND_IMPL_METHOD_NAMES.add(m.getName());
			}
		}
	}
	
	protected static Object relayApiCall(ZabbixGenericApiNode path, Method b, Object[] c) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
	{
		return ZabbixGenericApiNode.class.getDeclaredMethod(b.getName(), b.getParameterTypes()).invoke(path, c);
	}
	
	public ZabixUserData login(String user, String password)
	{
		ZabixUserData ret = root.getUser().login(user, password);
		sessionId = ret.sessionid;
		return ret;
	}
	
	@Override
	public void close() throws IOException
	{
		logout();
	}
	
	public void logout()
	{
		root.getUser().logout();
	}

	public ZabbixRoot getApiRoot()
	{
		return root;
	}
}