Ultrakompakter HTTP Server
ulrich
2024-12-01 40f0b0d27ec38c97fadb4c64dba6855c6ee2e904
commit | author | age
f4025a 1 /*
c2e8cf 2   neon - Embeddable HTTP Server based on jdk.httpserver
U 3   Copyright (C) 2024  Ulrich Hilger
f4025a 4
c2e8cf 5   This program is free software: you can redistribute it and/or modify
U 6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
f4025a 18 package de.uhilger.neon;
U 19
692dc7 20 import java.io.BufferedReader;
f4025a 21 import java.io.File;
U 22 import java.io.IOException;
692dc7 23 import java.io.InputStream;
U 24 import java.io.InputStreamReader;
f4025a 25 import java.net.MalformedURLException;
U 26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.util.Enumeration;
692dc7 31 import java.util.Iterator;
f4025a 32 import java.util.logging.Level;
U 33 import java.util.logging.Logger;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36
37 /**
1f6776 38  * Die Klasse Scanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, in welcher JAR-Datei sie
U 39  * liegt und diese JAR-Datei nach Klassen zu durchsuchen.
47e67b 40  *
f4025a 41  * @author Ulrich Hilger
U 42  * @version 0.1, 30.11.2024
43  */
692dc7 44 public final class Scanner {
47e67b 45
U 46   private final URI path;
47   private final Class annotation;
692dc7 48
47e67b 49   private final Class cls;
U 50   private final ClassLoader urlCL;
51
52   /**
40f0b0 53    * Einen Scanner erzeugen, der den Ort, 
U 54    * in dem sich eine gegebene Klasse befindet, nach Klassen
1f6776 55    * durchsucht, die eine bestimmte Annotation besitzen
40f0b0 56    * 
U 57    * Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner 
58    * im Dateisystem sein.
47e67b 59    *
U 60    * @param c eine Klasse die sich im Archiv befindet, das durchsucht werden soll
61    * @param annotation die Annotation, nach der gesucht wird
62    */
692dc7 63   public Scanner(Class c, Class annotation) {
47e67b 64     this.annotation = annotation;
U 65     this.cls = c;
66     this.urlCL = getUrlClassLoader(cls);
67     this.path = getPath(c);
692dc7 68   }
U 69
70   public Class getAnnotation() {
71     return annotation;
1f6776 72   }
U 73
40f0b0 74   /**
U 75    * Klassen suchen, die die dem Konstruktor gegebene Annotation besitzen.
76    * 
77    * Anhand der im Konstruktor uebergebenen Klasse wird deren Ablageort ermittelt, entweder 
78    * ein Ordner im Dateisystem oder ein Java-Archiv (.jar). Dieser Ablageort wird dann 
79    * nach annotierten Klassen durchsucht. Gefundene Klassen werden dem Listener gemeldet.
80    *
81    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
82    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
83    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
84    * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
85    * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
86    */
87   
1f6776 88   public void process(ScannerListener l, String packageName, Handler h, String contextName) {
U 89     if (isJar()) {
90       processZipContent(packageName, l, h, contextName);
91     } else {
92       processClasses(l, packageName, h, contextName);
93     }
692dc7 94   }
47e67b 95
U 96   /**
97    * Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
98    * Annotation besitzen.
99    *
100    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
101    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
692dc7 102    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
47e67b 103    * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
U 104    * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
105    */
40f0b0 106   private void processZipContent(String packageName, ScannerListener l, Handler h, String contextName) {
f4025a 107     try {
47e67b 108       ZipFile zipfile = new ZipFile(new File(path));
f4025a 109       Enumeration en = zipfile.entries();
47e67b 110       //ClassLoader cl = getUrlClassLoader(cls);
f4025a 111       while (en.hasMoreElements()) {
U 112         ZipEntry zipentry = (ZipEntry) en.nextElement();
113         if (!zipentry.isDirectory()) {
47e67b 114           processZipEntry(zipentry, packageName, l, h, contextName);
f4025a 115         } else {
U 116           // ZIP-Dir muss nicht bearbeitet werden
117         }
118       }
119     } catch (IOException ex) {
120       log(Level.SEVERE, ex.getLocalizedMessage());
121     }
122   }
123
47e67b 124   @SuppressWarnings("unchecked")
692dc7 125   private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Handler h, String contextName) {
7456da 126     finest(zipentry.getName());
f4025a 127     String zName = zipentry.getName();
U 128     if (zName.toLowerCase().endsWith(".class")) {
129       int pos = zName.indexOf(".class");
130       String fullClassName = zName.substring(0, pos);
7456da 131       finest("full class name: " + zName);
f4025a 132       String fullClassNameDots = fullClassName.replace('/', '.');
7456da 133       finest("full class name dots: " + fullClassNameDots);
f4025a 134       String pkgName = getPackageName(fullClassNameDots);
7456da 135       finest(" -- package name: " + pkgName);
f4025a 136       if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
U 137         try {
47e67b 138           Class c = urlCL.loadClass(fullClassNameDots);
f4025a 139           if (c != null) {
47e67b 140             if (c.isAnnotationPresent(annotation)) {
7456da 141               finest(" ---- ACTOR ---- " + fullClassNameDots);
47e67b 142               l.annotationFound(c, h, contextName);
f4025a 143             } else {
7456da 144               finest("kein Actor " + fullClassNameDots);
f4025a 145             }
U 146           } else {
7456da 147             finest("class NOT loaded: " + zName);
f4025a 148           }
U 149         } catch (ClassNotFoundException ex) {
7456da 150           finest(" +++++ Class not found: " + ex.getMessage());
f4025a 151         }
U 152       }
153     }
154   }
47e67b 155
40f0b0 156   /**
U 157    * Einen Ordner mit Klassen durchsuchen
158    * 
159    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
160    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
161    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
162    * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
163    * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
164    */
1f6776 165   @SuppressWarnings("unchecked")
40f0b0 166   private void processClasses(ScannerListener l, String packageName, Handler h, String contextName) {
1f6776 167     ClassLoader cl = getCl();
U 168     InputStream stream = cl.getResourceAsStream(packageName.replaceAll("[.]", "/"));
169     BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
170     Iterator i = reader.lines().iterator();
171     while (i.hasNext()) {
172       String line = i.next().toString();
173       if (line.endsWith(".class")) {
174         try {
175           Class actorClass = cl.loadClass(packageName + "."
176                   + line.substring(0, line.lastIndexOf('.')));
177           if (actorClass != null && actorClass.isAnnotationPresent(getAnnotation())) {
178             //wire(h, actorClass, contextName);
179             l.annotationFound(actorClass, h, contextName);
692dc7 180           }
1f6776 181         } catch (ClassNotFoundException ex) {
U 182           // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
692dc7 183         }
1f6776 184       } else {
U 185         //wireActors(js, packageName + "." + line, h, contextName);
186         processClasses(l, packageName + "." + line, h, contextName);
692dc7 187       }
1f6776 188     }
692dc7 189   }
1f6776 190
f4025a 191   private String getPackageName(String fullClassName) {
U 192     String packageName;
193     int pos = fullClassName.lastIndexOf(".");
194     if (pos > 0) {
195       packageName = fullClassName.substring(0, pos);
196     } else {
197       packageName = fullClassName;
198     }
199     return packageName;
692dc7 200   }
U 201
202   public ClassLoader getCl() {
203     return cls.getClassLoader();
f4025a 204   }
47e67b 205
f4025a 206   public ClassLoader getUrlClassLoader(Class c) {
47e67b 207     ClassLoader cl = null;
f4025a 208     try {
47e67b 209       URL url = getPath(c).toURL();
7456da 210       finer("url: " + url.getPath());
47e67b 211       cl = new URLClassLoader(new URL[]{url});
f4025a 212     } catch (MalformedURLException ex) {
U 213       log(Level.SEVERE, ex.getMessage());
214     } finally {
47e67b 215       return cl;
f4025a 216     }
U 217   }
47e67b 218
U 219   public String getPathStr() {
220     if (path != null) {
221       return path.toString();
222     } else {
223       return "";
224     }
225   }
226
227   public boolean isJar() {
228     return !getPathStr().toLowerCase().endsWith(".class");
229   }
230
231   private URI getPath(Class c) {
f4025a 232     String className = c.getName();
7456da 233     finest("this name: " + className);
47e67b 234     String classNameWoPkg = c.getSimpleName();//className.substring(className.lastIndexOf(".") + 1);
7456da 235     finest("Class name: " + classNameWoPkg);
f4025a 236     String classPath = c.getResource(classNameWoPkg + ".class").getPath();
47e67b 237     int pos = classPath.indexOf("!");
f4025a 238     String jarPath;
47e67b 239     if (pos > -1) {
f4025a 240       jarPath = /*"jar:" + */ classPath.substring(0, pos);
U 241     } else {
242       jarPath = classPath;
243     }
7456da 244     finest("path: " + jarPath);
47e67b 245     try {
U 246       return new URI(jarPath);
247     } catch (URISyntaxException ex) {
692dc7 248       Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
47e67b 249       return null;
U 250     }
f4025a 251   }
47e67b 252
7456da 253   private void finest(String msg) {
U 254     log(Level.FINEST, msg);
255   }
47e67b 256
f4025a 257   private void finer(String msg) {
U 258     log(Level.FINER, msg);
259   }
47e67b 260
f4025a 261   private void log(Level l, String msg) {
692dc7 262     Logger.getLogger(Scanner.class.getName()).log(l, msg);
f4025a 263   }
47e67b 264
692dc7 265   public interface ScannerListener {
47e67b 266     public void annotationFound(Class foundClass, Handler h, String contextName);
f4025a 267   }
1f6776 268 }