From cc007e5339f7ffc35cdd9b94ce3b712596a1494e Mon Sep 17 00:00:00 2001 From: ulrich Date: Tue, 03 Dec 2024 16:20:46 +0000 Subject: [PATCH] Scan nach Actor-Klassen aus der Initialisierung der Kontexte herausgeloest und zu 'runInstance' verlagert. Hilfstabelle fuer Actors waehrend der Initialisierung eingefuehrt. --- src/de/uhilger/neon/Factory.java | 275 +++++++++++++++++++++++++++++++++++------------------- 1 files changed, 179 insertions(+), 96 deletions(-) diff --git a/src/de/uhilger/neon/Factory.java b/src/de/uhilger/neon/Factory.java index 14015d5..9f9ef19 100644 --- a/src/de/uhilger/neon/Factory.java +++ b/src/de/uhilger/neon/Factory.java @@ -35,6 +35,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 +45,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 @@ -59,10 +65,13 @@ * @author Ulrich Hilger * @version 1, 6.2.2024 */ -public class Factory { +public class Factory implements ScannerListener { + + private Map<String, List<TempActor>> actorMap; public Factory() { listeners = new ArrayList<>(); + actorMap = new HashMap<>(); } /** @@ -88,21 +97,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 @@ -115,98 +126,93 @@ * @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(); } - + private Authenticator createAuthenticator(NeonDescriptor d) { Authenticator auth = null; - if(d.authenticator != null) { + if (d.authenticator != null) { try { Object authObj = Class.forName(d.authenticator.className) .getDeclaredConstructor().newInstance(); - if(authObj instanceof Authenticator) { + if (authObj instanceof Authenticator) { auth = (Authenticator) authObj; 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? 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); - } - } - if(cd.authenticator instanceof String) { - if(!(auth instanceof Authenticator)) { + ctxAttrs.putAll(cd.attributes); + ctxAttrs.put("serverDataProviderList", sdp); + if (h instanceof Handler handler) { + wire(handler, cd.attributes.get("contextName")); + } + if (cd.authenticator instanceof String) { + if (!(auth instanceof Authenticator)) { auth = createAuthenticator(d); } - if(auth instanceof Authenticator) { - ctx.setAuthenticator(auth); + if (auth instanceof Authenticator) { + ctx.setAuthenticator(auth); ctx.getAttributes().putAll(d.authenticator.attributes); fireAuthenticatorCreated(ctx, auth); // event umbenennen in etwas wie authAdded oder so } - } - - //Authenticator auth = createAuthenticator(d); - //if (auth instanceof Authenticator && cd.authenticator instanceof String) { - // ctx.setAuthenticator(auth); - // ctx.getAttributes().putAll(d.authenticator.attributes); - // fireAuthenticatorCreated(ctx, auth); - //} - if(cd.filter != null) { - for(String filterClassName : cd.filter) { + if (cd.filter != null) { + for (String filterClassName : cd.filter) { // Object filterObj = Class.forName(filterClassName) - .getDeclaredConstructor().newInstance(); - if(filterObj instanceof Filter) { - Filter filter = (Filter) filterObj; + .getDeclaredConstructor().newInstance(); + if (filterObj instanceof Filter filter) { ctx.getFilters().add(filter); } } @@ -218,10 +224,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 { @@ -235,7 +241,7 @@ } return h; } - + private HttpHandler getHandlerInstance(ContextDescriptor cd) { try { Object handlerObj = Class.forName(cd.className) @@ -246,38 +252,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 @@ -286,22 +326,37 @@ 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<TempActor> actorList = actorMap.get(contextName); + Iterator<TempActor> i = actorList.iterator(); + while(i.hasNext()) { + TempActor actor = i.next(); + h.setActor(actor.getHttpMethod(), actor.getRoute(), actor.getActorClassName()); } } /* -------------- FactoryListener Implementierung --------------- */ - private List<FactoryListener> listeners; public void addListener(FactoryListener l) { @@ -315,6 +370,8 @@ public void destroy() { this.listeners.clear(); this.listeners = null; + this.actorMap.clear(); + this.actorMap = null; } private void fireServerCreated(HttpServer server) { @@ -322,7 +379,7 @@ l.serverCreated(server); } } - + private void fireHandlerCreated(HttpContext ctx, HttpHandler h) { for (FactoryListener l : listeners) { l.handlerCreated(ctx, h); @@ -334,7 +391,7 @@ l.contextCreated(context); } } - + private void fireAuthenticatorCreated(HttpContext context, Authenticator auth) { for (FactoryListener l : listeners) { l.authenticatorCreated(context, auth); @@ -346,4 +403,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) { + TempActor tempActor = new TempActor(); + tempActor.setContextName(contextName); + tempActor.setHttpMethod(action.type()); + tempActor.setRoute(action.route()); + tempActor.setActorClassName(foundClass.getName()); + + List<TempActor> 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