| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | * @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<>(); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 |
| | |
| | | * @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); |
| | | //} |
| | | for(String filterClassName : cd.filter) { |
| | | // |
| | | Object filterObj = Class.forName(filterClassName) |
| | | .getDeclaredConstructor().newInstance(); |
| | | if(filterObj instanceof Filter) { |
| | | Filter filter = (Filter) filterObj; |
| | | ctx.getFilters().add(filter); |
| | | if (cd.filter != null) { |
| | | for (String filterClassName : cd.filter) { |
| | | // |
| | | Object filterObj = Class.forName(filterClassName) |
| | | .getDeclaredConstructor().newInstance(); |
| | | if (filterObj instanceof Filter filter) { |
| | | ctx.getFilters().add(filter); |
| | | } |
| | | } |
| | | } |
| | | fireHandlerCreated(ctx, h); |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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 { |
| | |
| | | } |
| | | return h; |
| | | } |
| | | |
| | | |
| | | private HttpHandler getHandlerInstance(ContextDescriptor cd) { |
| | | try { |
| | | Object handlerObj = Class.forName(cd.className) |
| | |
| | | // 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 |
| | |
| | | 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) { |
| | |
| | | public void destroy() { |
| | | this.listeners.clear(); |
| | | this.listeners = null; |
| | | this.actorMap.clear(); |
| | | this.actorMap = null; |
| | | } |
| | | |
| | | private void fireServerCreated(HttpServer server) { |
| | |
| | | l.serverCreated(server); |
| | | } |
| | | } |
| | | |
| | | |
| | | private void fireHandlerCreated(HttpContext ctx, HttpHandler h) { |
| | | for (FactoryListener l : listeners) { |
| | | l.handlerCreated(ctx, h); |
| | |
| | | l.contextCreated(context); |
| | | } |
| | | } |
| | | |
| | | |
| | | private void fireAuthenticatorCreated(HttpContext context, Authenticator auth) { |
| | | for (FactoryListener l : listeners) { |
| | | l.authenticatorCreated(context, auth); |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |