/**
* Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.remoteapi
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.remoteapi;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.appwork.net.protocol.http.HTTPConstants;
import org.appwork.net.protocol.http.HTTPConstants.ResponseCode;
import org.appwork.storage.InvalidTypeException;
import org.appwork.storage.JSonStorage;
import org.appwork.utils.logging.Log;
import org.appwork.utils.net.HTTPHeader;
/**
* @author thomas
*
*/
public class InterfaceHandler<T> {
/**
* @param c
* @param x
* @return
* @throws ParseException
* @throws NoSuchMethodException
* @throws SecurityException
*/
public static <T extends RemoteAPIInterface> InterfaceHandler<T> create(final Class<T> c, final RemoteAPIInterface x) throws ParseException, SecurityException, NoSuchMethodException {
final InterfaceHandler<T> ret = new InterfaceHandler<T>(c, x);
ret.parse();
return ret;
}
private final RemoteAPIInterface impl;
private final Class<T> interfaceClass;
private final TreeMap<String, TreeMap<Integer, Method>> methods;
private final HashMap<Method, Integer> parameterCountMap;
private static Method HELP;
static {
try {
InterfaceHandler.HELP = InterfaceHandler.class.getMethod("help", new Class[] { RemoteAPIRequest.class, RemoteAPIResponse.class });
} catch (final SecurityException e) {
Log.exception(e);
} catch (final NoSuchMethodException e) {
Log.exception(e);
}
}
/**
* @param <T>
* @param c
* @param x
* @throws NoSuchMethodException
* @throws SecurityException
*/
private InterfaceHandler(final Class<T> c, final RemoteAPIInterface x) throws SecurityException, NoSuchMethodException {
this.interfaceClass = c;
this.impl = x;
this.methods = new TreeMap<String, TreeMap<Integer, Method>>();
TreeMap<Integer, Method> map;
this.methods.put("help", map = new TreeMap<Integer, Method>());
map.put(0, InterfaceHandler.HELP);
this.parameterCountMap = new HashMap<Method, Integer>();
this.parameterCountMap.put(InterfaceHandler.HELP, 0);
}
/**
* @param length
* @param methodName
* @return
*/
public Method getMethod(final String methodName, final int length) {
if (methodName.equals(InterfaceHandler.HELP.getName())) { return InterfaceHandler.HELP; }
final TreeMap<Integer, Method> methodsByName = this.methods.get(methodName);
if (methodsByName == null) { return null; }
return methodsByName.get(length);
}
/**
* @param method
* @return
*/
public int getParameterCount(final Method method) {
return this.parameterCountMap.get(method);
}
public void help(final RemoteAPIRequest request, final RemoteAPIResponse response) throws InstantiationException, IllegalAccessException, UnsupportedEncodingException, IOException {
final StringBuilder sb = new StringBuilder();
sb.append(this.interfaceClass.getName());
sb.append("\r\n\r\n");
Entry<String, TreeMap<Integer, Method>> next;
for (final Iterator<Entry<String, TreeMap<Integer, Method>>> it = this.methods.entrySet().iterator(); it.hasNext();) {
next = it.next();
for (final Method m : next.getValue().values()) {
if (m == InterfaceHandler.HELP) {
sb.append("\r\n====- " + m.getName() + " -====");
sb.append("\r\n Description: This Call");
sb.append("\r\n Call: ");
sb.append("/" + m.getName() + "\r\n");
continue;
}
sb.append("\r\n====- " + m.getName() + " -====");
final ApiDoc an = m.getAnnotation(ApiDoc.class);
if (an != null) {
sb.append("\r\n Description: ");
sb.append(an.value() + "");
}
// sb.append("\r\n Description: ");
final HashMap<Type, Integer> map = new HashMap<Type, Integer>();
String call = "/" + m.getName();
int count = 0;
for (int i = 0; i < m.getGenericParameterTypes().length; i++) {
if (m.getParameterTypes()[i] == RemoteAPIRequest.class || m.getParameterTypes()[i] == RemoteAPIResponse.class) {
continue;
}
count++;
if (i > 0) {
call += "&";
} else {
call += "?";
}
Integer num = map.get(m.getParameterTypes()[i]);
if (num == null) {
map.put(m.getParameterTypes()[i], 0);
num = 0;
}
num++;
call += m.getParameterTypes()[i].getSimpleName() + "" + num;
sb.append("\r\n Parameter: " + count + " - " + m.getParameterTypes()[i].getSimpleName() + "" + num);
map.put(m.getParameterTypes()[i], num);
}
sb.append("\r\n Call: " + call);
sb.append("\r\n");
}
}
response.setResponseCode(ResponseCode.SUCCESS_OK);
final String text = sb.toString();
final int length = text.getBytes().length;
response.getResponseHeaders().add(new HTTPHeader(HTTPConstants.HEADER_REQUEST_CONTENT_LENGTH, length + ""));
response.getResponseHeaders().add(new HTTPHeader(HTTPConstants.HEADER_REQUEST_CONTENT_TYPE, "text"));
response.getOutputStream().write(text.getBytes("UTF-8"));
}
/**
* @param method
* @param parameters
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public Object invoke(final Method method, final Object[] parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (method.getDeclaringClass() == InterfaceHandler.class) {
return method.invoke(this, parameters);
} else {
return method.invoke(this.impl, parameters);
}
}
/**
* @throws ParseException
*
*/
private void parse() throws ParseException {
for (final Method m : this.interfaceClass.getMethods()) {
this.validateMethod(m);
TreeMap<Integer, Method> methodsByName = this.methods.get(m.getName());
if (methodsByName == null) {
methodsByName = new TreeMap<Integer, Method>();
this.methods.put(m.getName(), methodsByName);
}
int l = 0;
for (final Class<?> c : m.getParameterTypes()) {
if (c != RemoteAPIRequest.class && c != RemoteAPIResponse.class) {
l++;
}
}
this.parameterCountMap.put(m, l);
if (methodsByName.containsKey(l)) { throw new ParseException(this.interfaceClass + " Contains ambiguous methods: \r\n" + m + "\r\n" + methodsByName.get(l)); }
methodsByName.put(l, m);
}
}
/**
* @param m
* @throws ParseException
*/
private void validateMethod(final Method m) throws ParseException {
if (m == InterfaceHandler.HELP) { throw new ParseException(m + " is reserved for internal usage"); }
boolean responseIsParamater = false;
for (final Type t : m.getGenericParameterTypes()) {
if (RemoteAPIRequest.class == t) {
continue;
} else if (RemoteAPIResponse.class == t) {
responseIsParamater = true;
continue;
} else {
try {
JSonStorage.canStore(t);
} catch (final InvalidTypeException e) {
throw new ParseException("Parameter " + t + " of " + m + " is invalid", e);
}
}
}
if (responseIsParamater) {
if (m.getGenericReturnType() != void.class && m.getGenericReturnType() != Void.class) { throw new ParseException("Response in Parameters. " + m + " must return void, and has to handle the response itself"); }
} else {
try {
JSonStorage.canStore(m.getGenericReturnType());
} catch (final InvalidTypeException e) {
throw new ParseException("return Type of " + m + " is invalid", e);
}
}
}
}