/* neon - Embeddable HTTP Server based on jdk.httpserver Copyright (C) 2024 Ulrich Hilger This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package de.uhilger.neon; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import de.uhilger.neon.Action.Type; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen * NeonActor enthalten. Deren mit NeonMethod 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. * * @author Ulrich Hilger * @version 1, 6.2.2024 */ public class Handler implements HttpHandler { private final Map dispatcher; /** * Ein Objekt der Klasse Handler erzeugen */ public Handler() { dispatcher = new EnumMap<>(Type.class); dispatcher.put(Type.GET, new HashMap()); dispatcher.put(Type.PUT, new HashMap()); dispatcher.put(Type.POST, new HashMap()); dispatcher.put(Type.DELETE, new HashMap()); } /** * Diesem Handler einen Actor hinzufuegen * * @param methodType HTTP Methode * @param route die Route, ueber die der Actor aufgerufen werden soll * @param className die Klasse, die die Methode enthaelt, die zur Verarbeitung der Route * ausgefuehrt werden soll */ public void setActor(Type methodType, String route, String className) { //Logger.getLogger(Handler.class.getName()) // .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className}); dispatcher.get(methodType).put(route, className); } /** * Eine Antwort senden * * Diese Methode kann ueberschrieben werden, falls die Antwort vor dem Senden noch weiter * bearbeiten werden soll. * * @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort * @param response das Ergebnisobjekt, das als Antwort gesendet werden soll * @throws IOException wenn die Antwort nicht gesendet werden kann */ public void respond(HttpExchange exchange, Object response) throws IOException { new HttpResponder().antwortSenden(exchange, HttpResponder.SC_OK, response.toString()); } /** * Das Objekt abrufen, das auf die Routen verweist, die von diesem Handler beantwortet werden. * * eine Map> GET - route1 - Klassenname GET - route2 - Klassenname PUT - * route1 - Klassenname PUT - route2 - Klassenname usw. * * @return das Dispatcher-Objekt dieses Handlers */ public Map getDispatcher() { return dispatcher; } /** * Eine HTTP-Anfrage ausfuehren * * @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort * @throws IOException */ @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; /* 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); } } } } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden? } } } private Object[] getActionArgs(HttpExchange exchange, Method method, Map queryParams) { Object[] actionArgs = new Object[method.getParameterCount()]; int k = 0; Parameter[] methodParams = method.getParameters(); 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()); } } return actionArgs; } private void addDataProvider(HttpExchange exchange, Object actorObj) { if (actorObj instanceof DataConsumer) { DataConsumer consumer = (DataConsumer) actorObj; Object sdpListObj = exchange.getHttpContext().getAttributes().get("serverDataProviderList"); if (sdpListObj instanceof List) { List sdpList = (List) sdpListObj; Iterator i = sdpList.iterator(); while (i.hasNext()) { Object value = i.next(); if (value instanceof DataProvider) { consumer.addDataProvider((DataProvider) value); } } } } } }