Ultrakompakter HTTP Server
ulrich
2024-12-01 12fdfa4c6bd2515e142a996673489cfdd656cb0a
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 /**
12fdfa 38  * Die Klasse Scanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, an welchem Ablageort sie
U 39  * sich befindet und diesen Ort nach Klassen zu durchsuchen, die eine gegebene Annotation besitzen.
40  *
41  * Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner im Dateisystem sein.
47e67b 42  *
f4025a 43  * @author Ulrich Hilger
U 44  * @version 0.1, 30.11.2024
45  */
692dc7 46 public final class Scanner {
47e67b 47
U 48   private final URI path;
49   private final Class annotation;
692dc7 50
47e67b 51   private final Class cls;
U 52   private final ClassLoader urlCL;
53
54   /**
12fdfa 55    * Einen Scanner erzeugen, der den Ort, in dem sich eine gegebene Klasse befindet, nach Klassen
1f6776 56    * durchsucht, die eine bestimmte Annotation besitzen
12fdfa 57    *
U 58    * Der Ort fur Klassen kann ein Java-Archiv (.jar) oder ein Ordner 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.
12fdfa 76    *
U 77    * Anhand der im Konstruktor uebergebenen Klasse wird deren Ablageort ermittelt, entweder ein
78    * Ordner im Dateisystem oder ein Java-Archiv (.jar). Dieser Ablageort wird dann nach annotierten
79    * Klassen durchsucht. Gefundene Klassen werden dem Listener gemeldet.
40f0b0 80    *
U 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    */
1f6776 87   public void process(ScannerListener l, String packageName, Handler h, String contextName) {
U 88     if (isJar()) {
89       processZipContent(packageName, l, h, contextName);
90     } else {
91       processClasses(l, packageName, h, contextName);
92     }
692dc7 93   }
47e67b 94
U 95   /**
96    * Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
97    * Annotation besitzen.
98    *
99    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
100    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
692dc7 101    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
47e67b 102    * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
U 103    * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
104    */
40f0b0 105   private void processZipContent(String packageName, ScannerListener l, Handler h, String contextName) {
f4025a 106     try {
47e67b 107       ZipFile zipfile = new ZipFile(new File(path));
f4025a 108       Enumeration en = zipfile.entries();
47e67b 109       //ClassLoader cl = getUrlClassLoader(cls);
f4025a 110       while (en.hasMoreElements()) {
U 111         ZipEntry zipentry = (ZipEntry) en.nextElement();
112         if (!zipentry.isDirectory()) {
47e67b 113           processZipEntry(zipentry, packageName, l, h, contextName);
f4025a 114         } else {
U 115           // ZIP-Dir muss nicht bearbeitet werden
116         }
117       }
118     } catch (IOException ex) {
119       log(Level.SEVERE, ex.getLocalizedMessage());
120     }
121   }
122
47e67b 123   @SuppressWarnings("unchecked")
692dc7 124   private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Handler h, String contextName) {
7456da 125     finest(zipentry.getName());
f4025a 126     String zName = zipentry.getName();
U 127     if (zName.toLowerCase().endsWith(".class")) {
128       int pos = zName.indexOf(".class");
129       String fullClassName = zName.substring(0, pos);
7456da 130       finest("full class name: " + zName);
f4025a 131       String fullClassNameDots = fullClassName.replace('/', '.');
7456da 132       finest("full class name dots: " + fullClassNameDots);
f4025a 133       String pkgName = getPackageName(fullClassNameDots);
7456da 134       finest(" -- package name: " + pkgName);
f4025a 135       if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
U 136         try {
47e67b 137           Class c = urlCL.loadClass(fullClassNameDots);
f4025a 138           if (c != null) {
47e67b 139             if (c.isAnnotationPresent(annotation)) {
7456da 140               finest(" ---- ACTOR ---- " + fullClassNameDots);
47e67b 141               l.annotationFound(c, h, contextName);
f4025a 142             } else {
7456da 143               finest("kein Actor " + fullClassNameDots);
f4025a 144             }
U 145           } else {
7456da 146             finest("class NOT loaded: " + zName);
f4025a 147           }
U 148         } catch (ClassNotFoundException ex) {
7456da 149           finest(" +++++ Class not found: " + ex.getMessage());
f4025a 150         }
U 151       }
152     }
153   }
47e67b 154
40f0b0 155   /**
U 156    * Einen Ordner mit Klassen durchsuchen
12fdfa 157    *
40f0b0 158    * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
U 159    * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
160    * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
161    * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
162    * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
163    */
1f6776 164   @SuppressWarnings("unchecked")
40f0b0 165   private void processClasses(ScannerListener l, String packageName, Handler h, String contextName) {
1f6776 166     ClassLoader cl = getCl();
U 167     InputStream stream = cl.getResourceAsStream(packageName.replaceAll("[.]", "/"));
168     BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
169     Iterator i = reader.lines().iterator();
170     while (i.hasNext()) {
171       String line = i.next().toString();
172       if (line.endsWith(".class")) {
173         try {
174           Class actorClass = cl.loadClass(packageName + "."
175                   + line.substring(0, line.lastIndexOf('.')));
176           if (actorClass != null && actorClass.isAnnotationPresent(getAnnotation())) {
177             //wire(h, actorClass, contextName);
178             l.annotationFound(actorClass, h, contextName);
692dc7 179           }
1f6776 180         } catch (ClassNotFoundException ex) {
U 181           // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
692dc7 182         }
1f6776 183       } else {
U 184         //wireActors(js, packageName + "." + line, h, contextName);
185         processClasses(l, packageName + "." + line, h, contextName);
692dc7 186       }
1f6776 187     }
692dc7 188   }
1f6776 189
f4025a 190   private String getPackageName(String fullClassName) {
U 191     String packageName;
192     int pos = fullClassName.lastIndexOf(".");
193     if (pos > 0) {
194       packageName = fullClassName.substring(0, pos);
195     } else {
196       packageName = fullClassName;
197     }
198     return packageName;
692dc7 199   }
U 200
201   public ClassLoader getCl() {
202     return cls.getClassLoader();
f4025a 203   }
47e67b 204
f4025a 205   public ClassLoader getUrlClassLoader(Class c) {
47e67b 206     ClassLoader cl = null;
f4025a 207     try {
47e67b 208       URL url = getPath(c).toURL();
7456da 209       finer("url: " + url.getPath());
47e67b 210       cl = new URLClassLoader(new URL[]{url});
f4025a 211     } catch (MalformedURLException ex) {
U 212       log(Level.SEVERE, ex.getMessage());
213     } finally {
47e67b 214       return cl;
f4025a 215     }
U 216   }
47e67b 217
U 218   public String getPathStr() {
219     if (path != null) {
220       return path.toString();
221     } else {
222       return "";
223     }
224   }
225
226   public boolean isJar() {
227     return !getPathStr().toLowerCase().endsWith(".class");
228   }
229
230   private URI getPath(Class c) {
f4025a 231     String className = c.getName();
7456da 232     finest("this name: " + className);
47e67b 233     String classNameWoPkg = c.getSimpleName();//className.substring(className.lastIndexOf(".") + 1);
7456da 234     finest("Class name: " + classNameWoPkg);
f4025a 235     String classPath = c.getResource(classNameWoPkg + ".class").getPath();
47e67b 236     int pos = classPath.indexOf("!");
f4025a 237     String jarPath;
47e67b 238     if (pos > -1) {
f4025a 239       jarPath = /*"jar:" + */ classPath.substring(0, pos);
U 240     } else {
241       jarPath = classPath;
242     }
7456da 243     finest("path: " + jarPath);
47e67b 244     try {
U 245       return new URI(jarPath);
246     } catch (URISyntaxException ex) {
692dc7 247       Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
47e67b 248       return null;
U 249     }
f4025a 250   }
47e67b 251
7456da 252   private void finest(String msg) {
U 253     log(Level.FINEST, msg);
254   }
47e67b 255
f4025a 256   private void finer(String msg) {
U 257     log(Level.FINER, msg);
258   }
47e67b 259
f4025a 260   private void log(Level l, String msg) {
692dc7 261     Logger.getLogger(Scanner.class.getName()).log(l, msg);
f4025a 262   }
47e67b 263
692dc7 264   public interface ScannerListener {
12fdfa 265
47e67b 266     public void annotationFound(Class foundClass, Handler h, String contextName);
f4025a 267   }
1f6776 268 }