From f4025a39226331d8c1c4686d2d1c73e080f9f4b3 Mon Sep 17 00:00:00 2001
From: ulrich
Date: Sat, 30 Nov 2024 18:31:19 +0000
Subject: [PATCH] Fix: wireActors, wenn App als JAR laeuft

---
 src/de/uhilger/neon/Handler.java    |   50 ++++++++-
 src/de/uhilger/neon/JarScanner.java |  144 ++++++++++++++++++++++++++++
 src/de/uhilger/neon/RangeGroup.java |    2 
 src/de/uhilger/neon/Factory.java    |   77 ++++++++++-----
 4 files changed, 238 insertions(+), 35 deletions(-)

diff --git a/src/de/uhilger/neon/Factory.java b/src/de/uhilger/neon/Factory.java
index 14015d5..ff5977d 100644
--- a/src/de/uhilger/neon/Factory.java
+++ b/src/de/uhilger/neon/Factory.java
@@ -23,6 +23,7 @@
 import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
+import de.uhilger.neon.JarScanner.JarScannerListener;
 import de.uhilger.neon.entity.ContextDescriptor;
 import de.uhilger.neon.entity.NeonDescriptor;
 import de.uhilger.neon.entity.ServerDescriptor;
@@ -35,6 +36,9 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -42,6 +46,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * Einen Neon-Server aus einer Beschreibungsdatei herstellen
@@ -59,7 +65,7 @@
  * @author Ulrich Hilger
  * @version 1, 6.2.2024
  */
-public class Factory {
+public class Factory implements JarScannerListener {
 
   public Factory() {
     listeners = new ArrayList<>();
@@ -89,16 +95,16 @@
     return gson.fromJson(sb.toString(), NeonDescriptor.class);
   }
   
-  public void runInstance(NeonDescriptor d) 
+  public void runInstance(Class c, NeonDescriptor d) 
           throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
-    this.runInstance(d, null, new ArrayList<>());
+    this.runInstance(c, d, null, new ArrayList<>());
   }
 
-  public void runInstance(NeonDescriptor d, List<String> packageNames) 
+  public void runInstance(Class c, NeonDescriptor d, List<String> packageNames) 
           throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
-    this.runInstance(d, packageNames, new ArrayList<>());
+    this.runInstance(c, d, packageNames, new ArrayList<>());
   }
   /**
    * Einen Neon-Server gemaess einem Serverbeschreibungsobjekt herstellen und starten
@@ -115,7 +121,7 @@
    * @throws InvocationTargetException
    * @throws IOException
    */
-  public void runInstance(NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp) 
+  public void runInstance(Class c, NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp) 
           throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
     List serverList = d.server;
@@ -128,7 +134,7 @@
       if(packageNames == null) {
         packageNames = d.actorPackages;
       }
-      addContexts(d, server, sd.contexts, packageNames, sdp);
+      addContexts(c, d, server, sd.contexts, packageNames, sdp);
 
       server.setExecutor(Executors.newFixedThreadPool(10));
       server.start();
@@ -156,7 +162,7 @@
     return auth;
   }
 
-  private void addContexts(NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames, 
+  private void addContexts(Class c, NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames, 
           List<DataProvider> sdp) 
           throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
           IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
@@ -176,7 +182,7 @@
         ctxAttrs.putAll(cd.attributes);        
         if (h instanceof Handler) {         
           for (String packageName : packageNames) {
-            wireActors(
+            wireActors(c, 
                     packageName, Actor.class, (Handler) h, 
                     cd.attributes.get("contextName"));
               ctx.getAttributes().put("serverDataProviderList", sdp);
@@ -254,27 +260,39 @@
     }
   }
 
-  private void wireActors(String packageName, Class annotation, Handler h, String contextName) {
-    ClassLoader cl = ClassLoader.getSystemClassLoader();
-    InputStream stream = cl
-            .getResourceAsStream(packageName.replaceAll("[.]", "/"));
-    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
-    Iterator i = reader.lines().iterator();
-    while (i.hasNext()) {
-      String line = i.next().toString();
-      if (line.endsWith(".class")) {
-        try {
-          Class actorClass = Class.forName(packageName + "."
-                  + line.substring(0, line.lastIndexOf('.')));
-          if (actorClass != null && actorClass.isAnnotationPresent(annotation)) {
-            wire(h, actorClass, contextName);
+  private void wireActors(Class c, String packageName, Class annotation, Handler h, String contextName) {
+    JarScanner js = new JarScanner();
+    URI path;
+    try {
+      path = js.getPath(c);
+      if(path.toString().endsWith(".class")) {
+        ClassLoader cl = c.getClassLoader();
+        InputStream stream = cl
+                .getResourceAsStream(packageName.replaceAll("[.]", "/"));
+        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+        Iterator i = reader.lines().iterator();
+        while (i.hasNext()) {
+          String line = i.next().toString();
+          if (line.endsWith(".class")) {
+            try {
+              Class actorClass = cl.loadClass(packageName + "."
+                      + line.substring(0, line.lastIndexOf('.')));
+              if (actorClass != null && actorClass.isAnnotationPresent(annotation)) {
+                wire(h, actorClass, contextName);
+              }
+            } catch (ClassNotFoundException ex) {
+              // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
+            }
+          } else {
+            wireActors(c, packageName + "." + line, annotation, h, contextName);
           }
-        } catch (ClassNotFoundException ex) {
-          // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
         }
       } else {
-        wireActors(packageName + "." + line, annotation, h, contextName);
+        ClassLoader cl = js.getUrlClassLoader(c);
+        js.processZipContent(cl, new File(path), packageName, this, h, contextName);
       }
+    } catch (URISyntaxException ex) {
+      Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
     }
   }
   
@@ -346,4 +364,11 @@
       l.instanceStarted();
     }
   }
+
+  /* -------------- JarScannerListener Implementierung --------------- */
+    
+  @Override
+  public void actorFound(Class actorClass, Handler h, String contextName) {
+    wire(h, actorClass, contextName);
+  }
 }
diff --git a/src/de/uhilger/neon/Handler.java b/src/de/uhilger/neon/Handler.java
index 14d34d3..db7d025 100644
--- a/src/de/uhilger/neon/Handler.java
+++ b/src/de/uhilger/neon/Handler.java
@@ -22,6 +22,7 @@
 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;
@@ -30,6 +31,8 @@
 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.
@@ -91,7 +94,17 @@
 
     //Logger.getLogger(Handler.class.getName())
     //        .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className});
-    dispatcher.get(methodType).put(ad.route, ad);
+    //dispatcher.get(methodType).put(ad.route, ad);
+    Object adMapObj = dispatcher.get(methodType);
+    if(adMapObj instanceof HashMap hashMap) {
+      @SuppressWarnings("unchecked")
+      HashMap<String, ActionDescriptor> map = hashMap;
+      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");        
+    }
+    
   }
 
   /**
@@ -135,8 +148,9 @@
                     .getHttpContext()
                     .getPath()
                     .length());            
-            
-    Type requestMethod = Type.valueOf(exchange.getRequestMethod());
+    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 
@@ -147,10 +161,12 @@
     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;
@@ -163,16 +179,24 @@
         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 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 {
@@ -183,11 +207,20 @@
         if (action != null) {
           if ((action.route().equals("/") || action.route().startsWith(route)) && action.type().equals(requestMethod)) {
             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);
+            @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");        
             }
           }
         }
@@ -196,6 +229,7 @@
             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");        
     }
     //}
   }
diff --git a/src/de/uhilger/neon/JarScanner.java b/src/de/uhilger/neon/JarScanner.java
new file mode 100644
index 0000000..d2c350c
--- /dev/null
+++ b/src/de/uhilger/neon/JarScanner.java
@@ -0,0 +1,144 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+
+package de.uhilger.neon;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Die Klasse JarScanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, in 
+ * welcher JAR-Datei sie liegt und diese JAR-Datei nach Klassen zu durchsuchen.
+ * 
+ * @author Ulrich Hilger
+ * @version 0.1, 30.11.2024
+ */
+public class JarScanner {
+  
+  
+  public void processZipContent(ClassLoader urlCL, File archive, String packageName, JarScannerListener l, Handler h, String contextName) {
+    try {
+      ZipFile zipfile = new ZipFile(archive);
+      Enumeration en = zipfile.entries();
+      while (en.hasMoreElements()) {
+        ZipEntry zipentry = (ZipEntry) en.nextElement();
+        if (!zipentry.isDirectory()) {
+          processZipEntry(urlCL, zipentry, packageName, l, h, contextName);
+        } else {
+          // ZIP-Dir muss nicht bearbeitet werden
+        }
+      }
+      zipfile.close();
+    } catch (IOException ex) {
+      log(Level.SEVERE, ex.getLocalizedMessage());
+    }
+  }
+
+  private void processZipEntry(ClassLoader urlCL, ZipEntry zipentry, String packageName, JarScannerListener l, Handler h, String contextName) {
+    log(Level.FINEST, zipentry.getName());
+    String zName = zipentry.getName();
+    if (zName.toLowerCase().endsWith(".class")) {
+      int pos = zName.indexOf(".class");
+      String fullClassName = zName.substring(0, pos);
+      log(Level.FINEST, "full class name: " + zName);
+      String fullClassNameDots = fullClassName.replace('/', '.');
+      log(Level.FINEST, "full class name dots: " + fullClassNameDots);
+      String pkgName = getPackageName(fullClassNameDots);
+      log(Level.FINEST, " -- package name: " + pkgName);
+      if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
+        Class c = null;
+        try {
+          c = urlCL.loadClass(fullClassNameDots);
+          if (c != null) {
+            if (c.isAnnotationPresent(Actor.class)) {
+              log(Level.FINER, " ---- ACTOR ---- " + fullClassNameDots);
+              l.actorFound(c, h, contextName);
+            } else {
+              log(Level.FINER, "kein Actor " + fullClassNameDots);
+            }
+          } else {
+            log(Level.FINER, "class NOT loaded: " + zName);
+          }
+        } catch (ClassNotFoundException ex) {
+          log(Level.FINER, " +++++ Class not found: " + ex.getMessage());
+        }
+      }
+    }
+  }
+  
+  private String getPackageName(String fullClassName) {
+    String packageName;
+    int pos = fullClassName.lastIndexOf(".");
+    if (pos > 0) {
+      packageName = fullClassName.substring(0, pos);
+    } else {
+      packageName = fullClassName;
+    }
+    return packageName;
+  }
+  
+  public ClassLoader getUrlClassLoader(Class c) {
+    URL url;
+    ClassLoader urlCL = null;
+    try {
+      url = getPath(c).toURL();
+      log(Level.FINER, "url: " + url.getPath());
+      urlCL = new URLClassLoader(new URL[]{url});
+    } catch (URISyntaxException ex) {
+      log(Level.SEVERE, ex.getMessage());
+    } catch (MalformedURLException ex) {
+      log(Level.SEVERE, ex.getMessage());
+    } finally {
+      return urlCL;
+    }
+  }
+  
+  public URI getPath(Class c) throws URISyntaxException {
+    //Class c = this.getClass();
+    String className = c.getName();
+    finer("this name: " + className);
+    
+    int pos = className.indexOf(".class");
+    if(pos > -1) {
+      String classNameWoExt = className.substring(0, pos);
+    }
+    String classNameWoPkg = className.substring(className.lastIndexOf(".") + 1);
+    finer("Class name: " + classNameWoPkg);
+    String classPath = c.getResource(classNameWoPkg + ".class").getPath();
+    pos = classPath.indexOf("!");
+    String jarPath;
+    if(pos > -1) {
+      jarPath = /*"jar:" + */ classPath.substring(0, pos);
+    } else {
+      jarPath = classPath;
+    }
+    finer("path: " + jarPath);
+    return new URI(jarPath);
+  }
+  
+  private void finer(String msg) {
+    log(Level.FINER, msg);
+  }
+  
+  private void log(Level l, String msg) {
+    Logger.getLogger(JarScanner.class.getName()).log(l, msg);
+  }
+  
+  
+  public interface JarScannerListener {
+    public void actorFound(Class actorClass, Handler h, String contextName);
+  }
+  
+}
diff --git a/src/de/uhilger/neon/RangeGroup.java b/src/de/uhilger/neon/RangeGroup.java
index 8aa2adb..1dc369c 100644
--- a/src/de/uhilger/neon/RangeGroup.java
+++ b/src/de/uhilger/neon/RangeGroup.java
@@ -35,7 +35,7 @@
      * Ein neues Objekt der Klasse RangeGroup erzeugen
      */
     public RangeGroup() {
-      ranges = new ArrayList();
+      ranges = new ArrayList<>();
     }
 
     /**

--
Gitblit v1.9.3