From d3cc22c6082522c7518fe6e12010f11413b4e0c4 Mon Sep 17 00:00:00 2001
From: ulrich <ulli@ulrichs-mac.fritz.box>
Date: Mon, 24 Nov 2025 17:19:28 +0000
Subject: [PATCH] WsServerDescriptor hinzugefuegt
---
src/de/uhilger/neon/Factory.java | 299 +++++++++++++++++++++++++++++++++++++++++++----------------
1 files changed, 217 insertions(+), 82 deletions(-)
diff --git a/src/de/uhilger/neon/Factory.java b/src/de/uhilger/neon/Factory.java
index b6550ba..4405332 100644
--- a/src/de/uhilger/neon/Factory.java
+++ b/src/de/uhilger/neon/Factory.java
@@ -17,8 +17,10 @@
*/
package de.uhilger.neon;
+import de.uhilger.neon.entity.ActorDescriptor;
import com.google.gson.Gson;
import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
@@ -41,6 +43,9 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import de.uhilger.neon.Scanner.ScannerListener;
/**
* Einen Neon-Server aus einer Beschreibungsdatei herstellen
@@ -58,10 +63,13 @@
* @author Ulrich Hilger
* @version 1, 6.2.2024
*/
-public class Factory {
+public class Factory implements ScannerListener {
+
+ private Map<String, List<ActorDescriptor>> actorMap;
public Factory() {
listeners = new ArrayList<>();
+ actorMap = new HashMap<>();
}
/**
@@ -87,21 +95,23 @@
Gson gson = new Gson();
return gson.fromJson(sb.toString(), NeonDescriptor.class);
}
-
- public void runInstance(NeonDescriptor d)
- throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
+
+ 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)
- throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
+ 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
*
+ * @param starter die Klasse, mit der Neon durch Aufruf dieser Methode gestartet wird
* @param d das Object mit der Serverbeschreibung
* @param packageNames Namen der Packages, aus der rekursiv vorgefundene Actors eingebaut werden
* sollen
@@ -114,77 +124,115 @@
* @throws InvocationTargetException
* @throws IOException
*/
- public void runInstance(NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)
- throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
+ public void runInstance(Class starter, NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)
+ throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
- List serverList = d.server;
- Iterator<ServerDescriptor> serverIterator = serverList.iterator();
- while (serverIterator.hasNext()) {
- ServerDescriptor sd = serverIterator.next();
+
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, System.getProperty("java.class.path"));
+
+ List<ServerDescriptor> serverList = d.server;
+ for (ServerDescriptor sd : serverList) {
HttpServer server = HttpServer.create(new InetSocketAddress(sd.port), 0);
fireServerCreated(server);
- if(packageNames == null) {
+ if (packageNames == null) {
packageNames = d.actorPackages;
+ }
+
+ Scanner scn = new Scanner(starter, Actor.class);
+ for (String packageName : packageNames) {
+ scn.process(this, packageName, new Object[]{});
+ // ctx.getAttributes().put("serverDataProviderList", sdp);
}
- addContexts(d, server, sd.contexts, packageNames, sdp);
+
+ addContexts(d, server, sd.contexts, sdp);
server.setExecutor(Executors.newFixedThreadPool(10));
server.start();
}
- fireInstanceStarted();
+
+// List<WsServerDescriptor> wsserverList = d.wsserver;
+// for (WsServerDescriptor wsd : wsserverList) {
+// WebSocketServer wsserver = new SimpleWsServer(new InetSocketAddress(wsd.port));
+// fireWsServerCreated(wsserver);
+// }
+
+ fireInstanceStarted();
+
+
}
-
+
private Authenticator createAuthenticator(NeonDescriptor d) {
+ Logger.getLogger(Factory.class.getName()).log(
+ Level.FINER, "{0} ", new Object[]{"creating Authenticator"});
Authenticator auth = null;
- if(d.authenticator != null) {
+ if (d.authenticator != null) {
try {
+ Logger.getLogger(Factory.class.getName()).log(
+ Level.FINER, "Authenticator className {0} ", new Object[]{d.authenticator.className});
Object authObj = Class.forName(d.authenticator.className)
.getDeclaredConstructor().newInstance();
- if(authObj instanceof Authenticator) {
+ if (authObj instanceof Authenticator) {
auth = (Authenticator) authObj;
+ Logger.getLogger(Factory.class.getName()).log(
+ Level.FINER, "Authenticator className {0} created", new Object[]{d.authenticator.className});
return auth;
}
- } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
- InstantiationException | IllegalAccessException | IllegalArgumentException |
- InvocationTargetException ex) {
+ } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
+ | InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException ex) {
// Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
+ Logger.getLogger(Factory.class.getName()).log(
+ Level.FINER, "Authenticator className {0} not created, error {1}",
+ new Object[]{d.authenticator.className, ex.getLocalizedMessage()});
+
return null;
- }
- }
+ }
+ }
return auth;
}
- private void addContexts(NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,
- List<DataProvider> sdp)
- throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
+ private void addContexts(NeonDescriptor d, HttpServer server, List<ContextDescriptor> contextList,
+ List<DataProvider> sdp)
+ throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
- Map<String, HttpHandler> sharedHandlers = new HashMap();
+ Map<String, HttpHandler> sharedHandlers = new HashMap<>();
Iterator<ContextDescriptor> contextIterator = contextList.iterator();
+ Authenticator auth = null;
while (contextIterator.hasNext()) {
ContextDescriptor cd = contextIterator.next();
HttpHandler h = buildHandler(cd, sharedHandlers);
if (h != null) {
- HttpContext ctx = server.createContext(cd.contextPath, h);
+ HttpContext ctx = server.createContext(cd.contextPath, h);
Map<String, Object> ctxAttrs = ctx.getAttributes();
/*
Achtung: Wenn verschiedene Elemente dasselbe Attribut
- deklarieren, ueberschreiben sie sich die Attribute gegenseitig.
+ deklarieren, ueberschreiben sich die Attribute gegenseitig.
*/
- ctxAttrs.putAll(cd.attributes);
- if (h instanceof Handler) {
- for (String packageName : packageNames) {
- wireActors(
- packageName, Actor.class, (Handler) h,
- cd.attributes.get("contextName"));
- ctx.getAttributes().put("serverDataProviderList", sdp);
+ ctxAttrs.putAll(cd.attributes);
+ ctxAttrs.put("serverDataProviderList", sdp);
+ if (h instanceof Handler) {
+ wire((Handler) h, cd.attributes.get("contextName"));
+ }
+ if (cd.authenticator instanceof String) {
+ if (!(auth instanceof Authenticator)) {
+ auth = createAuthenticator(d);
}
- }
- Authenticator auth = createAuthenticator(d);
- if (auth instanceof Authenticator && cd.authenticator instanceof String) {
- ctx.setAuthenticator(auth);
+ if (auth instanceof Authenticator) {
+ ctx.setAuthenticator(auth);
ctx.getAttributes().putAll(d.authenticator.attributes);
- fireAuthenticatorCreated(ctx, auth);
+ fireAuthenticatorCreated(ctx, auth); // event umbenennen in etwas wie authAdded oder so
+ }
+ }
+ if (cd.filter != null) {
+ for (String filterClassName : cd.filter) {
+ //
+ Object filterObj = Class.forName(filterClassName)
+ .getDeclaredConstructor().newInstance();
+ if (filterObj instanceof Filter) {
+ ctx.getFilters().add((Filter) filterObj);
+ }
+ }
}
fireHandlerCreated(ctx, h);
fireContextCreated(ctx);
@@ -193,10 +241,10 @@
}
}
}
-
- private HttpHandler buildHandler(ContextDescriptor cd, Map<String, HttpHandler> sharedHandlers) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
+
+ private HttpHandler buildHandler(ContextDescriptor cd, Map<String, HttpHandler> sharedHandlers) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
- HttpHandler h;
+ HttpHandler h;
if (!cd.sharedHandler) {
h = getHandlerInstance(cd);
} else {
@@ -210,7 +258,7 @@
}
return h;
}
-
+
private HttpHandler getHandlerInstance(ContextDescriptor cd) {
try {
Object handlerObj = Class.forName(cd.className)
@@ -221,38 +269,72 @@
// kein HttpHandler aus newInstance
return null;
}
- } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
- InstantiationException | IllegalAccessException | IllegalArgumentException |
- InvocationTargetException ex) {
+ } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
+ | InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException ex) {
// Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
return null;
}
}
- 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);
+ /**
+ * Diese Testmethode zeigt, dass die Methode getResourceAsStream nicht funktioniert wie
+ * dokumentiert.
+ *
+ * 1. Sie liefert den Inhalt einer gegebenen Package mitsamt Unterpackages als Stream, wenn sie
+ * auf eine Packagestruktur angewendet wird, die unverpackt in einem Verzeichnis des Dateisystems
+ * liegt.
+ *
+ * 2. Sie liefert - faelschlicherweise - null bzw. einen leeren Stream, wenn die Packagestruktur
+ * in einem Jar verpackt ist.
+ *
+ * Saemtliche Versuche, ueber den ClassPath oder die Pfadangabe der Package das Verhalten zu
+ * aendern, gehen bislang fehl (z.B. / oder . als Separator, / oder . zu Beginn enthalten oder
+ * nicht, realative oder absolute packagepfadangabe). Es ist auch unerheblich, ob
+ * Class.getResourceAsStream oder Class.getClassLoader().getResourceAsStream verwendet wird.
+ *
+ * Unabhaengig davon, ob und wie letztlich im Fall 2. oben die Methode getResourceAsStream dazu zu
+ * bringen waere, eine Inhaltsliste fuer eine Package zu liefern ist allein die Tatsache, dass
+ * sich die Methode unterschiedlich verhaelt bereits ein schwerer Bug.
+ *
+ * @param c
+ * @param packageName
+ */
+ private void listClasses(Class c, String packageName) {
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "packageName: " + packageName);
+ //ClassLoader cl = c.getClassLoader();
+ String newPackageName = packageName.replaceAll("[.]", "/");
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "newPackageName: " + newPackageName);
+ InputStream stream = c
+ .getResourceAsStream(newPackageName);
+ if (stream != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+ Iterator i = reader.lines().iterator();
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, Long.toString(reader.lines().count()));
+ while (i.hasNext()) {
+ String line = i.next().toString();
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "class to inspect: " + line);
+ if (line.endsWith(".class")) {
+ try {
+ Class actorClass = c.getClassLoader().loadClass(packageName + "."
+ + line.substring(0, line.lastIndexOf('.')));
+ if (actorClass != null && actorClass.isAnnotationPresent(Actor.class)) {
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "ACTOR");
+ } else {
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "no actor");
+ }
+ } catch (ClassNotFoundException ex) {
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "Klasse nicht gefunden");
}
- } catch (ClassNotFoundException ex) {
- // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
+ } else {
+ listClasses(c, packageName + "." + line);
}
- } else {
- wireActors(packageName + "." + line, annotation, h, contextName);
}
+ } else {
+ Logger.getLogger(Factory.class.getName()).log(Level.FINER, "stream ist null");
}
}
-
+
/*
Eine Action-Annotation enthaelt gewoehnlich die Route,
die 'unterhalb' des Kontextpfades als 'Ausloeser' zur
@@ -261,22 +343,40 @@
Wenn die Action fuer alle Routen 'unterhalb' des
Kontextpfades ausgefuehrt werden soll, muss die Action
als Route '/' angeben.
+ */
+
+ /*
+ Tradeoff:
+ Es muss bei Initialisierung die Actor-Klasse ganz durchlaufen werden, um alle Methoden
+ zu finden, die eine Action-Annotation haben. Der Handler 'merkt' sich lediglich den Namen der
+ Actor-Klassen. Daher muessen bei jedem Aufruf eines Actors ueber den Handler abermals
+ alle Methoden dieses Actors durchsucht werden, um diejenige Methode zu finden, die mit
+ der zur Route des Requests passenden Action annotiert ist.
+
+ Dieser Tradeoff bewirkt, dass nicht grosse Geflechte aus Klassen- und Methodenobjekten
+ fuer die gesamten Actors einer Anwendung im Speicher gehalten werden muessen
+ sondern dynamisch zur Laufzeit instanziiert werden.
*/
- private void wire(Handler h, Class c, String contextName) {
- Method[] methods = c.getMethods();
- for (Method method : methods) {
- Action action = method.getAnnotation(Action.class);
- if (action != null) {
- List actionHandlers = Arrays.asList(action.handler());
- if (actionHandlers.contains(contextName)) {
- h.setActor(action.type(), action.route(), c.getName());
- }
- }
+
+ /**
+ * Actor-Klassen dem Handler hinzufuegen
+ *
+ * @param h Handler, dem der Actor hinzugefuegt wird, falls der Kontext uebereinstimmt
+ * @param contextName Name des Kontext, dem der Handler zugeordnet ist
+ */
+ private void wire(Handler h, String contextName) {
+ List<ActorDescriptor> actorList = actorMap.get(contextName);
+ Iterator<ActorDescriptor> i = actorList.iterator();
+ while(i.hasNext()) {
+ ActorDescriptor actor = i.next();
+ Logger.getLogger(Factory.class.getName()).log(
+ Level.FINER, "actorClassName {0} route {1}",
+ new Object[]{actor.getActorClassName(), actor.getRoute()});
+ h.setActor(actor.getHttpMethod(), actor.getRoute(), actor.getActorClassName());
}
}
/* -------------- FactoryListener Implementierung --------------- */
-
private List<FactoryListener> listeners;
public void addListener(FactoryListener l) {
@@ -290,6 +390,8 @@
public void destroy() {
this.listeners.clear();
this.listeners = null;
+ this.actorMap.clear();
+ this.actorMap = null;
}
private void fireServerCreated(HttpServer server) {
@@ -298,6 +400,13 @@
}
}
+ //private void fireWsServerCreated(WebSocketServer wsserver) {
+ // for (WsFactoryListener l : listeners) {
+ // l.wsServerCreated(wsserver);
+ // }
+ //}
+
+
private void fireHandlerCreated(HttpContext ctx, HttpHandler h) {
for (FactoryListener l : listeners) {
l.handlerCreated(ctx, h);
@@ -309,7 +418,7 @@
l.contextCreated(context);
}
}
-
+
private void fireAuthenticatorCreated(HttpContext context, Authenticator auth) {
for (FactoryListener l : listeners) {
l.authenticatorCreated(context, auth);
@@ -321,4 +430,30 @@
l.instanceStarted();
}
}
-}
+
+ /* -------------- ScannerListener Implementierung --------------- */
+ @Override
+ public void annotationFound(Class foundClass, Object[] params) {
+ Method[] methods = foundClass.getMethods();
+ for (Method method : methods) {
+ Action action = method.getAnnotation(Action.class);
+ if (action != null) {
+ List<String> actionHandlers = Arrays.asList(action.handler());
+ for (String contextName : actionHandlers) {
+ ActorDescriptor tempActor = new ActorDescriptor();
+ tempActor.setContextName(contextName);
+ tempActor.setHttpMethod(action.type());
+ tempActor.setRoute(action.route());
+ tempActor.setActorClassName(foundClass.getName());
+
+ List<ActorDescriptor> actorList = actorMap.get(contextName);
+ if(actorList == null) {
+ actorList = new ArrayList<>();
+ }
+ actorList.add(tempActor);
+ actorMap.put(contextName, actorList);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--
Gitblit v1.9.3