/** * 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.ArrayList; import java.util.HashMap; import java.util.LinkedList; import org.appwork.net.protocol.http.HTTPConstants; import org.appwork.net.protocol.http.HTTPConstants.ResponseCode; import org.appwork.storage.JSonStorage; import org.appwork.storage.TypeRef; import org.appwork.utils.Regex; import org.appwork.utils.net.HTTPHeader; import org.appwork.utils.net.httpserver.HttpRequestHandler; import org.appwork.utils.net.httpserver.requests.GetRequest; import org.appwork.utils.net.httpserver.requests.HttpRequest; import org.appwork.utils.net.httpserver.requests.PostRequest; import org.appwork.utils.net.httpserver.responses.HttpResponse; import org.appwork.utils.reflection.Clazz; /** * @author daniel * */ public class RemoteAPI implements HttpRequestHandler { @SuppressWarnings("unchecked") public static <T> T cast(Object v, final Class<T> type) { if (type.isPrimitive()) { if (type == boolean.class) { v = ((Boolean) v).booleanValue(); } else if (type == char.class) { v = (char) ((Long) v).byteValue(); } else if (type == byte.class) { v = ((Number) v).byteValue(); } else if (type == short.class) { v = ((Number) v).shortValue(); } else if (type == int.class) { v = ((Number) v).intValue(); } else if (type == long.class) { v = ((Number) v).longValue(); } else if (type == float.class) { v = ((Number) v).floatValue(); } else if (type == double.class) { // v = ((Number) v).doubleValue(); } } return (T) v; } /* hashmap that holds all registered interfaces and their pathes */ private final HashMap<String, InterfaceHandler<?>> interfaces = new HashMap<String, InterfaceHandler<?>>(); private final Object LOCK = new Object(); public RemoteAPI() { } private void _handleRemoteAPICall(final RemoteAPIRequest request, final RemoteAPIResponse response) throws UnsupportedEncodingException, IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ApiCommandNotAvailable, BadParameterException { final Method method = request.getMethod(); if (method == null) { throw new ApiCommandNotAvailable(); } final Object[] parameters = new Object[method.getParameterTypes().length]; boolean responseIsParameter = false; int count = 0; for (int i = 0; i < parameters.length; i++) { if (method.getParameterTypes()[i] == RemoteAPIRequest.class) { parameters[i] = request; } else if (method.getParameterTypes()[i] == RemoteAPIResponse.class) { responseIsParameter = true; parameters[i] = response; } else { try { parameters[i] = this.convert(request.getParameters()[count], method.getGenericParameterTypes()[i]); } catch (final Throwable e) { throw new BadParameterException(request.getParameters()[count], e); } count++; } } final Object ret = request.getIface().invoke(method, parameters); if (responseIsParameter) { return; } if (Clazz.isVoid(method.getReturnType())) { response.setResponseCode(ResponseCode.SUCCESS_OK); response.getResponseHeaders().add(new HTTPHeader(HTTPConstants.HEADER_REQUEST_CONTENT_LENGTH, "0")); } else { response.setResponseCode(ResponseCode.SUCCESS_OK); final String text = JSonStorage.toString(ret); 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 string * @param type * @return */ private Object convert(final String string, final Type type) { @SuppressWarnings("unchecked") final Object v = JSonStorage.restoreFromString(string, new TypeRef(type) { }, null); if (type instanceof Class && Clazz.isPrimitive((Class<?>) type)) { return RemoteAPI.cast(v, (Class<?>) type); } else { return v; } } public RemoteAPIRequest getInterfaceHandler(final HttpRequest request) { final String[] intf = new Regex(request.getRequestedPath(), "/((.+)/)?(.+)$").getRow(0); if (intf == null || intf.length != 3) { return null; } InterfaceHandler<?> interfaceHandler = null; if (intf[1] == null) { intf[1] = ""; } synchronized (this.LOCK) { interfaceHandler = this.interfaces.get(intf[1]); } if (interfaceHandler == null) { return null; } final ArrayList<String> parameters = new ArrayList<String>(); /* convert GET parameters to methodParameters */ for (final String[] param : request.getRequestedURLParameters()) { if (param[1] != null) { /* key=value(parameter) */ parameters.add(param[1]); } else { /* key(parameter) */ parameters.add(param[0]); } } if (request instanceof PostRequest) { try { final LinkedList<String[]> ret = ((PostRequest) request).getPostParameter(); if (ret != null) { /* add POST parameters to methodParameters */ for (final String[] param : ret) { if (param[1] != null) { /* key=value(parameter) */ parameters.add(param[1]); } else { /* key(parameter) */ parameters.add(param[0]); } } } } catch (final Throwable e) { throw new RuntimeException(e); } } return new RemoteAPIRequest(interfaceHandler, intf[2], parameters.toArray(new String[] {}), request); } /** * @param interfaceHandler * @param string * @return */ public boolean onGetRequest(final GetRequest request, final HttpResponse response) { final RemoteAPIRequest apiRequest = this.getInterfaceHandler(request); if (apiRequest == null) { return false; } try { this._handleRemoteAPICall(apiRequest, new RemoteAPIResponse(response)); } catch (final Throwable e) { throw new RuntimeException(e); } return true; } public boolean onPostRequest(final PostRequest request, final HttpResponse response) { final RemoteAPIRequest apiRequest = this.getInterfaceHandler(request); if (apiRequest == null) { return false; } try { this._handleRemoteAPICall(apiRequest, new RemoteAPIResponse(response)); } catch (final Throwable e) { throw new RuntimeException(e); } return true; } @SuppressWarnings("unchecked") public void register(final RemoteAPIInterface x) throws ParseException { synchronized (this.LOCK) { for (final Class<?> c : x.getClass().getInterfaces()) { if (RemoteAPIInterface.class.isAssignableFrom(c)) { String namespace = c.getName(); final ApiNamespace a = c.getAnnotation(ApiNamespace.class); if (a != null) { namespace = a.value(); } if (this.interfaces.containsKey(namespace)) { throw new IllegalStateException("Interface " + c.getName() + " with namespace " + namespace + " already has been registered by " + this.interfaces.get(namespace)); } System.out.println("Register: " + c.getName() + "->" + namespace); try { this.interfaces.put(namespace, InterfaceHandler.create((Class<RemoteAPIInterface>) c, x)); } catch (final SecurityException e) { throw new ParseException(e); } catch (final NoSuchMethodException e) { throw new ParseException(e); } } } } } public void unregister(final RemoteAPIInterface x) { synchronized (this.LOCK) { for (final Class<?> c : x.getClass().getInterfaces()) { if (RemoteAPIInterface.class.isAssignableFrom(c)) { String namespace = c.getName(); final ApiNamespace a = c.getAnnotation(ApiNamespace.class); if (a != null) { namespace = a.value(); } this.interfaces.remove(namespace); } } } } }