| | |
| | | import com.sun.net.httpserver.HttpExchange; |
| | | import com.sun.net.httpserver.HttpHandler; |
| | | import de.uhilger.neon.Action.Type; |
| | | import de.uhilger.neon.entity.ActionDescriptor; |
| | | import java.io.IOException; |
| | | import java.lang.reflect.Constructor; |
| | | import java.lang.reflect.InvocationTargetException; |
| | | import java.lang.reflect.Method; |
| | | import java.lang.reflect.Parameter; |
| | |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.logging.Level; |
| | | import java.util.logging.Logger; |
| | | |
| | | /** |
| | | * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen |
| | | * NeonActor enthalten. Deren mit NeonMethod annotierten Methoden stellt |
| | | * der Handler via HTTP bereit. |
| | | * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen Actor enthalten. |
| | | * Deren mit Action annotierten Methoden stellt der Handler via HTTP bereit. |
| | | * |
| | | * Wird ein Neon-Server mit der Klasse NeonFactory erzeugt, kann mit der Verwendung |
| | | * dieses Handlers die NeonFactory den Server selbsttaetig erstellen, ohne |
| | | * zusaetzlichen Boilerplate Code, den eine eigene Anwendung mitbringen muesste. |
| | | * Wird ein Neon-Server mit der Klasse Factory erzeugt, kann mit der Verwendung dieses Handlers |
| | | * die Factory den Server selbsttaetig erstellen, ohne zusaetzlichen Boilerplate Code, den eine |
| | | * eigene Anwendung mitbringen muesste. |
| | | * |
| | | * @author Ulrich Hilger |
| | | * @version 1, 6.2.2024 |
| | |
| | | */ |
| | | public Handler() { |
| | | dispatcher = new EnumMap<>(Type.class); |
| | | dispatcher.put(Type.GET, new HashMap<String, String>()); |
| | | dispatcher.put(Type.PUT, new HashMap<String, String>()); |
| | | dispatcher.put(Type.POST, new HashMap<String, String>()); |
| | | dispatcher.put(Type.DELETE, new HashMap<String, String>()); |
| | | //dispatcher.put(Type.GET, new HashMap<String, String>()); |
| | | //dispatcher.put(Type.PUT, new HashMap<String, String>()); |
| | | //dispatcher.put(Type.POST, new HashMap<String, String>()); |
| | | //dispatcher.put(Type.DELETE, new HashMap<String, String>()); |
| | | dispatcher.put(Type.GET, new HashMap<String, ActionDescriptor>()); |
| | | dispatcher.put(Type.PUT, new HashMap<String, ActionDescriptor>()); |
| | | dispatcher.put(Type.POST, new HashMap<String, ActionDescriptor>()); |
| | | dispatcher.put(Type.DELETE, new HashMap<String, ActionDescriptor>()); |
| | | } |
| | | |
| | | /** |
| | |
| | | * ausgefuehrt werden soll |
| | | */ |
| | | public void setActor(Type methodType, String route, String className) { |
| | | ActionDescriptor ad = new ActionDescriptor(); |
| | | ad.className = className; |
| | | ad.routeParams = new HashMap<>(); |
| | | int pos = route.indexOf("{"); |
| | | if (pos > -1) { |
| | | String paramStr = route.substring(pos); |
| | | String[] params = paramStr |
| | | .replaceAll("\\{", "") |
| | | .replaceAll("\\}", "") |
| | | .split("/"); |
| | | for (int i = 0; i < params.length; i++) { |
| | | ad.routeParams.put(params[i], i); |
| | | } |
| | | ad.route = route.substring(0, pos - 1); |
| | | } else { |
| | | // Map kann leer bleiben |
| | | ad.route = route; |
| | | } |
| | | |
| | | //Logger.getLogger(Handler.class.getName()) |
| | | // .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className}); |
| | | dispatcher.get(methodType).put(route, className); |
| | | //dispatcher.get(methodType).put(ad.route, ad); |
| | | Object adMapObj = dispatcher.get(methodType); |
| | | if(adMapObj instanceof HashMap) { |
| | | @SuppressWarnings("unchecked") |
| | | HashMap<String, ActionDescriptor> map = (HashMap) adMapObj; |
| | | map.put(ad.route, ad); |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "ActionDescriptor route {0} className {1}", new Object[]{route, className}); |
| | | } else { |
| | | Logger.getLogger(Handler.class.getName()).finer("ActionDescriptorMap nicht gefunden"); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public void handle(HttpExchange exchange) throws IOException { |
| | | HttpHelper hh = new HttpHelper(); |
| | | String route = hh.getRouteString(exchange); |
| | | Type requestMethod = Type.valueOf(exchange.getRequestMethod()); |
| | | Map queryParams = hh.getQueryMap(exchange); |
| | | Object o; |
| | | |
| | | String route = exchange |
| | | .getRequestURI() |
| | | .getPath() |
| | | .substring(exchange |
| | | .getHttpContext() |
| | | .getPath() |
| | | .length()); |
| | | String requestMethodStr = exchange.getRequestMethod(); |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "method {0} route {1}", new Object[]{requestMethodStr, route}); |
| | | Type requestMethod = Type.valueOf(requestMethodStr); |
| | | /* |
| | | Es wird erst geprueft, ob zu einer bestimmten Route |
| | | ein Actor registriert wurde. Wenn kein Actor mit dieser |
| | | Route existiert, wird geprueft, ob ein Actor |
| | | mit der Route '/' vorhanden ist. |
| | | */ |
| | | o = dispatcher.get(requestMethod).get(route); |
| | | if (!(o instanceof String)) { |
| | | o = dispatcher.get(requestMethod).get("/"); |
| | | } |
| | | |
| | | if (o instanceof String) { |
| | | String actorClassName = (String) o; |
| | | try { |
| | | Class actorClass = Class.forName(actorClassName); |
| | | Method[] methods = actorClass.getMethods(); |
| | | for (Method method : methods) { |
| | | Action action = method.getAnnotation(Action.class); |
| | | if (action != null) { |
| | | if (action.route().equals("/") || action.route().equals(route)) { |
| | | Object[] actionArgs = getActionArgs(exchange, method, queryParams); |
| | | Object actorObj = actorClass.getDeclaredConstructor().newInstance(); |
| | | addDataProvider(exchange, actorObj); |
| | | Object antwort = method.invoke(actorObj, actionArgs); |
| | | respond(exchange, antwort); |
| | | } |
| | | boolean found = false; |
| | | Object md = dispatcher.get(requestMethod); |
| | | if (md instanceof Map) { |
| | | int pos = route.lastIndexOf("/"); |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0}", pos); |
| | | Object o = ((Map) md).get(route); |
| | | if (!(o instanceof ActionDescriptor)) { |
| | | while (!found && (pos > -1)) { |
| | | String routeRest = route.substring(0, pos); |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0} routeRest {1}", new Object[]{pos, routeRest}); |
| | | o = ((Map) md).get(routeRest); |
| | | if (o instanceof ActionDescriptor) { |
| | | found = true; |
| | | handleRequest(exchange, o, routeRest, route.substring(routeRest.length()), requestMethod); |
| | | } |
| | | pos = routeRest.lastIndexOf("/"); |
| | | } |
| | | } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { |
| | | // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? |
| | | } else { |
| | | found = true; |
| | | handleRequest(exchange, o, route, route, requestMethod); |
| | | } |
| | | if (!found) { |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "{0} not found ", route); |
| | | o = dispatcher.get(requestMethod).get("/"); |
| | | if (o instanceof ActionDescriptor) { |
| | | handleRequest(exchange, o, route, route, requestMethod); |
| | | } else { |
| | | // kein ActionDescriptor für '/' |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actiondescriptor fuer '/'"); |
| | | } |
| | | } |
| | | } else { |
| | | // keine Actions fuer HTTP Methode |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actions fuer HTTP-Methode {0}", requestMethodStr); |
| | | } |
| | | |
| | | } |
| | | |
| | | private Object[] getActionArgs(HttpExchange exchange, Method method, Map queryParams) { |
| | | Object[] actionArgs = new Object[method.getParameterCount()]; |
| | | int k = 0; |
| | | private void handleRequest(HttpExchange exchange, Object o, String route, String subroute, Type requestMethod) throws IOException { |
| | | Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Handle Request route {0} subroute {1}", new Object[]{route, subroute}); |
| | | ActionDescriptor ad = (ActionDescriptor) o; |
| | | String actorClassName = ad.className; |
| | | try { |
| | | Class actorClass = Class.forName(actorClassName); |
| | | Method[] methods = actorClass.getMethods(); |
| | | for (Method method : methods) { |
| | | Action action = method.getAnnotation(Action.class); |
| | | if (action != null) { |
| | | if ((action.route().equals("/") || action.route().startsWith(route)) && action.type().equals(requestMethod)) { |
| | | Object[] actionArgs = getActionArgs(exchange, method, ad, subroute); |
| | | @SuppressWarnings("unchecked") |
| | | Object conObj = actorClass.getDeclaredConstructor(); |
| | | if(conObj instanceof Constructor) { |
| | | Constructor con = (Constructor) conObj; |
| | | Object actorObj; |
| | | actorObj = con.newInstance(); |
| | | addDataProvider(exchange, actorObj); |
| | | Object antwort = method.invoke(actorObj, actionArgs); |
| | | if (!action.handlesResponse()) { |
| | | respond(exchange, antwort); |
| | | } |
| | | } else { |
| | | // kein Konstruktor |
| | | Logger.getLogger(Handler.class.getName()).info("Kein Konstruktor gefunden"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | |
| | | InstantiationException | IllegalAccessException | IllegalArgumentException | |
| | | InvocationTargetException ex) { |
| | | // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? |
| | | Logger.getLogger(Handler.class.getName()).finer("Kein passende Actor-Klasse gefunden"); |
| | | } |
| | | //} |
| | | } |
| | | |
| | | private Object[] getActionArgs(HttpExchange exchange, Method method, ActionDescriptor ad, String subroute) { |
| | | int count = method.getParameterCount(); |
| | | Parameter[] methodParams = method.getParameters(); |
| | | Object[] actionArgs = new Object[count]; |
| | | String[] routeParams = subroute.split("/"); |
| | | |
| | | |
| | | /* |
| | | Fall 1: Es sind mehr als ein Parameter zu uebergeben und die Route enthaelt |
| | | weniger Parameter als die Methode erfordert. |
| | | Fall 2: Die Methode erwartet Parameter und der erste Parameter ist nicht |
| | | vom Typ HttpExchange. |
| | | |
| | | Wenn einer dieser beiden Faelle eintritt, wird alles als Parameter an die Methode |
| | | uebergeben, was eventuell als Teil einer Query im URL oder im Body enthalten ist. |
| | | Fuer Mthoden, die nicht vom Typ HTTP GET sind, kann ein Actor kann dann den Body |
| | | nicht mehr lesen, weil das bereits an dieser Stelle gemacht wurde. |
| | | */ |
| | | Map queryParams = new HashMap(); |
| | | if ((count > 1 && count > routeParams.length) |
| | | || (methodParams.length > 0 && !methodParams[0].getType().equals(HttpExchange.class))) { |
| | | queryParams = new HttpHelper().getQueryMap(exchange); |
| | | } |
| | | |
| | | int k = 0; |
| | | for (Parameter methodParam : methodParams) { |
| | | if (methodParam.getType().equals(HttpExchange.class)) { |
| | | actionArgs[k] = exchange; |
| | | } else { |
| | | /* |
| | | Konvention: Aktor-Parameter sind immer vom Typ String |
| | | und Parametername der Methode ist gleich dem Namen in der Query |
| | | */ |
| | | actionArgs[k++] = queryParams.get(methodParam.getName()); |
| | | Integer i = ad.routeParams.getOrDefault(methodParam.getName(), -1); |
| | | if (i < 0) { |
| | | actionArgs[k] = queryParams.get(methodParam.getName()); |
| | | } else { |
| | | actionArgs[k] = routeParams[i + 1]; |
| | | } |
| | | } |
| | | ++k; |
| | | } |
| | | return actionArgs; |
| | | } |