From 5ebab9058498312ad234226040f0ec506509e476 Mon Sep 17 00:00:00 2001
From: ulrich
Date: Wed, 21 Feb 2024 13:17:06 +0000
Subject: [PATCH] Verarbeitung von Routen um variable Elemente erweitert (einstweilen noch experimentell)

---
 src/de/uhilger/neon/Handler.java                 |  154 +++++++++++++++++++++++--------------
 src/de/uhilger/neon/entity/ActionDescriptor.java |   30 +++++++
 2 files changed, 125 insertions(+), 59 deletions(-)

diff --git a/src/de/uhilger/neon/Handler.java b/src/de/uhilger/neon/Handler.java
index 44c3b0d..6caa331 100644
--- a/src/de/uhilger/neon/Handler.java
+++ b/src/de/uhilger/neon/Handler.java
@@ -20,6 +20,7 @@
 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.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -31,13 +32,12 @@
 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.
+ * 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.
+ * 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
@@ -51,10 +51,14 @@
    */
   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>());
   }
 
   /**
@@ -66,9 +70,28 @@
    * 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);
   }
 
   /**
@@ -105,69 +128,80 @@
    */
   @Override
   public void handle(HttpExchange exchange) throws IOException {
-    HttpHelper hh = new HttpHelper();    
+    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.
      */
+    boolean found = false;
     Object md = dispatcher.get(requestMethod);
-    if(md instanceof Map) {
-    Object o = ((Map) md).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);
-              if(!action.handlesResponse()) {
-                respond(exchange, antwort);
-              }
-            }
+    if (md instanceof Map) {
+      int pos = route.lastIndexOf("/");
+      Object o = ((Map) md).get(route);
+      if (!(o instanceof ActionDescriptor)) {
+        while (!found && (pos > -1)) {
+          String routeRest = route.substring(0, pos);
+          o = ((Map) md).get(routeRest);
+          if (o instanceof ActionDescriptor) {
+            found = true;
+            handleRequest(exchange, o, routeRest, route.substring(routeRest.length()));
           }
+          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);
       }
-    }
+      if (!found) {
+        o = dispatcher.get(requestMethod).get("/");
+        if (o instanceof ActionDescriptor) {
+          handleRequest(exchange, o, route, route);
+        }
+      }
     }
 
   }
 
-  private Object[] getActionArgs(HttpExchange exchange, Method method/*, Map queryParams*/) {    
+  private void handleRequest(HttpExchange exchange, Object o, String route, String subroute) throws IOException {
+    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)) {
+            Object[] actionArgs = getActionArgs(exchange, method, ad, subroute);
+            Object actorObj = actorClass.getDeclaredConstructor().newInstance();
+            addDataProvider(exchange, actorObj);
+            Object antwort = method.invoke(actorObj, actionArgs);
+            if (!action.handlesResponse()) {
+              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, ActionDescriptor ad, String subroute) {
     int count = method.getParameterCount();
     Parameter[] methodParams = method.getParameters();
     Object[] actionArgs = new Object[count];
+    String[] routeParams = subroute.split("/");
     Map queryParams = new HashMap();
-    /*
-      Lesen des Body der Anfrage geht nur einmal. 
-    
-      bodyLesen soll nur in getQueryMap gerufen werden, wenn 
-      die Liste der Parameter mehr als einen Parameter umfasst 
-      oder wenn es nur ein Parameter ist, der nicht 
-      der HttpExchange ist.
-    
-      Anderenfalls sollte erst der Actor den Body aus dem 
-      HttpExchange lesen und nicht hier schon der Handler. 
-    */
-    if(count > 1 || !methodParams[0].getType().equals(HttpExchange.class)) {
+    if ((count > 1 && count > routeParams.length)
+            || (methodParams.length > 0 && !methodParams[0].getType().equals(HttpExchange.class))) {
       queryParams = new HttpHelper().getQueryMap(exchange);
     }
     int k = 0;
@@ -175,12 +209,14 @@
       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;
   }
diff --git a/src/de/uhilger/neon/entity/ActionDescriptor.java b/src/de/uhilger/neon/entity/ActionDescriptor.java
new file mode 100644
index 0000000..997f3c6
--- /dev/null
+++ b/src/de/uhilger/neon/entity/ActionDescriptor.java
@@ -0,0 +1,30 @@
+/*
+  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 <https://www.gnu.org/licenses/>.
+ */
+package de.uhilger.neon.entity;
+
+import java.util.Map;
+
+/**
+ *
+ * @author Ulrich Hilger
+ */
+public class ActionDescriptor {
+  public String route;
+  public Map<String, Integer> routeParams;
+  public String className;
+}

--
Gitblit v1.9.3